I know I can do this with interpolation. Can I do it using placeholders?
I am getting this error:
DBD::Pg::st execute failed: ERROR: invalid input syntax for integer: "{"22,23"}" at ./testPlaceHolders-SO.pl line 20.
For this script:
#!/usr/bin/perl -w
use strict;
use DBI;
# Connect to database.
my $dbh = DBI->connect("dbi:Pg:dbname=somedb;host=localhost;port=5432", "somedb", "somedb");
my $typeStr = "22,23";
my #sqlParms = [ $typeStr ];
my $sqlStr = << "__SQL_END";
SELECT id
FROM states
WHERE typeId in (?)
ORDER BY id;
__SQL_END
my $query = $dbh->prepare($sqlStr);
$query->execute(#sqlParms);
my $id;
$query->bind_columns(\$id);
# Process rows
while ($query->fetch())
{
print "Id: $id\n";
}
Is there a way around it besides interpolation?
DBD::PG has support for PostgreSQL arrays, so you can simply write a query like this:
WHERE typeid = ANY( ARRAY[1,2,3] )
or, with a parameter...
WHERE typeid = ANY(?)
Then just use the array support
my #targets = (1,2,3);
# ...
$query->execute(\#targets);
Posting comment as answer, as requested.
Generate your own placeholder string. Like so:
my #nums = (22,23);
my $placeholder = join ",", ("?") x #nums;
$query->execute(#nums);
Yes. You must use placeholders for each value, such as IN (?, ?, ?). You can however generate the correct number of question marks using something like this (untested):
my #values = (22, 23, ...);
# will produce "?, ?, ..."
my $in = join ", ", ("?") x #values;
my $sqlStr = "SELECT id FROM states WHERE typeId in ($in) ORDER BY id;";
my $query = $dbh->prepare($sqlStr);
$query->execute(#values);
Note that if you use an ORM such as DBIx::Class instead, this sort of ugly hack gets abstracted away.
You have to build the SQL statement with the correct number of question marks and then set the parameter values. There is no way to bind a list to a single question mark.
Related
I keep getting errors when attempting to use placeholders in my perl script for a Mysql routine.
Code :
use DBI;
my $driver = "mysql";
my $database = "database";
my $user = "exxxxxx";
my $password = "xxxxx";
my $dsn = "DBI:mysql:$database;mysql_local_infile=ON";
my $dbh = DBI->connect($dsn,$user,$password);
$dbh->do("SET \#tempc5 = (SELECT temp FROM day5 WHERE time = '00:00') ");
my $inter1 = i24;
$sth = $dbh->prepare( "SET \#sumadd5 = (SELECT ? FROM humid WHERE temp=\#tempc5) " );
$sth->bind_param( 1, $inter1 );
$sth->finish();
$dbh->disconnect();
This produces the following error:
Global symbol "$sth" requires explicit...
If I add a my $sth I get the following error:
Scalar found where operator expected...
Note that I am have no objection in trying this with $dbh->do("SET"
if possible.
The placeholders are not allowed for column names according to MySQL Manual for mysql_stmt_prepare() which is the function behind prepare.
The markers are legal only in certain places in SQL statements. For
example, they are permitted in the VALUES() list of an INSERT
statement (to specify column values for a row), or in a comparison
with a column in a WHERE clause to specify a comparison value.
However, they are not permitted for identifiers (such as table or
column names), or to specify both operands of a binary operator such
as the = equal sign. The latter restriction is necessary because it
would be impossible to determine the parameter type. In general,
parameters are legal only in Data Manipulation Language (DML)
statements, and not in Data Definition Language (DDL) statements.
If you think about it, it would not make sense to prepare a statement where you can change a column. Preparation of statement includes execution plan, but you can't plan execution of a statement where you don't know if given column has or doesn't have an index on it.
You can't use a placeholder there.
When you call prepare, all structural information about your tables is baked into the query, waiting for you to pass in data values to replace placeholders when you execute the query.
But you're trying to use a placeholder for a column name, which is part of the table's structure.
If you fix the Perl syntax to be:
my $inter1 = 'i24';
my $sth = $dbh->prepare( "SET \#sumadd5 = (SELECT ? FROM humid WHERE temp=\#tempc5) " );
$sth->execute($inter1);
it should run, but the ? will be treated as a data value rather than a column name (structural information). So you'll get the results of the SQL query
SET #sumadd5 = (SELECT 'i24' FROM humid WHERE temp=#tempc5)
instead of
SET #sumadd5 = (SELECT i24 FROM humid WHERE temp=#tempc5)
The subquery will return the literal value "i24" for each matching row rather than the value found in column i24.
You didn't quoted the vaule of $inter1. Change $inter1 = i24; to $inter1 = 'i24';. Just edited in your code, this will not give you syntax error.
use warnings;
use strict;
use DBI;
my $driver = "mysql";
my $database = "database";
my $user = "exxxxxx";
my $password = "xxxxx";
my $dsn = "DBI:mysql:$database;mysql_local_infile=ON";
my $dbh = DBI->connect($dsn,$user,$password);
$dbh->do("SET \#tempc5 = (SELECT temp FROM day5 WHERE time = '00:00') ");
my $inter1 = 'i24';
my $sth = $dbh->prepare( "SET \#sumadd5 = (SELECT ? FROM humid WHERE temp=\#tempc5) " );
$sth->bind_param( 1, $inter1 );
$sth->finish();
$dbh->disconnect();
In my perl program I have a function which takes a query as input and executes it and prints any rows fetched if the query is a select statement.
But the problem is with update or insert statements where fetchrow_array gives an error so is there any way i can know what type of query was executed so that i can skip the fetch part?
Not knowing what sort of SQL you are executing sounds like a bad idea to me, but that's not what you asked, so...
Check the docs and you will find:
You can tell if the statement was a SELECT statement by checking if
$sth->{NUM_OF_FIELDS} is greater than zero after calling execute.
Create a little helper function using a regular expressions to check the SQL string for specific words in the query...
my $sql = "UPDATE some_table SET some_value = 'abc' WHERE some_value = 'xyz'";
sub get_query_type
{
my $sql_string = shift;
my $query_type;
if( $sql_string =~ m/^UPDATE/i )
{
$query_type = "An UPDATE query";
# do UPDATE stuff
}
elsif( $sql_string =~ m/^INSERT/i )
{
$query_type = "An INSERT query";
# do INSERT stuff
}
# more blocks here as needed.
return $query_type;
}
my $type = get_query_type( $sql );
print $type;
Is there any easier way to add char to the beginning and end of array elements in perl.
Ex:my $str ='table1,table2,table3'
my #arry = split (/,/,$str)
I like the output should look like
'table1','table2','table3'
So it can be used in sql query
Thanks.
join(',', map "'$_'", #arry)
is what you are asking for.
But it's much better to use placeholders:
my $str = 'table1,table2,table3';
my #arry = split(/,/, $str);
if (#arry) {
my $query = 'select * from tablename where colname in (' . join(',',('?') x #arry) . ')';
my $sth = $dbh->prepare($query);
$sth->execute(#arry);
...
}
If you are going to use DBI (https://metacpan.org/module/DBI) to work with your database, it can do it for you if you use placeholders in your queries, so that you don't have to quote values.
For example, if you want to insert something into a table:
$dbh->do("INSERT INTO table VALUES(?, ?, ?)", undef, #arry)
or die $dbh->errstr;
where #arry is an array containing exactly 3 values to be inserted into a table.
More on this can be found here https://metacpan.org/module/DBI#Placeholders-and-Bind-Values
I am writing a Perl script that is using the DBI module and is connecting to a Sybase DB. I am calling a stored procedure (one that I don't have access to so I cannot post sample code) and when I get data back I get an error that reads "error_handler: Data-conversion resulted in overflow". I still get data back and after doing some intensive research it seems that some data types in the columns (such as BigInt, nvarchar, etc) are the culprits. Now the question is, how can I fix this? Can this be fixed on the client side or can it only be fixed on the server side?
my $dbh = DBI->connect("DBI:Sybase:server=$server", $username, $password, {PrintError => 0}) or die;
$dbh->do("use $database") or die;
my $sql = &getQuery;
my $sth = $dbh->prepare($sql) or die;
$sth->execute() or die;
while ($rowRef = $sth->fetchrow_arrayref) #Error seems to occur here
{
#Parse through each row
}
Part of the FreeTDS 0.82 log that explains the problem:
_ct_bind_data(): column 7 is type 38 and has length 8
_ct_get_server_type(0)
_ct_get_client_type(type 38, user 0, size 8)
cs_convert(0x18dfed40, 0x7fff73216050, 0x18e44250, 0x7fff73215fa0, 0x18e387c0, 0x18e45a64)
_ct_get_server_type(30)
_ct_get_server_type(0)
converting type 127 (8 bytes) to type = 47 (9 bytes)
cs_convert() calling tds_convert
cs_convert() tds_convert returned 10
cs_prretcode(0)
cs_convert() returning CS_FAIL
cs_convert-result = 1
The problem is on the FreeTDS side. I've had the same problem before and successfully fixed it by converting the returned fields to varchar in the select statement.
Given you don't have access to modify the original query, you can do some regex search and replace on the returned $sql variable in your code. In particular, if the original query has a part that looks like
SELECT field1, field2, field3 FROM ...
After you retrieve the query statement, you may run
my $new_sql;
if ($sql =~ /SELECT\s+(.*)\s+FROM/i) { # match selected field string
my $field_str = $1;
my #fields = split ",", $field_str; # parse individual fields
map s/\s//g, #fields; # get rid of spaces
my $new_str = join ", ", (map {sprintf "convert(varchar, $_)"} #fields); # construct new query string
my $quoted_field_str = quotemeta($field_str); # prepare regex replacement string
$new_sql = $sql;
$new_sql =~ s/$quoted_field_str/$new_str/i # actual replacement
}
print $new_sql;
Of course, if your original statement is more complex, you should print it out and check how to modify it with a generic replacement bearing the same spirit. Alternatively, you can ask your DBA (or whoever has access to the stored procedure) to modify the actual query directly.
Hope this helps.
I'm writing a subroutine for DBI updates, and are having some trouble figuring out how to add placeholder and stuff...
I have this:
sub row_update {
my $table = shift;
my %updates = #_;
my $placeholders = ...
$dbh->do("UPDATE $table SET (foo) WHERE (bar)") etc...
}
Any ideas?
I just want a simple update function where I can send in x number of arguments (as a hash).
Thanks in advance!
Something like this might be Good Enough:
sub update {
my ($dbh, $args) = #_;
my $table = $args->{table} || die 'need table';
my $updates = $args->{updates} || die 'need updates';
my #cols = keys %$updates;
my $query = 'UPDATE $table SET '.
(join ', ', map { '$_ = ?' } #cols)
($args->{where} ? ' WHERE '. $args->{where} : '');
my $sth = $dbh->prepare($query);
$sth->execute(map { $updates->{$_} } #cols);
return $sth;
}
Use it like:
my $sth = update $dbh, {
table => 'foo',
updates => {
col1 => 'new_value',
col2 => 'another_value',
},
where => 'id=42',
};
Really, though, you want to look into using an ORM like
DBIx::Class. It will do a much better job of building queries than
string manipulation like this will.
(Rewriting the where clause to be parameterized is left as an exercise to the reader. You also need to quote the update keys and table name. See why people use ORMs?)
Edit: Thinking about this a bit more, you might like DBIx::Simple combined with SQL::Abstract. This will take less configuration effort than an ORM, but still give you many of the benefits.
If I understand the question correctly, it sounds like you're after SQL::Abstract. First, we create an SQL::Abstract object:
use SQL::Abstract;
my $sql = SQL::Abstract->new;
Now, as an example, we'll use it to insert some data into a table:
my %record = (
FirstName => 'Buffy',
LastName => 'Summers',
Address => '1630 Revello Drive',
City => 'Sunnydale',
State => 'California',
Occupation => 'Student',
Health => 'Alive',
);
my ($stmt, #bind) = $sql->insert(’staff’,\%record);
This results in:
$stmt = "INSERT INTO staff
(FirstName, LastName, Address, City,
State, Occupation, Health)
VALUES (?, ?, ?, ?, ?, ?, ?)";
#bind = ('Buffy','Summers','1630 Revello Drive',
'Sunnydale',’California','Student','Alive');
The nice thing about this is we can pass it directly to DBI:
$dbh->do($stmt, undef, #bind);
Of course, you want to be updating records, not just inserting them. Luckily, this is also quite easy:
my $table = 'People';
my %new_fields = (
Occupation => 'Slayer',
Health => 'Dead',
);
my %where = (
FirstName => 'Buffy',
LastName => 'Summers',
);
my ($stmt, #bind) = $sql->update($table, \%new_fields, \%where);
$dbh->do($stmt, undef, #bind);
This produces:
$stmt = 'UPDATE People SET Health = ?, Occupation = ?
WHERE ( FirstName = ? AND LastName = ? )';
#bind = ('Dead', 'Slayer', 'Buffy', 'Summers');
If you're after more information about SQL::Abstract, I recommend you look at its CPAN page. There's also a chapter in Perl Training Australia's Database Programming with Perl manual, which are freely available from our course notes page.
All the best,
Paul
Disclaimer: I'm managing director of Perl Training Australia, and therefore think that our course notes are pretty good.
Others suggested the usual "build a query with the right number of '?'s" approach.
For most queries like this the DBI->quote method is forgotten, but it can keep the code simpler and in most cases it's not any slower than the "proper" placeholder approach.
1) Use DBI->quote instead of place holders to build the query. For example for a simple select:
my $sql = "select foo from bar where baz in ("
. join(",", map { DBI->quote($_) } #bazs)
. ")";
my $data = $dbh->selectall_arrayref($sql);
2) As jrockway suggested - use an ORM to do this sort of low level stuff for you. DBIx::Class or Rose::DB::Object, for example.
The NUM_OF_PARAMS attribute may be of assistance:
"NUM_OF_PARAMS" (integer, read-only)
The number of parameters (placeholders) in the prepared
statement.
So for instance, I've got a script to run arbitrary SQL from the command line that uses this code:
my $s = $h->prepare($_);
for my $i (1..$s->{NUM_OF_PARAMS}){
my $param = shift #ARGV;
$s->bind_param($i, $param);
print LOG "Bind param $i using $param.\n"
or die "can't append to $opt{log}: $!";
}
$s->execute();
I can't say I've used the other modules that have been suggested, so they may do a better job.