shemp has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,
I've run into a bit of a problem with bind params using the DBI. In short, the problem is: how do i get a DB defined default value into a column, using an insert query that is expecting a bound value for that column?

Here is a much more detailed explanation of what im trying to accomplish. Lets say i have a table defined by:
CREATE TABLE blah ( A INT, B INT NOT NULL DEFAULT 0 )
And i create a query to write to this table, such as:
my $query = "INSERT INTO blah SET A = ?, B = ? "; my $query_handle = $db_handle->prepare($query);
Then i have an array of hashrefs, each containing 1 row of values to insert, such as
my @to_insert = ( { 'A' => 1, 'B' => 1 }, { 'B' => 2 }, { 'A' => 3 }, );
Now i have a loop to go through and insert all of these rows. When i wasnt using bind params, if a column doesnt have a value, i just dont mention it in the query, and the DB takes care of inserting a default value. But when using bind params, i cant figure out a way to get the DB to insert the default value, without creating a separate query for each combination of fields to insert.

For this trivial example, there are only 3 queries, but in general, if there are n columns, there are (2^n - 1) possible column combinations, and i dont want to have to create each one.

I dont even know if this is possible, but any insight would be great.

Replies are listed 'Best First'.
Re: DBI bind params & column defaults
by Plankton (Vicar) on Oct 27, 2003 at 18:13 UTC
    maybe you could try something like this ...
    use strict; my @to_insert = ( { 'A' => 1, 'B' => 1 }, { 'B' => 2 }, { 'A' => 3 }, ); for my $row (@to_insert) { my $insertStmt; my $cols; my $vals; for my $col (keys %{$row} ) { $cols .= $col . ","; $vals .= "?,"; } $insertStmt = "insert into blah ($cols) values ($vals);"; print "$insertStmt\n"; }

    Plankton: 1% Evil, 99% Hot Gas.
Re: DBI bind params & column defaults
by dragonchild (Archbishop) on Oct 27, 2003 at 18:18 UTC
    Build your query dynamically. Do something like:
    sub inserter { my ($tablename, @insert) = @_; my $sql = "INSERT into $tablename SET"; my $rows_affected = 0; foreach my $insert (@insert) { my @columns; while (my ($k, $v) = each %$insert) { push @columns, "$k = $v"; } $rows_affected += $dbh->do( $sql . join(',', @columns), ) || die $DBI::errstr; } return $rows_affected; }

    That should do most of what we want.

    ------
    We are the carpenters and bricklayers of the Information Age.

    The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

    ... strings and arrays will suffice. As they are easily available as native data types in any sane language, ... - blokhead, speaking on evolutionary algorithms

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: DBI bind params & column defaults
by liz (Monsignor) on Oct 27, 2003 at 18:14 UTC
      Yes in fact it seems to be effectively the same. I hadnt found that thread in my poking around here. :(

      In regard to Jenda's solution, it sounds specific to UPDATE queries. Any idea what'll happen on an insert query? I suppose i could just try it out myself!

      Thanks much!

        You could use COALESCE just as well on INSERT. Something like:
        INSERT INTO table (id,type) VALUES ( id, COALESCE(?,defaulttypevalue) )
        Well, in general if a field is given the NULL value (which will be inserted if the given value for a placeholder is undef), then the database decides what to do with it. Usually it will just be marked as NULL in the database (as in: not having any value). Sometimes you can specify a default value when you create the table. Sometimes something special happens (e.g. with a TIMESTAMP field in MySQL).

        Liz

      and was answered by Jenda?

      I was under the impression that many wise monks answered your question. Jenda's solution is just one of the many you got.

        The answers of the wise monks, apart from Jenda's answer which introduced COALESCE() to me, all involved creating the query on the fly, over and over again. This is something that I have been doing since basically 1995, so that didn't bring much news to me.

        In my opinion, Jenda's answer was the only one that addressed the fixedness of the statement handle for the update. And therefore was considered an avenue for me to go in. And also in this thread, the only new answer for shemp as far as I can see.

        MySQL 4.1 seems to support prepared statements on the server side. Too bad DBD::mysql doesn't support that yet. Any takers? ;-)

        Liz

Re: DBI bind params & column defaults
by shemp (Deacon) on Oct 27, 2003 at 18:29 UTC
    I guess that i didn't strees in my original post that the purpose here is to take advantage of caching prepared statements with bound params. I am actually trying to improve upon my existing code that already does manual construction of the insert query for each record being inserted.
Re: DBI bind params & column defaults
by shemp (Deacon) on Oct 27, 2003 at 23:23 UTC
    I have discovered something that helps a little here.

    If a column can accept a null value and you dont assign a bind value to it, thats ok, a null is inserted there. But for non-null columns that arent assigned a bind value, an error like this occurs: (for MySQL at least)

    DBD::mysql::st execute failed: Column 'blah' cannot be null at bind_test.pl line 32.

    Yes this is probably well-documented somewhere, but i thought i had read quite a lot of bind param documentation, and much more was learned in working on this problem.

    *** Note: I have only tested this for char and int type columns, im assuming that other columns will behave similarly. (assuming will bite me though - as it always does)

Re: DBI bind params & column defaults
by injunjoel (Priest) on Oct 28, 2003 at 00:24 UTC
    Greetings all,
    one rather hokey/simple solution might be to declare the default values in a hash then validate your input rows before you execute the statement handle.
    #set your defaults for the table in question my %defaults = ('col1'=>'col1_default_value','col2'=>'col2_default_val +ue'...);#you get the idea. #your data set my @to_insert = ( { 'A' => 1, 'B' => 1 }, { 'B' => 2 }, { 'A' => 3 }, ); #clean it up for the statement handle foreach my $rec (@to_insert){##each record to be inserted foreach my $key (keys %defaults){##each field in the record unless(defined $rec->{$key}){ $rec->{$key} = $defaults{$key}; } } }
    A bit cheesey I know but I hope that helps.
    -injunjoel
    Side-Thought: Have you tried inserting a "" or '' for a bound column to see if the default value gets inserted?