ryddler has asked for the wisdom of the Perl Monks concerning the following question:

For some reason I just can't seem to wrap myself around dereferencing. Here I have a data structure I built on the fly from DB queries using the following snippet:

$SQLBuy = $dbh_metro->prepare("SELECT * FROM BuyersSellers WHERE F +ileNumber ='".$filenumber."'"); $rv = $SQLBuy->execute; while (my $hash_ref = $SQLBuy->fetchrow_hashref) { push @BuySellHashref, \$hash_ref; } $SQLBuy->finish; undef $SQLBuy; foreach (@BuySellHashref) { $SQLPEOPLE = $dbh_metro->prepare("SELECT * FROM People WHERE P +ersonID = '".${$_}->{PersonID}."'"); $rv = $SQLPEOPLE->execute; while (my $hash_ref = $SQLPEOPLE->fetchrow_hashref) { ${$_}->{PersonID} = \$hash_ref; } $SQLPEOPLE->finish; undef $SQLPEOPLE; }

After placing the hash reference in the array in the first query, it seemed to make sense to replace the value of one field (PersonID) with the data returned from the second query, because that value is the primary key of the second table, and no longer necessary after the query is performed. I figured I'd have the additional bonus of being able to only have one data structure to manipulate later on in the script. The problem I'm now facing is that what I thought was the proper dereferencing syntax

foreach $buysell (@BuySellHashref) { if (${$buysell}->{BuyerORSeller} =~ /BUYER/i) { print qq|<TR CLASS=$class><TD>$buysell->{PersonID}->{L +astName}</TD></TR>|; } }
gives me an error stating "Not a HASH reference".

I used Data::Dumper to view the structure I'm using, and it looks exactly like I figured it did, shown below:

$VAR1 = \{ 'FileNumber' => '01-0001', 'PersonID' => \{ 'FirstName' => 'JOE', 'PersonID' => '01SMITH', 'MiddleInitial' => 'D', 'LastName' => 'SMITH' }, 'BuyerSellerID' => '01-0001Bjoesmith', 'BuyerORSeller' => 'BUYER' };

"PersonID" holds a reference to a hash that conatins the data from my second query, but alas, I stumble over the dereferencing. If I want to return the value of "LastName" from the nested hash, what am I missing here?

Is there a good way to backstep out from the inside in order to build the full dereference?

ryddler

Replies are listed 'Best First'.
Re: Dereferencing nested references
by repson (Chaplain) on Jan 16, 2001 at 09:03 UTC
    No, you are not storing a reference to a hash, but a ref to a ref to a hash.

    The key is this:

    while (my $hash_ref = $SQLBuy->fetchrow_hashref) { push @BuySellHashref, \$hash_ref; }
    In there $hash_ref, as it says, contains a reference to a hash. Then you push a reference to $hash_ref (\$hash_ref) onto your array when you just want the hashref ($hash_ref). So change it to push @BuySellHashref, $hash_ref;

    Looping is then

    for my $hashref (@BuySellHashref) { print $hashref->{personID} . "\n"; }

    Or if you kept on the same way as before then stick to ${$hashref}->{personID} like you were doing at some points.

      No, you are not storing a reference to a hash, but a ref to a ref to a hash.

      Of course, what was I thinking? I didn't even realize what I was doing until I read and reread this line a second time. DBI was returning a reference and I didn't even blink before stuffing a ref to a ref into my hash value. repson, I bow before you with humility and respect.

      In the interest of learning, however, how would I dereference the ref to a ref that I embedded into that hash?

      ryddler

Re: Dereferencing nested references
by chipmunk (Parson) on Jan 16, 2001 at 09:21 UTC
    Because of the way you created @BuySellHashref, each element is actually a SCALAR reference to a HASH reference. That's why ${buysell}->{BuyerOrSeller} works, when a straight HASH ref would be used as $buysell->{BuyerOrSeller}.

    The error is generated for the print line, where you try to access $buysell->{PersonID}. Since $buysell is not a HASH reference, that expression causes an error. It would have to be ${$buysell}->{PersonID} instead.
     

    Frankly, I don't see what advantage you get from this extra level of references. I would write the code as:

    $SQLBuy = $dbh_metro->prepare(<<"EndOfSQL"); SELECT * FROM BuyersSellers WHERE FileNumber = ? EndOfSQL $rv = $SQLBuy->execute($filenumber); while (my $hash_ref = $SQLBuy->fetchrow_hashref) { push @BuySellHashref, { %{$hash_ref} }; } undef $SQLBuy; $SQLPEOPLE = $dbh_metro->prepare(<<"EndOfSQL"); SELECT * FROM People WHERE PersonID = ? EndOfSQL foreach (@BuySellHashref) { $rv = $SQLPEOPLE->execute($_->{PersonID}); while (my $hash_ref = $SQLPEOPLE->fetchrow_hashref) { $_->{PersonID} = { %{$hash_ref} }; } } undef $SQLPEOPLE; foreach $buysell (@BuySellHashref) { if ($buysell->{BuyerORSeller} =~ /BUYER/i) { print "<TR CLASS=$class><TD>$buysell->{PersonID}->{LastName}", "</TD></TR>"; } }
    It is important to note how I am handling the reference returned by fetchrow_hashref: push @BuySellHashref, { %{$hash_ref} }; This is because of the following caveat from the DBI documentation for the fetchrow_hashref method:
    Currently, a new hash reference is returned for each row. This will change in the future to return the same hash ref each time, so don't rely on the current behaviour.
    If you don't create a new hash from the hashref each time, then after the implementation of DBI changes, each element in @BuySellHashref would point to exactly the same hash.
      push @BuySellHashref, { %{$hash_ref} };

      This is what I'm having trouble wrapping myself around. I'm not sure I understand exactly what's happening in this snip, so I'll try to break it down the way I see it, and if I'm way out in left field, I'll get some feedback.

      %{$hash_ref}

      This dereferences $hash_ref and then...

      push @BuySellHashref, { dereferenced $hash_ref }

      This creates an anonymous hash containing the hash data from the dereferenced $hash_ref and pushes a reference to it into @BuySellHashref, which means instead of just having the original ref (or as in my original code a ref to a ref), I have a ref to an anonymous hash containing all the original data from $hash_ref.

      ryddler

Re: Dereferencing nested references
by eg (Friar) on Jan 16, 2001 at 09:22 UTC

    Aside from what repson and chipmunk say, I think another problem here is with your SQL query. Why aren't you using a JOIN?

    SELECT * FROM buyerssellers,people WHERE filenumber=? AND buyersellers.personid = people.personid

    lets your database do most of the work (which is almost always a Good Thing.) Also by using placeholders (the '?' in the query), you no longer have to do a seperate prepare for every iteration. This could save you a lot of time.

      Mainly I wasn't using a JOIN because I haven't done much database work, and am not familiar with all that can be done with SQL calls. After breaking down your statement I can see where it would be very efficient, unfortunately in my case the second table may not have any data when I make the calls, and I still need to return the info from the first table regardless. It looks to me like this statement will return "all or nothing." Is this true?

      ryddler
        ryddler, you'll just have to ponder SQL a little bit harder. Because, you can make outer joins as well. Generally:
        SELECT name, 'In table2!' FROM table1 WHERE ID IN (SELECT ID FROM table2) JOIN SELECT name, 'Not in table2!' FROM table1 WHERE NOT ID IN (SELECT ID FROM table2);
        Well, this isn't beautiful SQL either, as you write two subselects, but it gets the job done. Whatever the columns and order in which you want it be returned, it really can be done in SQL. Look here for a tutorial.

        Jeroen
        "We are not alone"(FZ)

Re: Dereferencing nested references
by dkubb (Deacon) on Jan 16, 2001 at 09:52 UTC
    Here's one possible solution:
    my $statement = q{ SELECT * FROM BuyersSellers WHERE FileNumber = ? }; #force selectall_arrayref to return an arrayref of hashrefs my %attr = ( dbi_fetchall_arrayref_attr => {}, ); my $BuyersSellers = $dbh->selectall_arrayref($statement, \%attr, $file +number);

    The above code replaces the first prepare, execute, fetchrow_hashref block above. It does the same thing, but utilizes DBI's utility method selectall_arrayref.

    Placeholders are used, which is what the question marks in the SQL query are. They allow you to substitute values into the SQL query on the fly. DBI even takes care of calling DBI->quote against each value before substituion. That means you don't have to worry about quoting values in the SQL, DBI will figure it out for you. Besides, the SQL code ends up prettier with placeholders than without =). There is an in-depth placeholder explanation here

    $statement = q{ SELECT * FROM People WHERE PersonID = ? }; my $sth = $dbh->prepare($statement); foreach my $buysell (@$BuyersSellers) { $sth->execute($buysell->{PersonID}); my $row = $sth->fetchrow_hashref('NAME_lc'); $buysell->{PersonID} = { %$row }; }

    Notice that I prepared the statment handle before the loop? Then inside the loop I call execute over and over, getting the matching People record, and assigning it to the $buysell->{PersonID}. Since the $sth handle was prepared using placeholders, the actual value to match PersonID doesn't need to be defined until $sth->execute, where we pass it in at run-time.

    With some databases that support placeholders natively, like Oracle, the speed increases can be significant. Worst case you'll get no speed up, but just be left with a tighter loop and cleaner code.

    foreach my $buysell (@$BuyersSellers) { next unless $buysell->{BuyerORSeller} eq 'BUYER'; print qq{<tr class="$class"><td>$buysell->{PersonID}->{LastName}</td +></tr>}; }

    Then finally, looping through the $BuyerSellers arrayref and printing out the results

      I was able to use part of your code, as well as some of the above info I learned to get back the data I need. My thanks to all of you.

      Now this bit below that you posted above is bothering me because I haven't been able to find any references to this attribute you set. Where did you find out how to do this litte miracle?

      #force selectall_arrayref to return an arrayref of hashrefs my %attr = ( dbi_fetchall_arrayref_attr => {}, );


      ryddler
        You really just need to look at the source for DBI, version 1.14 (I think that's when this feature was added). It's not documented, as the Todo points out:
        document dbi_fetchall_arrayref_attr attr of selectall_arrayref().
        Anyway, the way this works is that this attribute controls the behavior of selectall_arrayref. If you don't set it, it defaults to an arrayref, which tells the method to fetch the rows as array refs and return an arrayref of those arrayrefs.

        If, however, you set it to a hash ref, that tells selectall_arrayref to fetch the rows as hash refs, and return an array ref of hash refs. Make sense?