Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Preserving hash structure after subroutine call

by Anonymous Monk
on May 13, 2003 at 20:58 UTC ( #257897=perlquestion: print w/ replies, xml ) Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have a hash structure that I'd like to keep intact after a subroutine call. I've tried several things, but nothing works. There's got to be something simple I've overlooked:
my %myhash; build_hash(\%myhash); print Dumper(\%myhash); ## shows %myhash to be empty sub build_hash { my $href = shift; my %results; my $sth = $dbh->prepare( $query ); $sth->execute(); my $ct = 0; while (my $tmp_href = $sth->fetchrow_hashref) { $ct++; $results{$ct} = $tmp_href; } %{ $href } = %results; ## also tried Storable's dclone(), but same results ## $href = dclone(\%results); print Dumper($href); ## prints desired results }
The resulting structure looks like this (it's exactly the way I want it): <code> $VAR1 = { '1' => { 'f1' => undef, 'f2' => 'bleep@hotmail.com', 'f3' => 'OTH', 'f4' => 'N',

Comment on Preserving hash structure after subroutine call
Download Code
Re: Preserving hash structure after subroutine call
by suaveant (Parson) on May 13, 2003 at 21:08 UTC
    It may be that the reference to the fetchrow_hashref is somehow losing scope outside the subroutine. I tried by faking your data in and it worked fine.

    as for sugestions, I would just do

    $href->{$ct} = $tmp_href; #rather than $results{$ct} = $tmp_href;
    that way you don't need to waste time and memory on a copy of the hash

                    - Ant
                    - Some of my best work - (1 2 3)

      Yes. I had used that variant earlier without the intermediate hash but with the same results. I used the intermediate hoping that the references would resolve to actual data during the assignment:
      %{ $href } = %results;

      In my example I tried using Storable's dclone() which I thought replace the references with the real data, but apparently not. I agree that it has something to do with scoping, I'm just not sure how to solve it.

        well... try
        while(my $row = $sth->fetchrow_hashref() && my %row = %$row)
        or something similar. Copy the row hash.

                        - Ant
                        - Some of my best work - (1 2 3)

Re: Preserving hash structure after subroutine call
by artist (Parson) on May 13, 2003 at 21:09 UTC
    May be you can follow this simple example and try modifying to see where you are getting the problem:
    use Data::Dumper; my %myhash = {}; build_hash(\%myhash); print Dumper(\%myhash); sub build_hash { my $href = shift; my %results = (1,2,3,4);; %{$href} = %results; # print Dumper($href); }

    artist

      I've used the method in your example extensively to initialise hashes and never had any problem with it. For some reason (scope?) it just doesn't seem to work when the fetchrow_hashref method is involved.
Re: Preserving hash structure after subroutine call
by Limbic~Region (Chancellor) on May 13, 2003 at 21:13 UTC
    Anonymous Monk,
    There are at least 3 easy ways to do this:

  • Assign the hash = to the return value of the sub: %hash = build_hash(sub {return %new_hash});
  • Pass a hash reference to the sub and return a reference $hashref = build_hash(sub {return \%new_hash});
  • Pass a hash reference to the sub and modify the original - see below

    The first is usually considered bad form as there is too much copying going on and it will not allow you to pass more than one hash at a time. The second is my preferred method, but it looks like you were going for number 3.

    #!/usr/bin/perl -w use strict; my %hash; build_hash(\%hash); print "$_ : $hash{$_}\n" foreach(keys %hash); sub build_hash { my $hash_ref = shift; $hash_ref->{'key1'} = "foo"; $hash_ref->{'key2'} = "bar"; }
    By the way, if you are going to loop over the hash to get both the keys and values, each is typically considered the way to go.

    Cheers - L~R

Re: Preserving hash structure after subroutine call
by bart (Canon) on May 13, 2003 at 22:13 UTC
    It seems to me the only place that this can go wrong is in this section, copied from your code:
    while (my $tmp_href = $sth->fetchrow_hashref) { $ct++; $results{$ct} = $tmp_href; }
    You see: there's no garantee that DBI won't reuse the same hashref over and over again. That would mean that all, at least some, of the stored values in the final hash are the same reference. Quoting from `perldoc DBI` on fetchrow_hashref, $DBI::VERSION==1.15 (I haven't bothered to upgrade in a long time):
    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.

    Therefore, I propose to anonymise those records:

    while (my $tmp_href = $sth->fetchrow_hashref) { $ct++; $results{$ct} = { %$tmp_href }; }
    For the rest: doing %$href = %result; seems to work for me.
Re: Preserving hash structure after subroutine call
by nite_man (Deacon) on May 14, 2003 at 07:11 UTC
    I would like to suggest you this way:
    #my $myhash; #build_hash($myhash); my $myhash = build_hash(); print Dumper($myhash); ## shows %myhash to be empty sub build_hash { my $href_ref; #my %results; my $sth = $dbh->prepare( $query ); $sth->execute(); # Get all records as array reference of hash references. my $res = $sth->fetchall_arrayref({}); map { $hash->{$_+1} = $res->[$_] } (0 .. @$res-1); #my $ct = 0; #while (my $tmp_href = $sth->fetchrow_hashref) { # $ct++; # $results{$ct} = $tmp_href; #} #%{ $href } = %results; ## also tried Storable's dclone(), but same results ## $href = dclone(\%results); print Dumper($href); ## prints desired results return $href; }
    Hope I helped
          
    --------------------------------
    SV* sv_bless(SV* sv, HV* stash);
    
Re: Preserving hash structure after subroutine call
by dragonchild (Archbishop) on May 14, 2003 at 13:48 UTC
    I may be mistaken, but I believe that DBI will re-use the hashref it gives back through fetch(). I would suggest using bind_columns() (and prepare_cached(), but that's another story). I'd rewrite your thing as such:
    sub get_stuff_from_db { my ($query) = @_; my $sth = $dbh->prepare_cached( $query ) or die "Could not prepare '$query'\n"; $sth->execute() or die "Could not execute '$query'\n"; $sth->bind_columns( \(my ($x, $y, $z)) #Assumes three columns returned ); my %results; while ($sth->fetch) { @results{qw(X Y Z)) = ($x, $y, $z); } $sth->finish; return \%results; }
    A few notes:
    • Use strict! Do _NOT_ use globals. (Unless, of course, you know why you shouldn't. Then, go right ahead. For example, $dbh is usually an acceptable global, if not desirable.)
    • Pass your query in. This will allow you to reuse this function.
    • You'll notice I'm checking the return values of my calls to DBI. Good production code will always do this, and for more than just DBI.
    • I'm betting your query has variables in it. Use placeholders. That will allow for safer (and better) performance. (For example, do you know how to do all the quoting stuff? I don't, nor do I want to.)
    • Instead of passing in a hashref, return a hashref. The function shouldn't know anything about how you're going to use its data. It should just know how to make it. This way, you can use the data in more than one way, as you need to.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: Preserving hash structure after subroutine call
by Anonymous Monk on May 14, 2003 at 16:54 UTC
    It turned out that the problem was simple (stupid):
    my ($dbh, $field_str, $where_str, $href) = shift; ## instead of my ($dbh, $field_str, $where_str, $href) = @_;

    Apologies to all. Note that the mistake does not cause compile or runtime errors. The parameters $field_str and $where_str are optional. I should have included more complete code, as the problem would probably have been spotted right away. However, the feedback was of great value and some has found its way into the current version of the code. Thanks to all who replied.

    #initial stuff #... build_hash($dbh, '', '', $href); sub build_hash { my ($dbh, $field_str, $where_str, $href) = @_; my $table_str = 'table_name'; do { my @fields = qw( f1 f2 f3 f4 f4 f6 f7 f8 f9 ); $field_str = join(', ', @fields); } unless $field_str; my $query = qq( select $field_str from $table_str ); $query .= qq( where $where_str ) if $where_str; my $sth = $dbh->prepare($query) or die "Could not prepare '$query'\n"; $sth->execute() or die "Could not execute '$query'\n"; my $ct = 0; while (my $tmp_href = $sth->fetchrow_hashref) { $ct++; $$href{$ct} = $tmp_href; } $sth->finish or die "Could not finish '$query'\n"; }
    Any further comments, suggestions, etc. welcome.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://257897]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (9)
As of 2014-10-23 08:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (125 votes), past polls