mysql中or的隐藏语义

124 阅读3分钟

先说背景,重构项目,发现有如下sql

select
  id,
  name,
  company,
  SUM(
    if(
      firstTimes > 0
      and firstUploadYmd = #{ymd}
      or secondTimes > 0
      and secondUploadYmd = #{ymd}
      or thirdTimes > 0
      and thirdUploadYmd = #{ymd}
      or forthTimes > 0
      and forthUploadYmd = #{ymd},
      1,0)
  ) as num
from d_scan;

咋一看这个sql怎么这个样子啊,写的太乱了吧!但是仔细揣摩一下,还是可以看出来这个sql要表达的语义。

查询符合以下条件的数据:

第1次次数大于0且第1次上传时间是ymd
或
第2次次数大于0且第2次上传时间是ymd
或
第3次次数大于0且第3次上传时间是ymd
或
第4次次数大于0且第4次上传时间是ymd

但是这个sql语句这样写真的没问题吗?难道不应该这样写吗?

select
  id,
  name,
  company,
  SUM(
    if(
      (firstTimes > 0 and firstUploadYmd = #{ymd})
      or 
      (secondTimes > 0 and secondUploadYmd = #{ymd})
      or 
      (thirdTimes > 0 and thirdUploadYmd = #{ymd})
      or 
      (forthTimes > 0 and forthUploadYmd = #{ymd}),
      1,0)
  ) as num
from d_scan;

最后将sql改成上面这样,发现2个sql语句的执行结果是一样的。

带着这样的疑问,执行了以下sql语句,顿时恍然大悟。

SELECT 1>2 //返回0
SELECT 1>2 AND 1>2  //返回0
SELECT 1>2 AND 1>2 OR 2>1  //返回1

此时的结果符合预期。

SELECT 1>2 AND 1>2 OR 2>1 AND 1>2  //返回0

等等,这里为什么返回的是0?

1>2 AND 1>2 返回的是0, 1>2 AND 1>2 OR 2>1返回的是1, 1>2 AND 1>2 OR 2>1 AND 1>2返回的是0

那就是说最后1个 AND 1>2影响了最终的结果。

相当于整个sql语句每执行一次判断,返回的结果0或者1继续和下面的判断做与或操作。

0 and 0 返回0

0 and 1 返回0

1 and 0 返回0

1 and 1 返回1

0 or 0 返回0

0 or 1 返回1

1 or 0 返回1

1 or 1 返回1

个人是这么理解的:返回0的部分可以直接丢弃,返回1的部分保留。

最后执行的就是OR后面的部分 SELECT 2>1 AND 1>2

那么是否可以理解为OR后面的语句都自带括号,直到遇到下一个OR

SELECT 1>2 AND 1>2 OR 2>1 AND 1>2 等价于 SELECT 1>2 AND 1>2 OR (2>1 AND 1>2)

如果这么解释的话,上面的2个sql语句为什么执行结果一样,也就解释的通了。

虽然明白了其中的原理,但是从代码可读性以及可维护性的角度出发,最好不要这样写sql,能加括号的还是加上吧,避免出现歧义误导人。

SELECT 2>1 AND 2>1 OR 1>2 and 1>2 OR 2>3 and 2>3

这个sql语句等价于

SELECT 2>1 AND 2>1 OR (1>2 and 1>2) OR (2>3 and 2>3)

2>1 AND 2>1 返回1,后续的OR不需要执行直接返回1即可。

最后验证下学习成果,下面的sql返回0还是1?

SELECT 1>2 AND 1>2 OR 2>1 AND 1>2 OR 2>1 AND 2>1 AND 2>1 AND 2>3

等价于

SELECT 1>2 AND 1>2 OR (2>1 AND 1>2) OR (2>1 AND 2>1 AND 2>1 AND 2>3)

最后返回0。