How to bind ISO8601 TSQL DATETIME parameter with PDO? - php

It seems that PDO has a problem with ISO 8601 formatted timestamps.
I'm connecting from 64-bit Ubuntu 16.04 running PHP 7.0.8 using the Microsoft® ODBC Driver 13 (Preview) for SQL Server®
Here's my simple table:
CREATE TABLE dtest (
"stamp" DATETIME
);
Works:
$pdoDB = new PDO('odbc:Driver=ODBC Driver 13 for SQL Server;
Server='.DATABASE_SERVER.';
Database='.DATABASE_NAME,
DATABASE_USERNAME,
DATABASE_PASSWORD
);
$pdoDB->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$sql = "INSERT INTO dtest (stamp) VALUES ('2011-03-15T10:23:01')";
$stmt = $pdoDB->prepare($sql);
$params = [];
$stmt->execute($params);
Does not work:
$sql = "INSERT INTO dtest (stamp) VALUES (?)";
$stmt = $pdoDB->prepare($sql);
$params = ['2011-03-15T10:23:01'];
$stmt->execute($params);
Fatal error: Uncaught PDOException: SQLSTATE[22018]: Invalid character value for cast specification: 0 [Microsoft][ODBC Driver 13 for SQL Server]Invalid character value for cast specification (SQLExecute[0] at /build/php7.0-lPMnpS/php7.0-7.0.8/ext/pdo_odbc/odbc_stmt.c:260)
This works if I delete the T so '2011-03-15T10:23:01' becomes '2011-03-15 10:23:01'
$sql = "INSERT INTO dtest (stamp) VALUES (?)";
$stmt = $pdoDB->prepare($sql);
$params = ['2011-03-15 10:23:01'];
$stmt->execute($params);
But I'm writing a script that runs nightly on about 2 million records, so I'd really rather not bear the overhead of running millions of str_replace('T', ' ', $param)
I've also tried using bindParam, but it gives the same error:
$sql = "INSERT INTO dtest (stamp) VALUES (:tdate)";
$stmt = $pdoDB->prepare($sql);
$date = '2011-03-15T10:23:01';
$stmt->bindParam(':tdate',$date,PDO::PARAM_STR);
$stmt->execute();
Is there anyway to bind and execute this parameter as is? I'm a little dubious of the error message because it appears to be coming from SQL Server as if PDO did its job fine, but that doesn't make sense since it's able to handle the type conversion without parameterization.
I've also tried SQL conversion:
Works:
$sql = "INSERT INTO dtest (stamp) VALUES (CONVERT(DATETIME, '2011-03-15T10:23:02', 126))";
$stmt = $pdoDB->prepare($sql);
$params = [];
$stmt->execute($params);
Does not Work:
$sql = "INSERT INTO dtest (stamp) VALUES (CONVERT(DATETIME, ?, 126))";
$stmt = $pdoDB->prepare($sql);
$params = ['2011-03-15T10:23:02'];
$stmt->execute($params);

You will need to use SQL Server's built-in convert() function and specify the format (126) which you are giving it:
$sql = "INSERT INTO dtest (stamp) VALUES (CONVERT(DATETIME, '2011-03-15T10:23:01', 126))";
The documentation mentions :mmm at the end of your string so you might need to manually add :000 at the end of your date string for this to work.

After half a day spent trying to resolve the same issue, I ended up dropping odbc and using dblib instead. I installed php7.0-sybase package, adapted the data source name of my PDO connection and resolved once for all.
Now every bind is working.

Related

Prepared statement cannot be executed multiple times with integer values

How do I properly re-execute a prepared statement using different integer values?
There's something deathly wrong with explicit and implicit binding PDO::PARAM_INT when reusing an ODBC prepared statement.
CREATE TABLE mytab (
col INT,
something VARCHAR(20)
);
Works : multiple strings
$pdoDB = new PDO('odbc:Driver=ODBC Driver 13 for SQL Server;
Server='.DATABASE_SERVER.';
Database='.DATABASE_NAME,
DATABASE_USERNAME,
DATABASE_PASSWORD
);
$pdoDB->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$values = ['here','are','some','values'];
$sql = "INSERT INTO mytab (something) VALUES (:something)";
$stmt = $pdoDB->prepare($sql);
foreach ($values as $value)
$stmt->execute(['something'=>$value]);
Works : single integer
$values = [42];
$sql = "INSERT INTO mytab (col) VALUES (:col)";
$stmt = $pdoDB->prepare($sql);
foreach ($values as $value)
$stmt->execute(['col'=>$value]);
Does Not Work : multiple integers
$values = [1,3,5,7,11];
$sql = "INSERT INTO mytab (col) VALUES (:col)";
$stmt = $pdoDB->prepare($sql);
foreach ($values as $value)
$stmt->execute(['col'=>$value]);
It actually successfully inserts the first record 1 but fails when it tries to reuse the statement on the next execute.
PHP Fatal error: Uncaught PDOException: SQLSTATE[22018]: Invalid character value for cast specification: 206 [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Operand type clash: text is incompatible with int (SQLExecute[206] at /build/php7.0-lPMnpS/php7.0-7.0.8/ext/pdo_odbc/odbc_stmt.c:260)
I'm connecting from 64-bit Ubuntu 16.04 running PHP 7.0.8 using the Microsoft® ODBC Driver 13 (Preview) for SQL Server®
I have tried wrapping the whole thing in PDO::beginTransaction and PDO::commit
I've also tried using PDOStatement::bindParam but it throws the exact same error.
Works
$values = [1];
$sql = "INSERT INTO mytab (col) VALUES (:col)";
$stmt = $pdoDB->prepare($sql);
foreach ($values as $value){
$stmt->bindParam('col', $value, PDO::PARAM_INT);
$stmt->execute();
}
Does Not Work
$values = [1,2];
$sql = "INSERT INTO mytab (col) VALUES (:col)";
$stmt = $pdoDB->prepare($sql);
foreach ($values as $value){
$stmt->bindParam('col', $value, PDO::PARAM_INT);
$stmt->execute();
}
I think it's interesting to note that I am getting the exact same error as this unanswered question using PHP 5.6.9. However, they are not able to execute even one statement, so I'm wondering if there's been a partial patch considering the exact line throwing the error has moved from odbc_stmt.c:254 to odbc_stmt.c:260
Workaround
If I prepare the statement inside the loop, then it works just fine. But I've read that this is very inefficient and I should be able to reuse the statement. I'm particularly worried about using this with massive datasets. Is this OK? Is there something better that I can do?
$values = [1,3,5,7,9,11];
$sql = "INSERT INTO mytab (col) VALUES (:col)";
foreach ($values as $value){
$stmt = $pdoDB->prepare($sql);
$stmt->execute(['col'=>$value]);
}
In case of prepared statements you have to use bindParam outside of loop, usually.
bindParam is a single step
setting bound variables is a repeatable step (loop)
you have to run execute for each repetition
I guess, something like that would work:
$stmt = $pdoDB->prepare("INSERT INTO mytab (col, key) VALUES (:col, :key)");
// bind params (by reference)
$stmt->bindParams(":col", $col, PDO::PARAM_STR); //bind variable $col
$stmt->bindParams(":key", $key, PDO::PARAM_INT); //bind variable $key
$values = ['here','are','some','values'];
foreach ($values as $i => $value) {
$col = $value; //set col
$key = $i; //set key
$stmt->execute();
}

Mysql pdo different behaviour at 2 servers [duplicate]

This question already has answers here:
php pdo prepare repetitive variables
(2 answers)
Closed 6 years ago.
Why at 1st server such code on update set "00-00-00 00:00:00" and at the 2d set current time
$pdo = new PDO;
$sth = $pdo->prepare("INSERT INTO `tbl_process`
SET `good` = :good, `type` = :type, `pid` = :pid, `time` = :time
ON DUPLICATE KEY UPDATE `time` = :time");
$sth->bindParam(':good', $good);
$sth->bindParam(':type', $type);
$sth->bindParam(':pid', $pid);
$sth->bindParam(':time', $time);
$sth->execute();
if i change code to this (add :time2)- i get the right time in both cases
$pdo = new PDO;
$sth = $pdo->prepare("INSERT INTO `tbl_process`
SET `good` = :good, `type` = :type, `pid` = :pid, `time` = :time
ON DUPLICATE KEY UPDATE `time` = :time2");
$sth->bindParam(':good', $good);
$sth->bindParam(':type', $type);
$sth->bindParam(':pid', $pid);
$sth->bindParam(':time', $time);
$sth->bindParam(':time2', $time);
$sth->execute();
Placeholders must be UNIQUE within a query. You're not allowed to re-use them:
SET `good` = :good, `type` = :type, `pid` = :pid, `time` = :time
^^^^^
ON DUPLICATE KEY UPDATE `time` = :time");
^^^^^
The second query works because you've used a different placeholder name.
PDO has three error handling modes.
PDO::ERRMODE_SILENT acts like mysql_* where you must check each result and then look at $db->errorInfo(); to get the error details.
PDO::ERRMODE_WARNING throws PHP Warnings
PDO::ERRMODE_EXCEPTION throws PDOException. In my opinion this is the mode you should use. It acts very much like or die(mysql_error()); when it isn't caught, but unlike or die() the PDOException can be caught and handled gracefully if you choose to do so.
Getting the Last Insert Id
<?php
$result = mysql_query("INSERT INTO table(firstname, lastname) VALUES('John', 'Doe')") or die("Insert Failed ".mysql_error());
$insert_id = mysql_insert_id();
So far we've only shown simple statements that don't take in any variables. These are simple statements and PDO has the shortcut methods query for SELECT statements and exec for INSERT, UPDATE, DELETE statements. For statements that take in variable parameters, you should use bound parameter methods to execute your queries safely. Consider the following mysql_* code.

How to add geopoint array, via PDO to crate*

I have crateio set up and it's working fine using the PDO class.
I'm, trying to get a set of geopoints into the db using binds.
I have tried foreach but doesn't seem to work, I've tried this - which also doesn't work.
The geopoint column is set to geo_point_array.
$route="[[30.33333, -6.13336],[30.33333, -6.13336]]";
$db = new Database;
$db->Query("insert into geopoints (id, longlat, name) values ('33',?,'pat')");
$db->bind(1, $route);
$db->execute();
How do I add this set of cordinates to the db?
Thanks
GeoPoint is not supported as a native type in Crate's PDO driver yet, however you can use an double ARRAY.
From the Crate documentation:
Columns with the geo_point are represented and inserted using a double
array in the following format: [lon_value, lat_value]
I also strongly recommend to do parameter substitution for the other values.
use Crate\PDO\PDO;
$route = [[30.33333, -6.13336], [30.33333, -6.13336]];
$db = new PDO('crate:...');
$stmt = $db->query("insert into geopoints (id, longlat, name) values (?, ?, ?)");
$stmt->bind(1, 33, PDO::PARAM_INT);
$stmt->bind(2, $route, PDO::PARAM_ARRAY);
$stmt->bind(3, 'pat', PDO::PARAM_STR);
$stmt->execute();
PDO::query returns PDOStatement:
$route="[[30.33333, -6.13336],[30.33333, -6.13336]]";
//If Dateabase is a sublcass of PDO
//$db = new Database;
$db = new PDO(...);
$stmt = $db->query("insert into geopoints (id, longlat, name) values ('33',?,'pat')");
$stmt->bind(1, $route, PDO::PARAM_STR);
$stmt->execute();

Not inserting more than 300 characters using MSSQL query with PHP, using Zend_db_adapter

When I try to insert a string containing more that 300 characters it shows blank page and does not return any PHP error.
I am using ZF 1 - Zend db Adapter with MSSQL server 2012.
$db = Zend_Db_Table::getDefaultAdapter();
$insert = "insert into tbloffer (legal_restrictions) values ('more than 300 character string')";
$stmt = $db->prepare($insert);
$stmt->execute();
my database column datatype is nvarchar(max)
In Place of
$insert = "insert into tbloffer (legal_restrictions) values ('more than 300 character string')";
$stmt = $db->prepare($insert);
$stmt->execute();
Try this one
$data = array('legal_restrictions'=> 'more than 300 character string');
$db->insert('tbloffer',$data);

Inserting date in oracle database using Php

I am trying to insert date in Oracle 10g using php. This is my query:
$dat='1989-10-21';
$did="0011";
$nam="George";
$sql= "insert into table (did, name, date_of_birth) values (:did,:nam, TO_DATE(:dat,’YYYY-MM-DD’))";
$stmt = oci_parse($conn, $sql);
oci_bind_by_name($stmt, ':did', $did);
oci_bind_by_name($stmt, ':nam', $nam);
oci_bind_by_name($stmt, ':dat', $dat);
$result = oci_execute($stmt);
But it is giving me the following error:
oci_execute() [function.oci-execute]: ORA-00911: invalid character in
C:\Apache2.2\htdocs\new2.php on line 14
I have tried running it without binding but its still not working. I checked it on sql plus its working fine. Please help
Maybe you can try to quote the first param when use to_date,at least I use it like this:
$date = '2013-11-11';
$sql = "select t.* from my_table t where create_date>to_date('". $date ."','yyyy-mm-dd hh24:mi:ss')";
Perhaps it can give you some ideas.

Categories