【问题标题】:Empty IN clause parameter list in MySQLMySQL中的空IN子句参数列表
【发布时间】: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 会很好,而不是抛出语法错误。

标签: mysql sql in-clause


【解决方案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】:

          如果您在应用程序中使用该查询,并且您将对象列表动态传递给查询,我不应该调用数据库来执行具有不可能值的选择,我应该返回一个空列表而不调用数据库查询,直接。

          因为在调用之前执行您知道为空的查询是没有意义的。

          【讨论】:

          • 避免琐碎的空查询可能是过早的优化。
          猜你喜欢
          • 2021-05-11
          • 2017-06-07
          • 2016-06-17
          • 2017-11-26
          • 1970-01-01
          • 2016-07-19
          • 2013-08-14
          • 2011-03-07
          相关资源
          最近更新 更多