MySQL中的链式比较得到了意想不到的结果

时间:2015-03-09 22:31:58

标签: mysql sql database

我遇到了一个MySQL语句的问题,它一直在返回null,虽然我能够弄明白,但是这个原因让我有些困惑。

这是有问题的查询的简化版本:

SELECT id FROM users WHERE id = id = 2;

错误发生是因为id =重复,删除其中一个id =解决了问题(因为有一个id为2的用户)。但令我有点困惑的是它如何“无声地失败”,返回0行,而不是给出错误。

我在SQL Server上测试了一个类似的查询,并得到了错误消息Incorrect syntax near '='(类似于我对MySQL的期望)。

最初,我认为MySQL正在前两个字段之间进行比较,最后一个是比较的结果(0表示false,1表示true)。像这样:

SELECT id FROM users WHERE (id = id) = 2;

但后来我发了一些与之相矛盾的问题。让我详细说明一下。

想象一下这个表(称为“用户”):

id  | username
----|----------------
0   | anonymous
1   | root
2   | john
3   | doe

如果我SELECT * FROM users WHERE id = id = 1,我会收到所有4位用户。而SELECT * FROM users WHERE id = id = 0,我没有得到任何。这似乎证实了比较理论。但是,如果我做这样的事情会让事情变得混乱:

SELECT * FROM users WHERE id = username = 1;
SELECT * FROM users WHERE id = username = 0;

没有任何记录与用户名具有相同的ID(它们甚至不是同一类型:int(11)varchar(25)),但是对于第一个,我得到一个结果:“匿名”。而第二个,我得到所有用户,但“匿名”。为什么会这样?我发现它与id为0有关,因为如果我将“匿名”id替换为0到4,那么我不会再用第一个查询得到它(并且它显示在第二个)。

我想这与 MySQL在与数字比较时将字符串/ varchars转换为0有关。但为什么它允许在同一条款中进行链式比较?比较它们时会遵循什么顺序?

当这样的查询有效并且实际返回(意外的?)值时,

搞笑。例如:

SELECT * FROM users WHERE id = id = id = username = username = id = username = 1;

返回id为2和3的记录,但没有id为0和1的记录。为什么会这样?


tl; dr version:为什么带链式比较操作的查询有效?比较中遵循的顺序是什么?这是一个错误还是预期的行为?

3 个答案:

答案 0 :(得分:6)

首先,感谢您提出这个有趣的问题。我喜欢玩这个。让我们开始回答:

了解其工作原理的先决条件

了解SELECT-WHERE查询

这很明显,但我正在为每个程序员做出这个答案,即使你真的不知道SQL是如何工作的。一个SELECT-WHERE查询,基本上是:

  1. 循环受查询影响的每一行
  2. 使用该特定行的值评估where_condition
  3. 如果where_condition结果为TRUE,则会列出。{/ li>

    伪代码可以是:

    for every row:
        current values = row values # for example: username = 'anonymous'
        if (where_condition = TRUE)
            selected_rows.append(this row)
    

    几乎每个字符串都等于0(或FALSE)

    除非字符串是数字('1''423''-42')或字符串以数字开头,否则每个其他字符串等于0(或{{1 }})。 “数字字符串”等于其等效数字,“起始数字字符串”等于其初始数字。提供一些例子: MySQL的> SELECT'a'= 0;

    FALSE

    +---------+
    | 'a' = 0 |
    +---------+
    |       1 |
    +---------+
    1 row in set, 1 warning (0.00 sec)
    

    mysql> SELECT 'john' = 0;
    +------------+
    | 'john' = 0 |
    +------------+
    |          1 |
    +------------+
    1 row in set, 1 warning (0.00 sec)
    

    mysql> SELECT '123' = 123;
    +-------------+
    | '123' = 123 |
    +-------------+
    |           1 |
    +-------------+
    1 row in set (0.00 sec)
    

    WHERE_condition被解析为数学运算

    逐个解析链式比较,首先是括号的唯一偏好,从左侧开始直到mysql> SELECT '12a5' = 12; +-------------+ | '12a5' = 12 | +-------------+ | 1 | +-------------+ 1 row in set, 1 warning (0.00 sec) TRUE为剩余。

    因此,例如FALSE将被跟踪如下:

    1 = 1 = 0 = 0

    工作原理

    我将追踪最后一个查询,我认为这是最复杂但最美丽的解释:

    1 = 1 = 0 = 0
     [1]  = 0 = 0
         [0]  = 0
             [1]
    Final result: 1 -> TRUE
    

    首先,我将向每个SELECT * FROM users WHERE id = id = id = username = username = id = username = 1; 显示每个行变量:

    where_condition

    id = id = id = username = username = id = username = 1

    现在我将追踪每一行:

    0 = 0 = 0 = 'anonymous' = 'anonymous' = 0 = 'anonymous' = 1
    1 = 1 = 1 = 'root'      = 'root'      = 1 = 'root'      = 1
    2 = 2 = 3 = 'john'      = 'john'      = 2 = 'john'      = 1
    3 = 3 = 3 = 'doe'       = 'doe'       = 3 = 'doe'       = 1
    

    因此,0 = 0 = 0 = 'anonymous' = 'anonymous' = 0 = 'anonymous' = 1 [1] = 0 = 'anonymous' = 'anonymous' = 0 = 'anonymous' = 1 [0] = 'anonymous' = 'anonymous' = 0 = 'anonymous' = 1 [1] = 'anonymous' = 0 = 'anonymous' = 1 [0] = 0 = 'anonymous' = 1 [1] = 'anonymous' = 1 [0] = 1 [0] -> no match 1 = 1 = 1 = 'root' = 'root' = 1 = 'root' = 1 [1] = 1 = 'root' = 'root' = 1 = 'root' = 1 [1] = 'root' = 'root' = 1 = 'root' = 1 [0] = 'root' = 1 = 'root' = 1 [1] = 1 = 'root' = 1 [1] = 'root' = 1 [0] = 1 [0] -> no match 2 = 2 = 3 = 'john' = 'john' = 2 = 'john' = 1 [1] = 3 = 'john' = 'john' = 2 = 'john' = 1 [0] = 'john' = 'john' = 2 = 'john' = 1 [1] = 'john' = 2 = 'john' = 1 [0] = 2 = 'john' = 1 [0] = 'john' = 1 [1] = 1 [1] -> match 3 = 3 = 3 = 'doe' = 'doe' = 3 = 'doe' = 1 [1] = 3 = 'doe' = 'doe' = 3 = 'doe' = 1 [0] = 'doe' = 'doe' = 3 = 'doe' = 1 [1] = 'doe' = 3 = 'doe' = 1 [0] = 3 = 'doe' = 1 [0] = 'doe' = 1 [1] = 1 [1] -> match id2的行与3匹配,这就是查询显示的行。

答案 1 :(得分:1)

我根本不知道MYSQL,但怀疑你遇到了一个副作用,即where子句的每个谓词都必须求值为true。 E.g。

Select *
from table 
where 1 = 1

实际上在说

Select *
from table 
where is_true(1 = 1)

我建议你尝试除1和0之外的其他值,看看你得到了什么结果。

显然(id = id)为真所以在使用0和1表示false / true时这等于1

经过一些实验后,Alvaro Montoro对此进行了补充 (int == int)= true; 但对于int> 1:(int == true)= false,(int == false)= false。

答案 2 :(得分:1)

此查询:

SELECT *
FROM users
WHERE id = username = 1;

对于" anonymous"会返回true,如果它被解析为,则返回0:

SELECT *
FROM users
WHERE id = (username = 1);

id为0,因此它表示" false =" false"。

请注意,这些是任何类型的链式比较。 MySQL正在将比较转换为整数(0和1),然后应用=。因此,1 = 1 = 1为真,但0 = 0 = 0为假。所以,这是二元逻辑和相等比较的奇怪组合。

我的建议是不要在表达式中使用多个比较。忘记MySQL支持这一事实 - 它不是标准的一部分,大多数数据库都不会这样做。