Landon Statis
Landon Statis

Reputation: 839

Perl - Pushing Elements

I have this loop where I populate a record and attempt to push it to an array:

while (@data = $sth->fetchrow_array()) {
  $rec->{'article_id'}    = $data[0];
  $rec->{'category_id'}   = $data[1];
  $rec->{'category_name'} = $data[2];
  $rec->{'author_id'}     = $data[3];
  $rec->{'headline'}      = $data[4];
  $rec->{'teaser'}        = $data[5];
  $rec->{'author_name'}   = $data[7];

  push @article_data, $rec;
}

However, when this loop is done, @array_data only contains the last element which is in $rec. I have printed $rec each time through the loop, and it is populated properly, with different values each time. But, @array_data looks like this:

$VAR1 = {
          'author_name' => 'Equity Research',
          'author_id' => '631459560',
          'teaser' => 'Barnes' (B) first-quarter 2022 revenues increase 4% year over year.',
          'article_id' => '16608',
          'category_name' => 'Analyst Blog : Earnings Article',
          'category_id' => '298',
          'headline' => 'Barnes Group (B) Q1 Earnings & Revenues Surpass Estimates'
        };

$VAR2 = $VAR1;
$VAR3 = $VAR1;
$VAR4 = $VAR1;

This is the last element retrieved from the DB in the loop. Why is it not pushing each item into a different element in @article_data?

Upvotes: 1

Views: 486

Answers (3)

ikegami
ikegami

Reputation: 385506

You only have one hash, and $rec is a reference to that hash. Each pass of the loop, you add a copy of that reference to the array. So you end up with multiple reference to the one hash.

But you want multiple hashes. One for each row. You can use my %rec; or {} to create

while ( my @data = $sth->fetchrow_array() ) {
   my %rec = ( 
      article_id    => $data[ 0 ],
      category_id   => $data[ 1 ],
      category_name => $data[ 2 ],
      author_id     => $data[ 3 ],
      headline      => $data[ 4 ],
      teaser        => $data[ 5 ],
      author_name   => $data[ 7 ],
   );

   push @article_data, \%rec;
}

or

while ( my @data = $sth->fetchrow_array() ) {
   push @article_data, {
      article_id    => $data[ 0 ],
      category_id   => $data[ 1 ],
      category_name => $data[ 2 ],
      author_id     => $data[ 3 ],
      headline      => $data[ 4 ],
      teaser        => $data[ 5 ],
      author_name   => $data[ 7 ],
   };
}

Upvotes: 2

TLP
TLP

Reputation: 67900

You've already been given two working answers, I thought I would just elaborate around the various solutions.

while (@data = $sth->fetchrow_array()) {

What you really should be doing here is using a lexically scoped loop variable, e.g. my @data. It is a widely used Perl idiom that prevents many headaches.

It is in the next row where your error occurs that this idiom would have saved you:

  $rec->{'article_id'}    = $data[0];

Since you did not create a lexical variable, these hash keys just get overwritten every loop iteration, much the same as if you had used a regular scalar variable:

  $article_id = $data[0];

However, a simple my declaration would have saved you all this grief:

my $rec;    # create a fresh variable
$rec->{'article_id'}    = $data[0];

But there is more. You are using a reference to a hash as a vehicle to put data into an array, but we don't really need that vehicle. Consider this:

push @article_data, {           # this creates a new anonymous hash ref
    article_id  => $data[0],    # you can insert comments here (*)
    category_id => $data[1],
    .... };                     # end of hash ref

We can just create the temporary hash ref on the fly with { ... }. Everything inside those curly brackets will be considered hash key and value pairs.

This solution is appealing because it is verbose -- you can see everything very clearly, and you can even add comments (*) to the different values -- but sometimes you like it quick and neat. Especially if you have very large data structures. In which case you can use a hash slice. This will save you the effort of typing out the array indexes.

my @keys = qw(article_id category_id category_name author_id 
              headline teaser author_name);   # this can be place elsewhere in your code
my %rec;
@rec{ @keys } = @data;    # short and simple assignment
push @article_data, \%rec;

And if you absolutely prefer to use a hash ref, you can do

my $rec;
@{ $rec }{ @keys } = @data;
push @article_data, $rec;

Lastly, I feel sure that Perl DBI offers a feature where you can assign names to columns, or keep existing names as inherited from the database. For example fetchrow_hashref. The fetchall_arrayref will fetch all the rows, and might do all this work for you with one single function call. Look it up.

Upvotes: 2

collapsar
collapsar

Reputation: 17238

At the start of your loop, add $rec = {};.

Currently $rec remains the same entity throughout the loop, you only ever overwrite the values of a given set of keys in each iteration. Therefore you end up with an array of identical copies.

Upvotes: 1

Related Questions