DBD::SQLite HAVING 子句中整数值的占位符

DBD::SQLite Placeholder for integer value in HAVING clause

因为使用 DBD::mysql seem to work fine, where is my mistake in using a placeholder for an integer in a HAVING clause, when using DBD::SQLite 作为 DBI 驱动程序的类似语句?

#!/usr/bin/perl

use 5.012;
use warnings;

use DBI;

my $dbh = DBI->connect("dbi:SQLite:dbname=/tmp/test.sqlite","","", {
    RaiseError => 1,
    sqlite_allow_multiple_statements => 1,
});

$dbh->do( <<'EOT' );
CREATE TABLE cd (
    id    INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT
);

CREATE TABLE artist (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    cd   INTEGER,
    FOREIGN KEY (cd) REFERENCES cd (id)
);

INSERT INTO cd (title) VALUES ('foo');
INSERT INTO cd (title) VALUES ('bar');

INSERT INTO artist (name, cd) VALUES ('max fob', 1);
INSERT INTO artist (name, cd) VALUES ('max baz', 1);
EOT

my $sth1 = $dbh->prepare(<<'EOT');
   SELECT cd.title
     FROM cd
LEFT JOIN artist ON artist.cd = cd.id
    WHERE artist.name LIKE ?
 GROUP BY cd.title
   HAVING count( artist.cd ) = 2
EOT

my $sth2 = $dbh->prepare(<<'EOT');
   SELECT cd.title
     FROM cd
LEFT JOIN artist ON artist.cd = cd.id
    WHERE artist.name LIKE ?
 GROUP BY cd.title
   HAVING count( artist.cd ) = ?
EOT

$sth1->execute('max%');
# says 'hit'
say 'sth1: hit' if $sth1->fetch;

$sth2->execute('max%', 2);
# stays silent
say 'sth2: hit' if $sth2->fetch;

谢谢你,DDL。

我不知道为什么占位符被视为字符串[1],但您可以通过使 SQLite 将值视为整数来使代码正常工作。以下两种解决方案都是实现此目的的方法:

my $sth2 = $dbh->prepare(<<'EOT');
   SELECT cd.title
     FROM cd
LEFT JOIN artist ON artist.cd = cd.id
    WHERE artist.name LIKE ?
 GROUP BY cd.title
   HAVING count( artist.cd ) = ?
EOT

$sth2->bind_param(1, 'max%');
$sth2->bind_param(2, 2, DBI::SQL_INTEGER);
$sth2->execute();
my $sth2 = $dbh->prepare(<<'EOT');
   SELECT cd.title
     FROM cd
LEFT JOIN artist ON artist.cd = cd.id
    WHERE artist.name LIKE ?
 GROUP BY cd.title
   HAVING count( artist.cd ) = CAST( ? AS INTEGER )
EOT

$sth2->execute('max%', 2);

  1. 另一个 声称占位符总是被视为字符串。这不仅不是解释,甚至不是事实。他们的这种无根据的声明显然是错误的,因为 bind_param 可能导致占位符被视为字符串以外的其他内容。

A ? 占位符总是在准备好的语句中替换为字符串(如 '2'),对于 SQLite no affinity.

Datatypes In SQLite/Affinity Of Expressions 推导出像 count(artist.cd) 这样的表达式,尽管它被记录为 returns 一个整数, 没有亲和力 .

此外,从 Datatypes In SQLite/Type Conversions Prior To Comparison 推导出 2 个没有亲和力的操作数在没有任何隐式数据类型转换的情况下进行比较。

对于 SQLite 表达式 2 = '2' returns false.

这就是为什么您必须对 ? 占位符应用数据类型转换以将其转换为数值,您可以这样做:

HAVING count( artist.cd ) = ? + 0

此外,与您的问题无关,查询中的 WHERE 子句使 LEFT 连接的行为类似于 INNER 连接,因为它应用于 table 并过滤掉任何不匹配的行。