Creating a hashes from AoHs
bradcathey
created: 2006-03-02 21:32:52

Fellow Monastians:

As I pull in records from a flat, tab-delimited file, I parse it into an AoH, one array element for each record (I have to go through this preliminary step because I have to test a few of the hash values for legitimacy).

In the end, I loop through the array and copy each of the hashes into a single hash for insertion into my DB table.

I thought I could do this in just one step:

for ( 0 .. $#fields) {
   %sql_data = $fields[$_];
  ... insert into DB ...
}

but even with Data Dumping I couldn't figure out why it wasn't working. So, as a work-around until I could post it here, I did a very convoluted:

for ( 0 .. $#fields) {
   $sql_data{'description'}   = $fields[$_]{'description'};
   $sql_data{'billing_code'}  = $fields[$_]{'billing_code'};
   $sql_data{'user_id'}       = $fields[$_]{'user_id'};
   $sql_data{'project_id'}    = $fields[$_]{'project_id'};
   $sql_data{'bad_proj'}      = $fields[$_]{'bad_proj'};
   $sql_data{'bad_bill'}      = $fields[$_]{'bad_bill'};
   $sql_data{'bad_user'}      = $fields[$_]{'bad_user'};

   $stmt = qq/INSERT INTO temp_sheet (/ . join(',', keys %sql_data ) . qq/) 
               VALUES (/ . join(',', ('?') x keys %sql_data ) . qq/)/;   

   $sth = $dbh->prepare($stmt);
   $sth->execute(values %sql_data );
}

I know this could be better, but I just can't make it work. What am I totally not seeing. Thanks in advance.

Yes, I'm using strict.


—Brad
"The important work of moving the world forward does not wait to be done by perfect men." George Eliot
Re: Creating a hashes from AoHs
created: 2006-03-02 21:52:08
very close .. @fields is an array of hash refs, so you have to dereference it as a hash with the % ...
for ( 0 .. $#fields) {
   %sql_data = %{$fields[$_]};
  ... insert into DB ...
}
or really just:
foreach my $sql_data ( @fields ) {
   $stmt = qq/INSERT INTO temp_sheet (/ . join(',', keys %$sql_data ) . qq/) 
               VALUES (/ . join(',', ('?') x keys %$sql_data ) . qq/)/;   
   $dbh->do( $sql, {}, values %$sql_data );
}

A quick aside -- an example using [cpan://SQL::Abstract]:
use SQL::Abstract;
my $sql = SQL::Abstract->new;
foreach my $data (@fields){
  my($stmt, @bind) = $sql->insert('temp_sheet', $data);
  $dbh->do( $stmt, {}, @bind );
}
Re^2: Creating a hashes from AoHs
created: 2006-03-02 22:35:39

Thanks [davidrw], great answers and examples.

Strange though, I did try

%sql_data = %{$fields[$_]};

before my OP, but for some reason it didn't work. Anyway, I'm good to go...and a little smarter.


—Brad
"The important work of moving the world forward does not wait to be done by perfect men." George Eliot
Re: Creating a hashes from AoHs
created: 2006-03-03 03:26:45
Now that the basic syntax problem is solved, I can't resist harping on the old truism (which many have probably grown tired of seeing):

If you're processing a lot of rows, you'd be better off making you perl script a little simpler: have it just print out another suitable flat file that would be easy to feed into whatever data-importer tool is native to the particular database server you are using.

The speed differential between bulk-loading with such a tool vs. doing a series of inserts with DBI (even with a prepared statement and placeholders) can be dramatic if you're dealing with thousands of rows on a regular basis. And of course, you can set up your perl script so that once it's done writing the loadable flat file, it goes ahead and runs your database bulk-loader utility on the file. That way, the end result from the user's point of view is the same, except that it happens much faster.

(For smaller sets of rows, it's a toss-up -- whatever you're most comfortable with is fine -- but the bulk-load tool scales well, whereas DBI inserts do not.)

Re^2: Creating a hashes from AoHs
created: 2006-03-03 07:49:39

Okay, I'm interested. Do you have sample code that show how this works? I'm especially interested in how it gets from the new flat file into the database (I use MySQL).

Thanks.


—Brad
"The important work of moving the world forward does not wait to be done by perfect men." George Eliot
Re^3:
created: 2006-03-03 19:45:11
In terms of how to get flat-file data into table, just read about "LOAD DATA INFILE" and "mysqlimport" in the mysql manual -- it's pretty clear and simple (a lot less hassle than the bulk-loader tools of some other RDB engines).

As for getting your perl script to write a flat file instead of doing inserts directly to mysql, just open an output file instead of a connection to the db. (Well, if some of your input is coming from the db, you can either connect to do the query, or save the query output to a file before running this script.)

When you get to the point in the script where you would have done an insert statement, just write those values to the output file as tab-delimited fields, terminated by a newliine. When done, close the file and use "system()" to run mysqlimport on it.

Re: Creating a hashes from AoHs
created: 2006-03-03 09:44:04

Why are you even doing the copy? It seems like a waste:

foreach (@fields) {
   
  my $sth = $dbh->prepare( 'INSERT INTO temp_sheet ('
                           .join(',', keys %$_)
                           .') VALUES ('.
                           .join(',', map {'?'} keys %$_)
                           .')'
                          );
  
  $sth->execute(values %$_);
}

Of course, there are still issues with that in broad strokes, because you're preparing a statement for each record, and you're relying on keys and values returning the same order. While, AFAIK, they do, I'm not sure it's a promise future versions of Perl will keep.

So, I'd refactor a touch:

# list your headers
my @heading = qw[description billing_code user_id 
                 project_id bad_proj bad_bill bad_user];

# now prepare a statement *once*
my $sth = $dbh-execute( map { $row{$_} } @heading );
}

That should be a lot faster. You could also lower your maintenance requirements by determining @heading from the DB with:

my @heading;
{
   my $sth = $dbh->prepare('SELECT TOP 1 * FROM temp_sheet');
   $sth->execute;
   @heading = @{ $sth->{NAME_lc} };
}
<-radiant.matrix->
A collection of thoughts and links from the minds of geeks
The Code that can be seen is not the true Code
I haven't found a problem yet that can't be solved by a well-placed [http://en.wikipedia.org/wiki/Trebuchet|trebuchet]

perlmonks.org content © perlmonks.org and bradcathey, davidrw, graff, radiantmatrix

prlmnks.org © 2006 edmund von der burg (eccles & toad)

v 0.03