tags:

views:

374

answers:

5

I don't know if "variadic" is actually the right word, but I'm talking about things that can take a list of values, like IN(). If you've been working with DBI for long, you've probably tried to do this:

(Note: All examples extremely simplified for brevity)

my $vals = join ', ', @numbers;
my $sth = $dbh->prepare( "SELECT * FROM mytbl WHERE foo IN( ? )" );
$sth->execute( $vals );     # doesn't work

DBI placeholders simply don't support these kinds of shenanigans, it's a single value for each ? or nothing, as far as I know.

This leads me to end up doing something like:

my $sth = $dbh->prepare( "SELECT * FROM mytbl WHERE foo IN ( $vals )" );

which isn't so horrible, but consider a function, like one I wrote today, that has to accept some arbitrary SQL with an IN clause and a list of values

sub example { 
    my $self = shift;
    my ( $sql, @args ) = @_;

    my $vals = join ', ', @args;
    $sql =~ s/XXX/$vals/;    <---- # AARRRGHGH
    my $sth = $self->dbh->prepare( $sql );
    ...
}

This ends up getting called by stuff that looks like

my $sql = "SELECT * FROM mytbl WHERE foo IN( XXX ) AND bar = 42 ORDER BY baz";
my $result = $self->example( $sql, @quux );

This really offends my sense of aesthetics. Building custom SQL programmaticly is a big enough pain as it is; I don't want to go down the road of regexing my SQL strings if I don't have to.

Is there a better way?

+5  A: 

Food for thought.

DBIx::Simple offers a syntax for this type of thing using a double-question mark placeholder:

$db->query( 'SELECT * FROM mytbl WHERE foo IN ( ?? )', @args );

Also, SQL::Abstract is powerful, but I find sometimes the abstractions don't result in optimal SQL.

martin clayton
+4  A: 

Why not:

  my $sql = "SELECT * FROM mytbl WHERE foo IN(" . join(',', ('?')x@quux) . ") AND bar = 42 ORDER BY baz";
  my $sth = $dbh->prepare($sql);
  $sth->execute(@quux);
Josh McAdams
I wrote just such a method yesterday (well actually it was `my $sql = 'INSERT INTO ' . $databaseName . '.' . $tableName . ' (' . join(', ', $this->_fields) . ')' . ' VALUES (' . join(', ', ('?') x @{$this->_fields}) . ')';`, but close enough :)
Ether
With such a statement, take care that @quux isn't empty... `WHERE foo IN()` isn't valid.
ysth
+2  A: 

sprintf is handy in such situations:

my $sth = $dbh->prepare( 
    sprintf(
        'SELECT * FROM mytbl WHERE foo IN( %s )',
        join(',', ('?') x @numbers) )
);
Sinan Ünür
I like the `sprintf` idea; I'm amazed I didn't think of that as I was working on this
friedo
I always thought that `sprintf` wasn't very perlish, but there's definitely advantages in complicated substitutions (e.g. my concat statement in comments below is write-only). :)
Ether
Perlish = getting the job done easily. sprintf is often an easy way to get the job done, ergo ... :)
brian d foy
+2  A: 

If using placeholders and bind values gets clumsy, there's always DBI::quote().

my $sql = sprintf 'SELECT * FROM mytabl WHERE foo IN ( %s )',
     join( ',', map { $dbh->quote( $_ ) } @args );
innaM
+4  A: 

If you don't mind breaking from pure DBI and using some modules, I'd take a look at SQL::Abstract for your example. SQL::Abstract can take a Perl hash and turn it into a where clause.

my $sql  = SQL::Abstract->new;
my @numbers = (1 .. 10);
my ($stmt, @bind) = $sql->where({foo => {'in', \@numbers}});
# $stmt is " WHERE ( foo IN ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) )"
# @bind contains the values 1 through 10.
Jack M.