I am needing a gentle introduction to accessing a perl array from areference

  • Thread starter Larry W. Virden
  • Start date
L

Larry W. Virden

I have some legacy code, the author of whom is no longer available for
consultation.

Here is a piece of the code in question:

:
up here, the DBI connection to the database, the select statement,
etc. occurs
:

# put results of query into single array and return
while (@next_row= $query->fetchrow_array()){

push(@array_result,@next_row);
}

$query->finish;
$dbconn->disconnect();
return \@array_result;

I am a perl novice. I am trying to figure out a readable approach to
dealing with the value that is returned.

One piece of code that I've bubble-gummed and bailing wired together
basically does this:

$super_list = thefunction($argument);
my $tot = scalar(@$super_list);

if ( $tot_supers >= 1 ) {
for (my $staff=0; $staff < $tot_supers; $staff++ ) {
if ( defined( $super_list->[$staff]{'User_Name'} ) ) {
$super_login = $super_list->[$staff]
{'User_Name'};
chomp($super_login);
print $super_login . "\n" ;
} else {
print "Unable to identify user $staff \n";
}
}
}

The main concern I have here is that if I want to write a "print all
columns of data" type routine, I would have to hand code a series of
if (defined...) type statements, differing depending on what table is
being dumped.

Is there another approach that would allow me to dynamically generate
the columns from the hash and then loop through them?

I apologize from the vague description - I'm still working on
understanding the data structure , etc.
 
J

Jim Gibson

Larry W. Virden said:
I have some legacy code, the author of whom is no longer available for
consultation.

Here is a piece of the code in question:

:
up here, the DBI connection to the database, the select statement,
etc. occurs
:

# put results of query into single array and return
while (@next_row= $query->fetchrow_array()){

push(@array_result,@next_row);
}

Are you sure this is your code? The above statements will have the
unfortunate effect of concatenating all of the rows returned into a
single array. Perhaps this is what you actually have:
while ( my @next_row= $query->fetchrow_array()){

push(@array_result,\@next_row);
}

Notice the difference. This loop will produce an array of references to
arrays, one for each row. This is called an "array-of-arrays".
$query->finish;
$dbconn->disconnect();
return \@array_result;

I am a perl novice. I am trying to figure out a readable approach to
dealing with the value that is returned.

One piece of code that I've bubble-gummed and bailing wired together
basically does this:

$super_list = thefunction($argument);

What is the relationship between thefuncion() and the code you have
shown above?
my $tot = scalar(@$super_list);

if ( $tot_supers >= 1 ) {

What is the value of $tot_supers? Do you mean $tot?
for (my $staff=0; $staff < $tot_supers; $staff++ ) {
if ( defined( $super_list->[$staff]{'User_Name'} ) ) {

Where did the hash %{$super_list->[$staff]} come from?
$super_login = $super_list->[$staff]
{'User_Name'};
chomp($super_login);
print $super_login . "\n" ;
} else {
print "Unable to identify user $staff \n";
}
}
}

The main concern I have here is that if I want to write a "print all
columns of data" type routine, I would have to hand code a series of
if (defined...) type statements, differing depending on what table is
being dumped.

Why? Why not use the elements that went into the select statement that
generated the returned data? You should know what you have asked for.
Is there another approach that would allow me to dynamically generate
the columns from the hash and then loop through them?

I apologize from the vague description - I'm still working on
understanding the data structure , etc.

Please explain in more detail your data structures so that we may
understand them. Your descriptions are incomplete.
 
T

Tad J McClellan

Larry W. Virden said:
I have some legacy code, the author of whom is no longer available for
consultation.

Here is a piece of the code in question:


Is the code copy/pasted, or re-typed?

up here, the DBI connection to the database, the select statement,
etc. occurs
:

# put results of query into single array and return
while (@next_row= $query->fetchrow_array()){

push(@array_result,@next_row);
}

$query->finish;
$dbconn->disconnect();
return \@array_result;


That is an awfully strange way to do it, unless your query is selecting
only a single column.

Is this your real code?

The data structure here is nothing more than (a reference to) a flat array.

I am trying to figure out a readable approach to
dealing with the value that is returned.


To help you with that, we will need rather precise information on what
that structure is...

One piece of code that I've bubble-gummed and bailing wired together
basically does this:

$super_list = thefunction($argument);
my $tot = scalar(@$super_list);
if ( $tot_supers >= 1 ) {
for (my $staff=0; $staff < $tot_supers; $staff++ ) {


The Perlish way would be to replace the 3 lines above with:

if ( @$super_list ) {
foreach my $staff ( 0 .. $#$super_list ) {

I'm guessing you would benefit by spending some quality time with:

perldoc perlreftut

and

perldoc Data::Dumper

if ( defined( $super_list->[$staff]{'User_Name'} ) ) {
^ ^
^ ^
Eh? Where did the hash come from?

There is no hash anywhere in your code up at the top...

$super_login = $super_list->[$staff]
{'User_Name'};
chomp($super_login);
print $super_login . "\n" ;
} else {
print "Unable to identify user $staff \n";
}
}
}

The main concern I have here is that if I want to write a "print all
columns of data" type routine, I would have to hand code a series of
if (defined...) type statements, differing depending on what table is
being dumped.

Is there another approach that would allow me to dynamically generate
the columns from the hash and then loop through them?


perldoc -f keys

Returns a list consisting of all the keys of the named hash.

I apologize from the vague description - I'm still working on
understanding the data structure , etc.


We need an understanding of the (actual) data structure if we are
to help you suss out how to access it.

Try posting a bit of output from:

use Data::Dumper;
print Dumper $super_list;
 
T

Tim Greer

Larry said:
I have some legacy code, the author of whom is no longer available for
consultation.

Here is a piece of the code in question:

:
up here, the DBI connection to the database, the select statement,
etc. occurs

Can you either post the fully relevant portions of your code, or
elaborate on some aspects? Some parts of the code you've posted don't
seem to relate to the other, so either relevant code is missing, or...?
 
L

Larry W. Virden

Are you sure this is your code? The above statements will have the
unfortunate effect of concatenating all of the rows returned into a
single array. Perhaps this is what you actually have:
   while ( my @next_row= $query->fetchrow_array()){

     push(@array_result,\@next_row);
   }

That was a literal copy and paste from the code. The only thing I can
figure, looking at the code, is that in the most cases, I think there
is only one record satisfying the select, so there's perhaps never
been a time when more than one item was pushed. I just inherited this
system, so I'm still learning about it.
Notice the difference. This loop will produce an array of references to
arrays, one for each row. This is called an "array-of-arrays".







What is the relationship between thefuncion() and the code you have
shown above?

The initial code I showed is a small part of a web service "method".
the $super_list line is the call to the web service.
What is the value of $tot_supers? Do you mean $tot?

yes, that was a mistake in my attempt to translate things into a
simpler form for the posting. I apologize.

        for (my $staff=0; $staff < $tot_supers; $staff++ ) {
                if ( defined( $super_list->[$staff]{'User_Name'} ) ) {

Where did the hash %{$super_list->[$staff]} come from?

The web service returns an array reference, assigned to $super_list.
$staff is the attempt to increment through the rows of the array.
                       $super_login = $super_list->[$staff]
{'User_Name'};
                       chomp($super_login);
                       print $super_login . "\n" ;
                } else {
                        print "Unable to identify user $staff \n";
                }
        }
    }
The main concern I have here is that if I want to write a "print all
columns of data" type routine, I would have to hand code a series of
if (defined...) type statements, differing depending on what table is
being dumped.

Why? Why not use the elements that went into the select statement that
generated the returned data? You should know what you have asked for.

I was wanting to write some code that would , upon receiving one of
these references to an array, just loop through the array and print
out the column names and values. The web service module that I'm
interacting with has a dozen or more methods. Rather than trying to
figure out what columns are returned by each method, I was hoping a
more generic print function could be written that would just iterate
through the array.
Please explain in more detail your data structures so that we may
understand them. Your descriptions are incomplete.

The reference to the array is supposed to be results from select
statements, some of which are "select * from ...." . Certainly I can
do a describe of each table, and then hard code a series of print
statements. I was just hoping there was a streamlined best practice
for that sort of access that would generically apply.

Thank you so much for your time.
 
L

Larry W. Virden

Can you either post the fully relevant portions of your code, or
elaborate on some aspects?  Some parts of the code you've posted don't
seem to relate to the other, so either relevant code is missing, or...?

Thank you for your question. The bit of code was from 1600+ lines of
code in a web services program, as well as parts from a 50+ line
program which invokes one method from the web service, interacts with
other programs, to generate some internal information. I don't have
rights to distribute the whole program, so I was trying to (and
obviously failed at) picking just a small portion to get at a better
understanding at how one would generically access the elements of a
hash given a reference. I need to do more reading, and have some
references elsewhere in this thread I will use to try and learn more.

Thank you all so much for your help.
 
L

Larry W. Virden

Is the code copy/pasted, or re-typed?

The code dealing with the fetchrow was copy/pasted. The remaining code
was typed with me trying to include just relevant details (without all
the debugging statements, etc.). I failed at some of the retyping. I
apologize.
That is an awfully strange way to do it, unless your query is selecting
only a single column.

I didn't write the code, and haven't used arrays, references, and DBI
enough to know about it. In the particularly select I was looking at,
I think the select might very well be one that only returns a single
row. I'm still trying to learn about the database and its
characteristics.
Is this your real code?

The data structure here is nothing more than (a reference to) a flat array.

That's the real code from the program I'm trying to understand. So,
when the fetchrow_array returns @next_row, this is a "flat array".
It's still a hash, right? With the hash entries (sorry I don't know
the technical term - the values by which one accesses information in
the hash) being the names of the columns from the select and the
information stored in the hash being the values from the columns. And
fetchrow_array returns ONE row worth of data.

That's what I understood.

One point that confused me though was the push(@array_result,
@next_row); statement. From other statements mentioned here, it sounds
to me as if, in cases of multiple rows returned, @array_result becomes
a "mish mash" of information, overwriting previously existing
values.

Am I understanding that correctly?
To help you with that, we will need rather precise information on what
that structure is...

I'm trying to understand the code to the point that I can figure the
structure out. I apologize for my limited vocabulary and
understanding.
The Perlish way would be to replace the 3 lines above with:

   if ( @$super_list ) {
       foreach my $staff ( 0 .. $#$super_list ) {

I'm guessing you would benefit by spending some quality time with:

   perldoc perlreftut

and

   perldoc Data::Dumper

Thank you so much for the suggestion. I'll read those and hopefully
have a better comprehension of what is going on.
                if ( defined( $super_list->[$staff]{'User_Name'} ) ) {

                                                     ^           ^
                                                     ^           ^
Eh? Where did the hash come from?

The original documentation for this web services program was spotty. I
took what was provided, tried to get something to work, talked to some
people who knew more perl than I did, and that's where I ended up
getting data back that was expected. The data that was returned to
$super_list was an array generated by the push
(@array_result,@next_row) statement after a DBI fetchrow_array. The
hash index is one of the column names of data returned by the fetch
from the select handle.
There is no hash anywhere in your code up at the top...

The fetchrow_array was from DBI, returning sql rows from an oracle
database.
                       $super_login = $super_list->[$staff]
{'User_Name'};
                       chomp($super_login);
                       print $super_login . "\n" ;
                } else {
                        print "Unable to identify user $staff \n";
                }
        }
    }
The main concern I have here is that if I want to write a "print all
columns of data" type routine, I would have to hand code a series of
if (defined...) type statements, differing depending on what table is
being dumped.
Is there another approach that would allow me to dynamically generate
the columns from the hash and then loop through them?

    perldoc -f keys

        Returns a list consisting of all the keys of the named hash.

Ah! That's the concept I was seeking.
We need an understanding of the (actual) data structure if we are
to help you suss out how to access it.

Try posting a bit of output from:

   use Data::Dumper;
   print Dumper $super_list;

Thanks - the code diving I am doing is to develop a better
understanding of what this web service is doing, so that I can figure
out how things could be done better. This is, alas, not one of the
primary tasks I have, so I have to eek out time every day or so to dig
in. The feedback here has been quite valuable, and I believe I have
several valuable avenues for learning.

I appreciate everyone's time and patience.
 
T

Tad J McClellan

I think the select might very well be one that only returns a single
row.


Oh. That makes it less strange.

But coding up a loop where you depend on always having a single
interation is kind of silly. It is a non-looping loop!

That's the real code from the program I'm trying to understand. So,
when the fetchrow_array returns @next_row, this is a "flat array".
It's still a hash, right?


No. If the array contained references to hashes (which it does not)
then it would not be "flat", it would be "hierarchical".

So, we still do not know where the hashes are coming from...

.... they are certainly not coming from any of the code we've seen thus far.

With the hash entries (sorry I don't know
the technical term - the values by which one accesses information in
the hash)

keys (indexes into a hash)

values (the values associated with particular keys)

being the names of the columns from the select and the
information stored in the hash being the values from the columns. And
fetchrow_array returns ONE row worth of data.

That's what I understood.


That part is mostly correct, though there is nothing creating any
hashes that we can see...

One point that confused me though was the push(@array_result,
@next_row); statement. From other statements mentioned here, it sounds
to me as if, in cases of multiple rows returned, @array_result becomes
a "mish mash" of information, overwriting previously existing
values.

Am I understanding that correctly?


mish mash, yes.

overwriting, no.

push() adds to what is already there.

If you had "select name, address from...", then all of the even indexes
in @array_result would be names and all of the odd indexes would be addresses.

That is, alternating names and addresses.

I'm trying to understand the code to the point that I can figure the
structure out.


Just ask Data::Dumper to show you the data structure, then figure it
out from the output of Data::Dumper.

                if ( defined( $super_list->[$staff]{'User_Name'} ) ) {

                                                     ^           ^
                                                     ^           ^
Eh? Where did the hash come from?

The original documentation for this web services program was spotty. I
took what was provided, tried to get something to work, talked to some
people who knew more perl than I did, and that's where I ended up
getting data back that was expected. The data that was returned to
$super_list was an array generated by the push
(@array_result,@next_row) statement after a DBI fetchrow_array. The
hash index is one of the column names of data returned by the fetch
from the select handle.


fetchrow_array does not do hashes or include column names, so its
return value must be getting modified somewhere else in the code...

However, you/we probably don't need to find it if only we knew for
certain what data structure you are ending up with.

                       $super_login = $super_list->[$staff]
{'User_Name'};
The main concern I have here is that if I want to write a "print all
columns of data" type routine, I would have to hand code a series of
if (defined...) type statements, differing depending on what table is
being dumped.
Is there another approach that would allow me to dynamically generate
the columns from the hash and then loop through them?

    perldoc -f keys

        Returns a list consisting of all the keys of the named hash.

Ah! That's the concept I was seeking.


Then you can apply "Use Rule 1" from perlreftut.pod to get a list
of keys. I like to apply it in 3 steps:

my @keys = keys %hash; # pretend it is an ordinary hash

my @keys = keys %{ }; # replace the _name_ with a block

# fillin the block with something that returns the right kind of reference
my @keys = keys %{ $super_list->[$staff] };
 
R

RedGrittyBrick

Larry said:
Thank you for your question. The bit of code was from 1600+ lines of
code in a web services program, as well as parts from a 50+ line
program which invokes one method from the web service, interacts with
other programs, to generate some internal information. I don't have
rights to distribute the whole program, so I was trying to (and
obviously failed at) picking just a small portion to get at a better
understanding at how one would generically access the elements of a
hash given a reference. I need to do more reading, and have some
references elsewhere in this thread I will use to try and learn more.

Thank you all so much for your help.

Just a suggestion:

Use Data::Dumper to find out what you have in various variables in the
actual program being worked on.

The output of Data::Dumper can be used to initialise a variable. You can
therefore include that output as code in new tiny throwaway Perl
programs to test out various ways to further manipulate or process those
data structures.

I find debugging tiny programs is easier than debugging big/complex
poorly understood ones. You also end up with small programs you can post
here for further help.
 
P

Peter J. Holzer

I didn't write the code, and haven't used arrays, references, and DBI
enough to know about it. In the particularly select I was looking at,
I think the select might very well be one that only returns a single
row. I'm still trying to learn about the database and its
characteristics.


That's the real code from the program I'm trying to understand. So,
when the fetchrow_array returns @next_row, this is a "flat array".
It's still a hash, right?

No, fetchrow_array returns an array, not a hash. If you want a hash(ref),
use fetchrow_hashref. Since the rest of the code looks like it expects
an array of hashrefs I have to ask whether you are sure that this is
really the code which is called. Or is there a very similar method which
differs only in calling fetchrow_hashref instead of fetchrow_array, and
you are calling that instead?

hp
 
L

Larry W. Virden

Since the rest of the code looks like it expects
an array of hashrefs I have to ask whether you are sure that this is
really the code which is called. Or is there a very similar method which
differs only in calling fetchrow_hashref instead of fetchrow_array, and
you are calling that instead?

Well

$ grep fetchrow_hash user_info.pl
$ getmanlist.pl
 
L

Larry W. Virden

Well

$ grep fetchrow_hash user_info.pl
$ getmanlist.pl

whoops - that Enter key just sent the posting, instead of moving to
the next line. Sigh.
anyways, when I run getmanlist.pl (the program that treats the value
returned as a hash) I get values back from the line that uses the
hash. Ah - I see the reason for the descrepancy. As I mentioned, the
web service has a lot of methods, and the one I was thinking was the
one being called was not.
I have problems with focusing with my trifocals. Here's the code the
function being called is using:

sub GetManagerList{

my (@next_row);
my @retval; #this will be array of hashes


#query db to find information
my ($dbconn) = DBI->connect("DBI:Oracle:$DBHOST",$DBUSER,$DBPASS) or
return \@retval;
my ($query) = $dbconn->prepare(
"SELECT h.*, c.UNIX_UID from CSI_HR
h, CSI_CORE c WHERE h.ALT_EMP_NO = c.HR_ALT_EMP_NO and EXISTS
(Select * FROM CSI_HR h2 where
h.Alt_Emp_No = h2.Supervisor_Emp_No) order by Last_Name");

$query->execute() or return \@retval; #problem return empty array


@next_row= $query->fetchrow_array();
while (@next_row) #get rows and add to return array of hashes
{

#get UID and Name

push(@retval,{Alt_Emp_No => $next_row[0], Job_Class => $next_row
[1], Title => $next_row[2],Dept_Code => $next_row[3],
Dept_No => $next_row[4],Last_Name => $next_row
[5],First_Name => $next_row[6],Middle_Name => $next_row[7],
Supervisor_Emp_No => $next_row[8], HR_Status =>
$next_row[9],
HR_Create => $next_row[10],
HR_Mod => $next_row[11], User_Name => scalar getpwuid
($next_row[12])});

@next_row= $query->fetchrow_array();
}

$query->finish;
$dbconn->disconnect();
return \@retval;
}

Anow now that I found the right function, I see the code manufactures
its own hash in the return value from the flat arrays. That makes so
much more sense. I kept trying to figure out how the other code was
doing what it was doing, and it was causing me to misunderstand how
perl in general worked. Now things are much more clear.

Thank you all for your help!
 
T

Tad J McClellan

Ah - I see the reason for the descrepancy. As I mentioned, the
web service has a lot of methods, and the one I was thinking was the
one being called was not.

Here's the code the
function being called is using:


As you may have already deduced, this is poor quality code.

I give it a C+.

sub GetManagerList{

my (@next_row);
my @retval; #this will be array of hashes


#query db to find information
my ($dbconn) = DBI->connect("DBI:Oracle:$DBHOST",$DBUSER,$DBPASS) or
return \@retval;


Programs should not fail silently. They should fail noisily.

When the user gets no results, they cannot know if that is because
there were actually no results or if it was because a connection
to the database could not be established!

my ($query) = $dbconn->prepare(
"SELECT h.*, c.UNIX_UID from CSI_HR
h, CSI_CORE c WHERE h.ALT_EMP_NO = c.HR_ALT_EMP_NO and EXISTS
(Select * FROM CSI_HR h2 where
h.Alt_Emp_No = h2.Supervisor_Emp_No) order by Last_Name");


Check the return value for the statement before this one.

Check the return value for the statement after this one.

Do not check the return value for this statement?


Using "select *" is a very bad practice.

If the table was defined like this:

create table test (
address text,
name text
);

then you get address followed by name from "select *".

If the table was defined like this:

create table test (
name text,
address text
);

then you get name followed by address from "select *".

$query->execute() or return \@retval; #problem return empty array


@next_row= $query->fetchrow_array();
while (@next_row) #get rows and add to return array of hashes
{

#get UID and Name

push(@retval,{Alt_Emp_No => $next_row[0], Job_Class => $next_row
[1], Title => $next_row[2],Dept_Code => $next_row[3],
Dept_No => $next_row[4],Last_Name => $next_row
[5],First_Name => $next_row[6],Middle_Name => $next_row[7],
Supervisor_Emp_No => $next_row[8], HR_Status =>
$next_row[9],
HR_Create => $next_row[10],
HR_Mod => $next_row[11], User_Name => scalar getpwuid
($next_row[12])});

@next_row= $query->fetchrow_array();
}


while ( my $href = fetchrow_hashref ) {
push @retval, $href;
}

Does (probably) the same thing in 2 lines instead of in 7 lines...

Anow now that I found the right function, I see the code manufactures
its own hash in the return value from the flat arrays. That makes so
much more sense.


That makes more sense with regard to how you're seeing what you're seeing.

It makes a great deal less sense with regard to how the hash is getting built.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top