MySQL中的空IN子句参数列表
Posted
技术标签:
【中文标题】MySQL中的空IN子句参数列表【英文标题】:Empty IN clause parameter list in MySQL 【发布时间】:2012-10-23 23:56:36 【问题描述】:当您在IN
子句为空的情况下执行 SQL 查询时会发生什么?
例如:
SELECT user WHERE id IN ();
mysql 会按预期处理这种情况(即始终为假),如果不是,我的应用程序在动态构建 IN
子句时如何处理这种情况?
【问题讨论】:
id 列值显然不在空集中,因为空集中没有任何内容。此外,事实证明这是不正确的语法。 sqlfiddle.com/#!2/a2581/2412 如果您“构建”(或确定)一个空的IN
子句,请不要执行查询。嘘!
@LoztInSpace 即使有 OR 条件,单个查询也很有用,而不是用另一种语言编写查询构建器逻辑。因此,令人讨厌的是 IN 不处理空列表。 (:ids is null or id in :ids) 会很棒,而不是在 :ids 为 null 时使用另一种语言省略 IN 部分。评估为 false 会很好,而不是抛出语法错误。
【参考方案1】:
如果我有一个动态构建IN
列表的应用程序,并且它可能最终为空,我有时会使用不可能的值初始化列表并添加到该列表中。例如。如果它是用户名列表,我将从一个空字符串开始,因为这不是可能的用户名。如果是 auto_increment ID,我会使用 -1,因为实际值总是正数。
如果这不可行,因为没有不可能的值,您必须使用条件来决定是否在 WHERE
子句中包含 AND column IN ($values)
表达式。
【讨论】:
有人在这里给我们一些 SQL 推理吗?正如 OP 所说,直观的行为是始终评估为 false。 这只是一个语法问题——他们不想允许空列表。 如果column IN (1, 2, 3)
等价于column=1 OR column=2 OR column=3
,那么空列表自然会等价于零个OR
项。但是,如果您尝试将其与其他布尔表达式结合使用,则会导致奇怪的规则。例如,<expr> AND column IN (1,2,3)
是否应该映射到 <expr> AND (true)
?但这似乎是错误的,如果直觉上你会说column IN ()
应该是false。【参考方案2】:
这也给了我 0 个结果:
SELECT id FROM User
WHERE id IN (NULL);
在 MYSQL 5.6 上试过
【讨论】:
这可能有问题,因为id IN (NULL)
的计算结果为NULL
而不是FALSE
。这主要是NOT IN
的问题:SELECT id FROM User WHERE id NOT IN (NULL);
也将返回 0 个结果。
非常有趣!遗憾的是,(NULL)不是一组空值。【参考方案3】:
使用有效语法的最接近此查询的近似值是:
SELECT user FROM tbl1 WHERE id IN (SELECT id FROM tbl1 WHERE FALSE);
无条件返回一个空的结果集。括号中的子查询总是返回一个空集,并且在空集中找不到值,因为一个空集不包含任何值。
【讨论】:
另一个返回空集的子查询是(select top 0 0)
,它的优点是不需要表名。
这在您动态生成文字 IN
列表的情况下并没有真正的帮助。他不是在寻找一种方法来产生一个空洞的IN
子句,他想知道当它意外发生时如何处理它,因为他给了一个空值数组。
或者干脆SELECT user FROM tbl1 WHERE id IN (FALSE)
@NicolaPedretti 假设 id
不是布尔字段,这可能是一个合理的假设,但仍然是一个假设。
该问题指定了 MySQL,SELECT TOP N
不是 MySQL。另一方面,(SELECT 0 WHERE 0)
有效。但是,在 MySQL 5.7 中执行IN (SELECT ...)
似乎会创建一个依赖子查询(如explain
所示),并且生成的查询非常慢。【参考方案4】:
使用总是错误的陈述
在创建 SQL 之前,请检查数组大小。
如果size为0,生成where语句为1 = 2
,如:
SELECT * FROM USERS WHERE name in ()
变成
SELECT * FROM USERS WHERE 1 = 2
对于空数组上的“不在”,生成一个始终为真的语句,如下所示:
SELECT * FROM USERS WHERE name not in ()
变成
SELECT * FROM USERS WHERE 1 = 1
这应该适用于更复杂的查询:
SELECT * FROM USERS WHERE (name in () OR name = 'Alice')
变成
SELECT * FROM USERS WHERE (1 = 2 OR name = 'Alice')
【讨论】:
这真是一个很棒的解决方案。我刚刚将它实现到我的 php 框架中。谢谢! 好吧,在这种情况下,我实际上可以只使用WHERE true
和WHERE false
,或者我错过了什么。但是,在动态构建查询时,这两种方法都不理想。【参考方案5】:
如果您对 id (1,2,3, ..) 使用 AUTO_INCREMENT 并且如果数组为空,则可以添加一项 [0]。所以会是
if (empty($arr))
$arr[] = 0;
SELECT user WHERE id IN (0);
并且不会出现mysql解析错误。这种情况在子查询中非常有用 - 当您的主查询不依赖于子查询结果时。
更好的方法 - 如果数组为空,则不要调用查询。
$data = null;
if (!empty($arr))
$data = ... call query
【讨论】:
【参考方案6】:您不能将 IN 运算符留空,您必须在其中输入一些内容,例如 NULL
。但即使在这种情况下,它也不会按预期工作,因为与NULL
比较总是返回NULL
(空)。一种可能的解决方案是添加子选择。
例子:
SELECT user_id FROM users;
+-----------+
| user_id |
+-----------+
| 1000 |
| 1001 |
| 1002 |
| 1003 |
| 1004 |
| 1005 |
| 1006 |
| 1007 |
| 1008 |
| 1009 |
| 1010 |
+-----------+
SELECT user_id FROM users WHERE user_id NOT IN ();
ERROR: 1064: You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near ')' at line 1
SELECT user_id FROM users WHERE user_id NOT IN (NULL);
Empty set (0.0003 sec)
SELECT user_id FROM users WHERE user_id IN (1001, 1002, 1003)
AND user_id NOT IN (SELECT user_id FROM users WHERE user_id IN (NULL));
+---------+
| user_id |
+---------+
| 1001 |
| 1002 |
| 1003 |
+---------+
3 rows in set (0.0004 sec)
您可以稍微修改(缩短)查询,但我认为,它们也会慢一点。
【讨论】:
【参考方案7】:这里是空列表的 always false 语句的另一种变体,它保留了查询中的逻辑和实际列的概念。
不正确的id in ()
可以改写成:
where id <> id;
同样不正确的否定条件id not in ()
可以通过自定义查询构建代码转换为:
where id = id;
这种方法比 id not in (NULL)
更安全,因为它不会评估为 NULL。
但请注意,这会从结果中过滤掉id is null
所在的行。在某些情况下,这可能被视为一项功能。
尤其适用于复杂的堆叠查询构建逻辑,其中嵌套构建器不知道如何在上面使用生成的子查询。
【讨论】:
【参考方案8】:我假设当您的IN
为空时,您仍需要运行查询;例如LEFT JOIN ... ON ... AND IN ()
。
由于您已经在应用层动态构建IN
,我将在那里处理空的情况。
这是一个 PHP 的基本示例
$condition = 'FALSE' // Could feasibly want TRUE under different circumstances
if($values)
$inParams = **dynamically generated IN parameters from $values**;
$condition = 'IN (' . $inParams . ')';
$sql = 'SELECT * FROM `user` WHERE '.$condition;
如果您不需要使用空的IN
运行查询,则不要;省去一次数据库之旅!
NB 如果您还没有,我会构建/使用一个可以长期使用并正确绑定您的 IN
参数的函数,而不仅仅是将它们原始连接到SQL。如果将 SQL 注入用于原始数据,这将为您提供一些保护。
【讨论】:
【参考方案9】:一种处理方法:
SELECT user WHERE id in (SELECT 1 WHERE 1!=1)
可能需要将 1 替换为 @JamesHoux 的评论中提到的适当数据类型的值。 这里的一个假设是用户的 id 是一个 int。
【讨论】:
建议调整:(SELECT NULL WHERE 1!=1)。使用 NULL 将创建一个与任何类型的列相当的空类型不可知集。如果您使用像数字 1 这样的文字,则会创建一个空的整数集。如果无法将类型“整数”与所需的列类型进行比较,数据库引擎可能会出错。 这很聪明! 不幸的是,我似乎对这种方法的可靠性有误。我在 C# 中将它与 NpgSQL 一起使用,并且 NpgsqlCommand.Prepare() 方法抛出一个异常,指出它无法将“text”与“bigint”进行比较。在这种情况下,将 bigint 与空数组进行比较。显然,空数组被创建为“文本”类型。 :( 我想没有灵丹妙药。 好的,很高兴知道。 SELECT 1 会为您解决吗? 哦,是的,绝对是。将 bigint 与 1 进行比较。我必须编写一个类型检查函数来确定 db 值类型以生成具有正确占位符值的 SELECT 语句。不幸的是,该 SELECT 语句不是类型不可知的,但除此之外,它仍然是一个非常好的方法,带有额外的类型处理逻辑。【参考方案10】:如果您在应用程序中使用该查询,并且您将对象列表动态传递给查询,我不应该调用数据库来执行具有不可能值的选择,我应该返回一个空列表而不调用数据库查询,直接。
因为在调用之前执行您知道为空的查询是没有意义的。
【讨论】:
避免琐碎的空查询可能是过早的优化。以上是关于MySQL中的空IN子句参数列表的主要内容,如果未能解决你的问题,请参考以下文章
在 Visual Studio 2008 中使用设计器将逗号分隔列表作为参数传递给 db2 查询的 IN 子句