PHP - PDO不将imploded数组作为SELECT查询中IN子句的问号参数

时间:2013-04-07 19:12:21

标签: php pdo parameters implode

我在使用PDO的预准备语句中遇到问号参数问题。我的查询类看起来像这样(目前,我仍在添加数据限制,自定义参数过滤和自动检测所用驱动程序支持的语句等功能):

// SQL query
class Query {
    public $attributes;

    // constructor for this object
    public function __construct() {
        if ($arguments = func_get_args()) {
            $tmp = explode(" ", current($arguments));

            if (in_array(mb_strtoupper(current($tmp)), ["ALTER", "DELETE", "DROP", "INSERT", "SELECT", "TRUNCATE", "UPDATE"], true)) {
                // classify the query type
                $this->attributes["type"] = mb_strtoupper(current($tmp));

                // get the query string
                $this->attributes["query"] = current($arguments);

                // get the query parameters
                if (sizeof($arguments) > 1) {
                    $this->attributes["parameters"] = array_map(function ($input) { return (is_array($input) ? implode(",", $input) : $input); }, array_slice($arguments, 1, sizeof($arguments)));
                }

                return $this;
            }
        }
    }
}

这是执行查询的代码片段:

$parameters = (!empty($this->attributes["queries"][$query]->attributes["parameters"]) ? $this->attributes["queries"][$query]->attributes["parameters"] : null);

if ($query = $this->attributes["link"]->prepare($this->attributes["queries"][$query]->attributes["query"], [\PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY])) {
    if ($query->execute((!empty($parameters) ? $parameters : null))) {
        return $query->fetchAll(\PDO::FETCH_ASSOC);
    }
}

这就是我在测试代码中调用它的方式:

$c1->addQuery("lists/product-range", "SELECT * FROM `oc_product` WHERE `product_id` IN (?);", [28, 29, 30, 46, 47]);

if ($products = $c1->execute("test2")) {
    foreach ($products as $product) {
        print_r($product);
    }
}

我遇到的问题是我只看到第一个产品(这是一个针对vanilla OpenCart安装的测试),其标识为28.正如您在我的代码中所看到的,如果传递的参数是一个数组,它将被自动检测到lambda我在Query类构造函数中已经存在,所以它被呈现为像28,29,30,46,47这样的字符串。

PDO设置中是否缺少参数我缺少?或者我正在做的事情中可能存在一些错误或平台限制?我知道PDO在数组方面可以做些什么限制,这就是为什么我预先破坏所有数组,让它们像一个简单的字符串一样传递。

我在SO中看到的一些程序基本上组成了WHERE product_id IN ({$marks})这样的查询字符串,其中$marks是使用类似str_repeat("?", sizeof($parameters))的程序动态生成的,但不是我正在寻找什么(如果没有其他已知选择,我可以诉诸于此,但它看起来不是一个非常优雅的解决方案)。

我的开发环境包括:Windows 7 x64,PHP 5.4.13(x86,线程安全),Apache 2.4.4(x86)和MySQL 5.6.10 x64。

任何提示都将不胜感激:)

2 个答案:

答案 0 :(得分:2)

Lambda检测数组并从中创建逗号分隔的字符串,并将传递的参数视为字符串,因此查询如下所示:

SELECT * FROM tbl WHERE id IN('1,2,3,4')

'1,2,3,4'是SQL的一个字符串值。

如果您只想要数值,可以省略将它们作为参数添加,只需将它们放在查询中:

$a = [28, 29, 30, 46, 47];
$s = "SELECT * FROM tbl WHERE id IN(".implode(',', array_map('intval', $a)).")";

对于不同的数据类型,您必须根据需要添加任意数量的参数占位符,并分别绑定每个参数。

答案 1 :(得分:2)

?占位符只能替换单个文字。如果希望IN子句接受任意数量的值,则必须为数组的每个可能长度准备一个新查询。

例如,如果要在数组[1, 2]中选择ID,则需要一个类似SELECT * FROM tbl WHERE id IN (?,?)的查询。如果然后传入三项数组,则需要准备SELECT * FROM tbl WHERE id IN (?,?,?)之类的查询,依此类推。

换句话说,在您拥有要绑定到预准备语句的数据之前,您无法确切地知道要构建/创建的查询。

这不是PDO限制,它是准备好的查询在SQL数据库中如何工作的基础。想一想 - 如果你说?但是IN ?代表非标量的东西,那么?在SQL-land中的数据类型是什么?

某些数据库具有数组类型(例如PostgreSQL)。 也许他们可以像IN <array-type>一样解释IN (?,?,...),这样可行。但PDO无法发送接收数组类型的数据(没有PDO::PARAM_ARRAY),因为这是一个不常见且深奥的功能,所以PDO不太可能。

注意这里有一层额外的破碎。当面对条件int_id = '1,2,3,4'时,正常数据库将无法匹配任何内容,因为'1,2,3,4'无法强制转换为整数。但是,MySQL会将其转换为整数1!这就是您查询的原因:

$pstmt = $db->prepare('SELECT * FROM `oc_product` WHERE `product_id` IN (?)');
$pstmt->execute(array('28,29,30,46,47'));

将匹配product_id = 28。看到疯狂:

mysql> SELECT CAST('28,29,30,46,47' AS SIGNED INTEGER);
+------------------------------------------+
| CAST('28,29,30,46,47' AS SIGNED INTEGER) |
+------------------------------------------+
| 28                                       |
+------------------------------------------+
1 rows in set (0.02 sec)