0

This is a question about Perl data structures, but first a breif description of some details involved. I have a perl script that uses DBI and performs three queries against a mysql database ($q1 $q2 $q3). Each query returns 2 or 3 fields (which is subject to change depending upon requirements) and any number of rows. My final output needs to be a csv of all the fields and their values.

The following represents the fields and rows returned from the database.

if $q1 returns

field1   field2
id1      val_a
id2      val_w

$q2

field3               #note $q2 has returned one row
000

$q3

field4    field5    field6
val_b     val_c     val_d
val_x     val_y     val_z

Then the csv will be

field1,field2,field3,field4,field5,field6
id1,val_a,000,val_b,val_c,val_d
id2,val_w,,val_x,val_y,val_z

I attempted to collect the data with a hash of arrays like this

my @statements = ($q1,$q2,$q3);
my %HoA;
for (@statements) {

    my $sth = $dbh->prepare($_);
    $sth->execute;

    my $i=0;
    while(my @row = $sth->fetchrow_array) {
        push ( @{ $HoA{$i} }, @row[0..$#row] );
    $i++;
    }

}

I am still learning so I am not sure if this was the best choice, though it has been working for me except when one or more of the queries returns less rows than the others, this is illustrated in the above example of the fields and rows returned. And this causes the Data structure to break, the rows will run into one another. I have also tried a hash of hashes, using the fields for the keys of the inner hash

my @statements = ($q1,$q2,$q3);
my %HoH;
for (@statements) {

    my $sth = $dbh->prepare($_);
    $sth->execute;

    my $fields_ref = $sth->{NAME_lc};

    my $i=0;
    while(my @row = $sth->fetchrow_array) {
        my $v=0;
        for my $field (@$fields_ref) {
                    $HoH{$i}{$field}=$row[$v];
        $v++;
        }
    $i++;
    }
}

This runs into another problem, the inner key for the field of the row that does not exist will not be created. So a print with Data::Dumper (for the example at the beginning of my question) would look like this:

      '0' => {
               'field1' => 'id1',
               'field2' => 'val_a',
               'field3' => '000',
               'field4' => 'val_b',
               'field5' => 'val_c',
               'field6' => 'val_d',
             },
      '1' => {
               'field1' => 'id2',
               'field2' => 'val_w',   # no field3
               'field4' => 'val_x',
               'field5' => 'val_y',
               'field6' => 'val_z',
             },

So I am kinda of stuck and not sure what to try next... Maybe there is a way to retain the key for field3 and give it something like a null value? Any help please?

BryanK
  • 1,211
  • 4
  • 15
  • 33
  • I would like to know what are you reading from .. $sth->fetchrow_array .. Is it from the CSV file which you have created or from the DB .. and if its from CSV then how are you creating the CSV incase of missing values – Gaurav Jan 24 '14 at 04:52
  • @Gaurav The three queries are being run against a mysql database. I am reading the data returned from the db – BryanK Jan 24 '14 at 05:16
  • Can you post the return value of `$sth->fetchrow_array` like Gaurav requested? Without it, it is not clear what is going on. Just after the while loop line, please add `print Data::Dumper->Dump([\@row], [qw(row)]);` You will also need a `use Data::Dumper()` somewhere to be able to use this function. – rzrgenesys187 Jan 24 '14 at 07:29
  • @rzrgenesys187 I attempted a simplified example of what the db returns in the first part of my question, so $sth->fetchrow_array is returning the rows in my example in an array. – BryanK Jan 24 '14 at 13:35

1 Answers1

0

Given two tables and a legimate reason for de-normalizing them into one .csv, you should use your DBMS and not try to roll your own. There is JOIN for combining columns 'in sync' and LEFT JOIN if Eve has no car. In code:

use Modern::Perl;
use DBI;

my $dbh = DBI->connect("dbi:mysql:sotrials", "eh", "ohbnbwrg")
          || die "Could not connect to mysql:sotrials: $DBI::errstr";
$dbh->do("DROP TABLE IF EXISTS Car");
$dbh->do("DROP TABLE IF EXISTS Person");

$dbh->do("CREATE TABLE Person (Id INTEGER PRIMARY KEY, FName VARCHAR(30))");
$dbh->do("INSERT INTO Person VALUES (1, 'Adam')");
$dbh->do("INSERT INTO Person VALUES (2, 'Eve')");
$dbh->do("INSERT INTO Person VALUES (3, 'Abel')");
$dbh->do("CREATE TABLE Car (Id INTEGER PRIMARY KEY, Make VARCHAR(30), Owner INTEGER, FOREIGN KEY(Owner) REFERENCES Person(Id))");
$dbh->do("INSERT INTO Car VALUES (1, 'BMW', 1)");
$dbh->do("INSERT INTO Car VALUES (2, 'Honda', 3)");

my $sth;
$sth = $dbh->prepare("SELECT * FROM Person");
$sth->execute();
$sth->dump_results();
$sth = $dbh->prepare("SELECT * FROM Car");
$sth->execute();
$sth->dump_results();

my $fn = "./out.csv";

open(my $fho, '>', $fn) or die "Could not write open file '$fn' $!";
print $fho "FName;Car\n";
$sth = $dbh->prepare("SELECT P.FName, C.Make FROM Person P LEFT JOIN Car C On P.Id = C.Owner");
$sth->execute();
foreach my $row (@{$sth->fetchall_arrayref()}) {
    printf $fho "\"%s\";\"%s\"\n", @{$row};
}
close($fho);

$dbh->disconnect();

open(my $fhi, '<', $fn) or die "Could not read open file '$fn' $!";
print <$fhi>;
close($fhi);

output:

perl 21323885.pl
'1', 'Adam'
'2', 'Eve'
'3', 'Abel'
3 rows
'1', 'BMW', '1'
'2', 'Honda', '3'
2 rows
Use of uninitialized value $row in printf at 21323885.pl line 32.
FName;Car
"Adam";"BMW"
"Eve";""
"Abel";"Honda"
Ekkehard.Horner
  • 38,498
  • 2
  • 45
  • 96
  • Okay, I am even less experienced with sql than I am with Perl, so you will have to give me some time to try this out. But if I can try to summarize your answer... basically I need to put the results from my queries into their own respective tables, then use another query with a join to get the results from those tables? then put the results from the join in my data structure? – BryanK Jan 24 '14 at 19:37
  • @BryanK - No, as you have the base tables (that I had to create for the demo), you just combine your three SELECTs into one statement with two LEFT JOINs and dump the resultset. – Ekkehard.Horner Jan 24 '14 at 19:48
  • hmm, well the reason I specified the question was about Perl data structures and used simplifed mysql in my example is because the actual queries from the application are each already using complex join statements and it is beyond our abilities to combine them any further. Thank you for your response, I am sorry if my question is confusing Perhaps I should have left out the details about mysql. But any ideas on how to do what I am trying to do with the Perl data structures or is it not feasible? – BryanK Jan 24 '14 at 21:01
  • @BryanK - The reason I gave you an SQL answer: If you have three arrays (resultsets) of different sizes/with gaps (even if the basic ordering is 'correct'), there is no way you can map the values to the appropiate rows - other than put the (foreign) keys into those arrays and 'roll your own' LEFT JOINs. If the query get too complex (for the DBMS), use temporary tables/viwes. – Ekkehard.Horner Jan 24 '14 at 21:20