views:

1118

answers:

6

I want to write a little "DBQuery" function in perl so I can have one-liners which send an SQL statement and receive back and an array of hashes, i.e. a recordset. However, I'm running into an issue with Perl syntax (and probably some odd pointer/reference issue) which is preventing me from packing out the information from the hash that I'm getting from the database. The sample code below demonstrates the issue.

I can get the data "Jim" out of a hash inside an array with this syntax:

print $records[$index]{'firstName'}

returns "Jim"

but if I copy the hash record in the array to its own hash variable first, then I strangely can't access the data anymore in that hash:


    %row = $records[$index];
    $row{'firstName'};

returns "" (blank)

Here is the full sample code showing the problem. Any help is appreciated:


my @records = (
   {'id' => 1, 'firstName' => 'Jim'},
   {'id' => 2, 'firstName' => 'Joe'}
);
my @records2 = ();

$numberOfRecords = scalar(@records);
print "number of records: " . $numberOfRecords . "\n";
for(my $index=0; $index < $numberOfRecords; $index++) {

   #works
   print 'you can print the records like this: ' . $records[$index]{'firstName'} . "\n";

   #does NOT work
   %row = $records[$index];
   print 'but not like this: ' . $row{'firstName'} . "\n";

} 
+15  A: 

The nested data structure contains a hash reference, not a hash.

# Will work (the -> dereferences the reference)
$row = $records[$index];
print "This will work: ", $row->{firstName}, "\n";

# This will also work, by promoting the hash reference into a hash
%row = %{ $records[$index] };
print "This will work: ", $row{firstName}, "\n";

If you're ever presented with a deep Perl data structure, you may profit from printing it using Data::Dumper to print it into human-readable (and Perl-parsable) form.

Commodore Jaeger
If you ever need to explictly dereference a reference then %{$row} will work, which can be useful sometimes to ensure you're passing the right thing to the right sort of thing.
Free Wildebeest
+1  A: 

what you actually have in your array is a hashref, not a hash. If you don't understand this concept, its probably worth reading the perlref documentation.

to get the hash you need to do

my %hash = %{@records[$index]};

Eg.

my @records = (
     {'id' => 1, 'firstName' => 'Jim'}, 
     {'id' => 2, 'firstName' => 'Joe'}
  );

  my %hash = %{$records[1]};

  print $hash{id}."\n";

Although. I'm not sure why you would want to do this, unless its for academic purposes. Otherwise, I'd recommend either fetchall_hashref/fetchall_arrayref in the DBI module or using something like Class::DBI.

Matthew Watson
+3  A: 

The array of hashes doesn't actually contain hashes, but rather an references to a hash. This line:

%row = $records[$index];

assigns %row with one entry. The key is the scalar:

   {'id' => 1, 'firstName' => 'Jim'},

Which is a reference to the hash, while the value is blank.

What you really want to do is this:

$row = $records[$index];
$row->{'firstName'};

or else:

$row = %{$records[$index];}
$row{'firstName'};
Nathan Fellman
That last bit of code makes no sense. It should be something like: %row=%{$records[$index]}; print $row{'firstName'};
Peter Stuifzand
+2  A: 

Others have commented on hashes vs hashrefs. One other thing that I feel should be mentioned is your DBQuery function - it seems you're trying to do something that's already built into the DBI? If I understand your question correctly, you're trying to replicate something like selectall_arrayref:

This utility method combines "prepare", "execute" and "fetchall_arrayref" into a single call. It returns a reference to an array containing a reference to an array (or hash, see below) for each row of data fetched.

zigdon
A: 

To add to the lovely answers above, let me add that you should always, always, always (yes, three "always"es) use "use warnings" at the top of your code. If you had done so, you would have gotten the warning "Reference found where even-sized list expected at -e line 1."

theorbtwo
A: 

Also note a good perl idiom to use is

for my $rowHR ( @records ) {
   my %row = %$rowHR; 
   #or whatever...
}

to iterate through the list.

jonfm