在PHP和PostgreSQL中使用带间隔的命名占位符失败

Ste*_*ber 5 postgresql pdo prepared-statement

好的,我已经确认可以在PHP上正常使用。

$ php --version
PHP 5.6.16 (cli) (built: Dec 30 2015 15:09:50) (DEBUG)

<pdo version>
pdo_pgsql
PDO Driver for PostgreSQL   enabled
PostgreSQL(libpq) Version   9.4.0
Module version  1.0.2
Revision    $Id: fe003f8ab9041c47e97784d215c2488c4bda724d $
Run Code Online (Sandbox Code Playgroud)

我想使用PDO在PHP中重新创建以下SQL:

UPDATE relationships SET status = 4 WHERE created > NOW() - interval '2 seconds';
Run Code Online (Sandbox Code Playgroud)

该脚本正在工作:

<?php

$db = new PDO('pgsql:dbname=db;host=localhost;user=stevetauber');
$stmt = $db->prepare("UPDATE relationships SET status = 4 WHERE created > NOW() - interval '?'");
$stmt->execute(array("2 seconds"));
Run Code Online (Sandbox Code Playgroud)

这里是命名占位符:

<?php

$db = new PDO('pgsql:dbname=db;host=localhost;user=stevetauber');
$stmt = $db->prepare("UPDATE relationships SET status = 4 WHERE created > NOW() - interval ':blah'");
$stmt->execute(array(":blah" => "2 seconds"));
Run Code Online (Sandbox Code Playgroud)

出现此错误:

Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: :blah in ... line 5
Run Code Online (Sandbox Code Playgroud)

现在根据PHP文档

Example#6无效使用占位符:

 <?php
 $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
 $stmt->execute(array($_GET['name']));

 // placeholder must be used in the place of the whole value
 $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
 $stmt->execute(array("%$_GET[name]%"));
 ?>
Run Code Online (Sandbox Code Playgroud)

这是更新的代码:

<?php

$db = new PDO('pgsql:dbname=db;host=localhost;user=stevetauber');
$stmt = $db->prepare("UPDATE relationships SET status = 4 WHERE created > NOW() - :blah");
$stmt->execute(array(":blah" => "interval '2 seconds'"));
Run Code Online (Sandbox Code Playgroud)

产生以下数据库错误(无脚本错误):

ERROR:  operator does not exist: timestamp with time zone > interval at character 51
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
STATEMENT:  UPDATE relationships SET status = 4 WHERE created > NOW() - $1
Run Code Online (Sandbox Code Playgroud)

PDO在这里做一些奇怪的事情,因为:

# select NOW() - interval '2 seconds' as a , pg_typeof(NOW() - interval '2 seconds') as b;
               a               |            b             
-------------------------------+--------------------------
 2015-12-30 18:02:20.956453+00 | timestamp with time zone
(1 row)
Run Code Online (Sandbox Code Playgroud)

那么如何在PostgreSQL和interval中使用命名占位符?

Dan*_*ité 5

占位符用于纯值,而不是用单位(或其他任何东西)装饰的值。

interval '2 seconds'在占位符中表达,有两种选择:

  • 在查询中,写入:secs * interval '1 second' 并绑定:secs到 php 中的数字

  • 或写:cast(:mystring as interval),并绑定:mystring到字符串'2 seconds'。它将通过显式转换动态解释。


当尝试使用 psql 命令行客户端与 PDO 驱动程序进行比较时,使用带有 postgres 本地占位符的PREPAREEXECUTESQL 语句$N,而不是将参数值按字面意思写入查询中。这将匹配 PHP 驱动程序在PDO::ATTR_EMULATE_PREPARES设置为 false时所做的事情。

在您问题的最后一部分中,在 psql 中尝试此操作时(您的查询,只是简化为不需要表):

select now() > now() - interval '2 seconds';
Run Code Online (Sandbox Code Playgroud)

它确实有效并返回“t”(真)。

但如果你尝试过:

prepare p as select now() > now() - $1;
Run Code Online (Sandbox Code Playgroud)

如果会失败

错误:运算符不存在:时间戳与时区 > 间隔

这与 PDO 的准备/执行错误相同。

另一方面,这确实有效:

=> prepare p as select now() > now() - interval '1 second'*$1;
PREPARE

=> execute p(2);
?column? 
----------
t
Run Code Online (Sandbox Code Playgroud)