perl dbi prepare with variable table column name - perl

I have used the following code many times before when inserting values into database tables using perl
my $SRV='xxx';
my $DB='dbname';
my $db = DBI->connect("dbi:Oracle:$SRV/$DB", "user", "pword" ) or die "impossible de se connecter à $SRV / $DB";
my $insert_T1 = "INSERT INTO tablename (ColA, ColB) VALUES ( ?, ?) " ;
my $insert_T1_sth = $db->prepare($insert_T1) ;
Later in the code I can then call the following to do the insertion
$insert_T1_sth->execute('val1','val2');
$insert_T1_sth->execute('val3','val4');
So basically when I use the prepare function above I can replace the entries I want to insert by question marks and then put the values of these question marks in the execute statements later on.
So to my question: Can I use question marks in place of column names in the prepare statement? I'm thinking no because when I try the following I get a runtime error on the line where the execute statement(s) are.
my $SRV='xxx';
my $DB='dbname';
my $db = DBI->connect("dbi:Oracle:$SRV/$DB", "user", "pword" ) or die "impossible de se connecter à $SRV / $DB";
$db->{AutoCommit} = 0 ;
my $insert_T1 = "INSERT INTO tablename (ColA, ?) VALUES ( ?, ?) " ;
my $insert_T1_sth = $db->prepare($insert_T1) ;
Then later, as before, use
$insert_T1_sth->execute('colname1','val1','val2');
$insert_T1_sth->execute('colname2','val3','val4');

You can't use dynamic column names with prepare like you are trying to do.
Your column names shouldn't be known to the user, and therefore don't really need to be part of the parameters, since they are not sensitive (and don't need to be protected against SQL injection). Preparing is still useful for performances though.
What I'd suggest is to do a prepare for each of you column name, and store those in a hash:
my #col_names = qw(colname1 colname2);
my %inserts;
for my $col (#col_names) {
$inserts{$col} = $db->prepare("INSERT INTO tablename (ColA, $col) VALUES (?, ?)");
}
...
$inserts{colname1}->execute('val1', 'val2');

Related

How can I combine these two statements?

I'm currently trying to insert data into a database from a text boxes, $enter / $enter2 being where the text is being written.
The database consists of three columns ID, name and nametwo
ID is auto incrementing and works fine
Both statements work fine on their own, but because they are being issued separately the first leaves nametwo blank and the second leaves name blank.
I've tried combining both but haven't had much luck, hope someone can help.
$dbh->do("INSERT INTO $table(name) VALUES ('".$enter."')");
$dbh->do("INSERT INTO $table(nametwo) VALUES ('".$enter2."')");
To paraphrase what others have said:
my $sth = $dbh->prepare("INSERT INTO $table(name,nametwo) values (?,?)");
$sth->execute($enter, $enter2);
So you don't have to worry about quoting.
You should read database manual.
The query should be:
$dbh->do("INSERT INTO $table(name,nametwo) VALUES ('".$enter."', '".$enter2."')");
The SQL syntax is
INSERT INTO MyTable (
name_one,
name_two
) VALUES (
"value_one",
"value_two"
)
Your way of generating SQL statements is very fragile. For example, it will fail if the table name is Values or the value is Jester's.
Solution 1:
$dbh->do("
INSERT INTO ".$dbh->quote_identifier($table_name)."
name_one,
name_two
) VALUES (
".$dbh->quote($value_one).",
".$dbh->quote($value_two)."
)
");
Solution 2: Placeholders
$dbh->do(
" INSERT INTO ".$dbh->quote_identifier($table_name)."
name_one,
name_two
) VALUES (
?, ?
)
",
undef,
$value_one,
$value_two,
);

How do I use Placeholders in Perl to Inject a Variable into Mysql

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();

How to use placeholders with "in (...)" clause?

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.

String getting converted to number when inserting it in database through Perl's DBI $sth->execute() function

I'm using Perl's DBI and SQLite database (I have DBD::SQLite installed). I have the following code:
my $dbh = DBI->connect("dbi:SQLite:dbname=$db", "", "", { RaiseError => 1, AutoCommit => 1 });
...
my $q = "INSERT OR IGNORE INTO books (identica, book_title) VALUES (?, ?)";
my $sth = $dbh->prepare($q);
$sth->execute($book_info->{identica}, $book_info->{book_title});
The problem I have is when $book_info->{identica} begins with 0's they get dropped and I get a number inserted in the database.
For example, identica of 00123 will get converted to 123.
I know SQLite doesn't have types, so how do I make DBI to insert the identica as string rather than number?
I tried quoting it as "$book_info->{identica}" when passing to $sth->execute but that didn't help.
EDIT
Even if I insert value directly in query it doesn't work:
my $i = $book_info->{identica};
my $q = "INSERT OR IGNORE INTO books (identica, book_title) VALUES ('$i', ?)";
my $sth = $dbh->prepare($q);
$sth->execute($book_info->{book_title});
This still coverts 00123 to 123, and 0000000009 to 9...
EDIT
Holy sh*t, I did this on the command line, and I got this:
sqlite> INSERT INTO books (identica, book_title) VALUES ('0439023521', 'a');
sqlite> select * from books where id=28;
28|439023521|a|
It was dropped by SQLite!
Here is how the schema looks:
CREATE TABLE books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
identica STRING NOT NULL,
);
CREATE UNIQUE INDEX IDX_identica on books(identica);
CREATE INDEX IDX_book_title on books(book_title);
Any ideas what is going on?
SOLUTION
It's sqlite problem, see answer by in the comments by Jim. The STRING has to be TEXT in sqlite. Otherwise it treats it as number!
Changing schema to the following solved it:
CREATE TABLE books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
identica TEXT NOT NULL,
);
Use bind params
my $sth = $dbh->prepare($q);
$sth->bind_param(1, 00123, { TYPE => SQL_VARCHAR });
$sth->bind_param(2, $book_info->{book_title});
$sth->execute();
UPDATE:
Read about type affinity in SQLite. Because your column type is STRING (technically unsupported), it defaults to INTEGER affinity. You need to create your column as TEXT instead.
According to the docs, if the column type (affinity) is TEXT it should store it as a string; otherwise it will be a number.

Perl DBI insert and select

I want to copy a single record from a table, modify some of the fields in the record and insert into the same table. The table has 90 columns.
Thought of using insert..select in one statement but there are 90 columns and i need to tell the column name in the select query. How can i do it in a better way in perldbi. Pls provide me an example.
Fetch and cache the column names for your table using the NAME statement attribute
my $sth = $dbh->prepare('SELECT * FROM my_table where 1=0');
$sth->execute;
my $cols = $sth->{NAME};
then use $cols to construct your insert...select using some replacement function to inject your modifications in the select.
my %mods_for_column = ( 'foo' => 'foo+10', 'bar' => 'trim(bar)' );
my $inscols = join(',', #$cols);
my $selcols = join(',',
map { exists($mods_for_column($_)) ? $mods_for_column($_) : $_ } #$cols
);
my $sql = "insert into my_table ($inscols) select $selcols from my_table where mumble...";