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.
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 );
}
use SQL::Abstract;
my $sql = SQL::Abstract->new;
foreach my $data (@fields){
my($stmt, @bind) = $sql->insert('temp_sheet', $data);
$dbh->do( $stmt, {}, @bind );
}
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.
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.)
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.
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.
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} };
}
perlmonks.org content © perlmonks.org and bradcathey, davidrw, graff, radiantmatrix
prlmnks.org © 2006 edmund von der burg (eccles & toad)
v 0.03