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

First I just wanted to say that i'm new to perl and i'm sorry if this is an easy question. I havent had luck reading on or playing with it so I thought I should ask.

I'm creating a CGI that will connect to a MySQL database to read various things. It would make the code cleaner and easier (I thought) if I fetched a hash instead of an array (like usual).

while($info = $sth->fetchrow_hashref) { @case = keys %{info}; print @case; }
^^ Doesnt print out anything. I've tried many variations of this and the most I can seem to get is a printing of hex numbers that repersent where the hash is in memory I think (whats the correct term for that?).
while ( my $row = $sth->fetchrow_hashref ) { print "New:\n"; foreach my $field ( keys %{ $row } ) { print "$field: $row->{ $field }\n"; }}
^^ Is a snippet of code I found that prints out the results of the SQL query. I thought I understood what this code was doing but since I've had no luck playing with my own I'm guessing I dont understand it like I thought.

What I'm trying to end up having happen is my CGI script will have popup menus. I was wanting those popup menu's populated with entries from the SQL query. To make this easier I thought I would fetch a hash, dump all of the values from the hash into an array and use that array to in the popup menu.

Any advice or information anyone can spare would be wonderful.

Replies are listed 'Best First'.
Re: DBI Fetchrow_hasref issue
by jweed (Chaplain) on Feb 21, 2004 at 17:20 UTC
    You're very close. To make your first example work, you need to just add a dollar sign in front of info when you dereference:
    while($info = $sth->fetchrow_hashref) { @case = keys %{ $info }; print @case; }
    Once you do this, you'll see that your second example is basically the same as the first, except it iterates thought the hash instead of dumping the keys all at once.

    HTH.



    Code is (almost) always untested.
    http://www.justicepoetic.net/
Re: DBI Fetchrow_hasref issue
by jonadab (Parson) on Feb 21, 2004 at 17:24 UTC

    Your DBI stuff is fine. Your problem is your reference syntax. Try this:

    while($info = $sth->fetchrow_hashref) { for (keys %$info) { print "$_ => $$info{$_}\n"; } }

    You can also use %{$info} instead of %$info, but IMO that syntax is noisier and usually unnecessary. (Sometimes you have to use it, for example when you are interpolating and the variable is directly followed by word characters that would otherwise be taken for part of the variable name.)

    The syntax you use, %{info}, is exactly the same as saying %info -- that is to say, it doesn't dereference $info, but refers to a completely separate variable instead, a hash variable called %info. You want either %{$info} or %$info.

    You may also occasionally see an arrow syntax, like $info->{key} = $value, but for now you don't need to worry about that. Just know that when you do see it, it's a dereferencing syntax.

    The reason for the %{info} syntax is so that 'info' can be replaced with an arbitrary expression. For example, under no strict refs you could say %{"in" . "fo"} and it would still refer to %{info}. It does not however refer to the same thing as %{$info}.


    ;$;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$;[-1]->();print
Re: DBI Fetchrow_hasref issue
by tilly (Archbishop) on Feb 21, 2004 at 17:35 UTC
    Let me add two things to what everyone else has said.

    The first is that References quick reference may help clear up some confusion about how to use references. The second is that using strict.pm would have caught the fact that you were using them incorrectly and have assisted in debugging.

Re: DBI Fetchrow_hasref issue
by jarich (Curate) on Feb 21, 2004 at 18:21 UTC
    It would make the code cleaner and easier (I thought) if I fetched a hash instead of an array (like usual).

    SNIP

    What I'm trying to end up having happen is my CGI script will have popup menus. I was wanting those popup menu's populated with entries from the SQL query. To make this easier I thought I would fetch a hash, dump all of the values from the hash into an array and use that array to in the popup menu.

    fetchrow_hashref is best when you're selecting a number of things out from the database and you don't want to have to care what order you're doing that in. For example:

    select name, address, phonehome, phonework from addressbook;
    would give us a reference to a hash with the keys "name", "address", "phonehome", "phonework" and their values would be the values from the db. Note that this hash would only contain the values for ONE entry. The next time we called fetchrow_hashref we'd get the values for the next entry. And so on.

    Considering that you've said that you just want to dump all the values from the hash into an array I get the distinct impression that you're SQL is more like:

    select name from addressbook;
    If this is the case then your code isn't going to do what you want it to anyway. $info is going to be set to something like:
    $info = { name => "fred", };
    and you're going to print out "name". Even if you try to capture all of the values from this hash you're only going to get "fred" this time through the loop and "julie" next time through the loop...

    You can achieve what you want with the following:

    my @case; while( my $info = $sth->fetchrow_hashref) { push @case, $info->{name}; } print "@case\n"; # prints built up @case.
    However that's not really the best way to be doing this.

    I think what you're looking for is selectcol_arrayref. This selects all the (appropriate values) from a single column and returns an array ref to them. So this:

    my $names = $dbh->selectcol_arrayref( "select name from addressbook where name like ?", undef, $name ) or die "select failed. " . $dbh->errstr; my @case = @$names; # If you'd rather deal with an array print "@case\n"; # prints out ALL of the names selected.
    gives you all the values in the "name" column of the database which matched your where clause. You can then use $names (or @case in this instance) to do whatever you need.

    I hope this helps

    jarich

Re: DBI Fetchrow_hasref issue
by zentara (Cardinal) on Feb 21, 2004 at 17:22 UTC
    Check out The Fine Art of Database Programming

    It answers alot of common errors.

    Also search perlmonks for DBI and "placeholders".

    I don't see any prepare statements to go along with your $sth lines. Like:

    $sth = $dbh->prepare("SELECT * FROM table" );

    I'm not really a human, but I play one on earth. flash japh
Re: DBI fetchrow_hashref issue
by cchampion (Curate) on Feb 21, 2004 at 18:40 UTC

    Aside from the specific problem in your code, what you want to achieve an also be done with:

    my @case = @{$sth->{NAME}}; while(my $info = $sth->fetchrow_hashref) { for (@case) { print "$_ => $info->{$_}\n"; } }

    I recommend reading DBI Recipes, which deals with several issues related to fetching hashrefs from a database.

    BTW, the probable reason for your mistake is that you are not using strict.

Re: DBI fetchrow_hashref issue
by jeffa (Bishop) on Feb 22, 2004 at 05:23 UTC

    "What I'm trying to end up having happen is my CGI script will have popup menus. I was wanting those popup menu's populated with entries from the SQL query. To make this easier I thought I would fetch a hash, dump all of the values from the hash into an array and use that array to in the popup menu."

    This, of course, all depends upon what your database table and query results look like. I will assume the following table:

    states
    +--------+------------------+----+
    | Field  | Type             | PK |
    +--------+------------------+----+
    | id     | int(10) unsigned |  y |
    | name   | varchar(64)      |    |
    | abbrev | char(2)          |    |
    +--------+------------------+----+
    
    And with that, let me introduce HTML::Template and selectall_arrayref with the optional "slice" twist:
    use DBI; use HTML::Template; use CGI qw(:standard); my $dbh = DBI->connect( qw(DBI:vendor:database:host user pass), {RaiseError=>1}, ); my $states = $dbh->selectall_arrayref(' select id,name from state order by name ',{Slice => {}}); my $tmpl = HTML::Template->new(filehandle => \*DATA); $tmpl->param(states => $states); print header,$tmpl->output; __DATA__ <form> <select name="states"> <tmpl_loop states> <option value="<tmpl_var id>"><tmpl_var name></option> </tmpl_loop> </select> <p><input type="submit" /></p> </form>
    But ... we lose stickiness, that is, when you submit the form, the item we selected will no longer be selected. Add this line after the database query (the assignment to $states):
    $_->{id} == param('state') and $_->{sel} = 1 for @$states;
    and add a new <tmpl_if> to the option element in the template:
    <option value="<tmpl_var id>" <tmpl_if sel>selected="1"</tmpl_if>> <tmpl_var name> </option>
    And you can handle single item selects, but multiple items requires more thought. Sometimes it easier instead to let CGI.pm create the HTML for you:
    ... beginning same as before ... my $states = $dbh->selectall_arrayref( ... ); my $tmpl = HTML::Template->new(filehandle => \*DATA); $tmpl->param(states => scrolling_list( -name => 'states', -values => [ map $_->{id}, @$states ], -size => 12, -multiple => 'why not', -labels => { map {$_->{id} => $_->{name}} @$states }, ), ); print header,$tmpl->output; __DATA__ <form> <tmpl_var states> <p><input type="submit" /></p> </form>
    Hope this helps,

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)