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 trueWHERE 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子句参数列表的主要内容,如果未能解决你的问题,请参考以下文章

MySql IN 子句,试图匹配元组的 IN 列表

在 Visual Studio 2008 中使用设计器将逗号分隔列表作为参数传递给 db2 查询的 IN 子句

如何在选择语句的“NOT IN”子句中使用逗号分隔的字符串列表作为 pl/sql 存储的函数参数

如何在 Django 中为“in”SQL 子句传递值列表

JPA将列表传递给命名本机查询中的IN子句

IN子句中的MySQL多列