

各位看官老爷大家早上好,我是你们的老㲻计小刘。起今天推文的题目可是难坏了我,琢磨来琢磨去也没想出精练准确的标题,唉,没文化是硬伤啊今天的题目源自前几天,展老师说要出题考考我,他道茴字有四种写法,问我可知道?他说题目是这样的,在一张表里有个状态列,该列的值有多种可能,且会动态的变化。其中有一个状态 S 是受到关注的,现在想要知道的事情是,用一个 SQL 得出表里的数据到底都是 S 状态的,还是既有 S 也有其它状态的,还是没有 S 状态的?
这个需求在小刘看来是一个组合型的需求,比如较此更为简单的有:如何甄别表里是否有 S 状态的数据高明的老爷们看到这个问题,都会想到用 COUNT,细心的老爷还会加上一句用 ROWNUM = 1 可以优化效率SELECT COUNT(1)
FROM demo_transactions
WHERE status = 'S'
AND rownum = 1;
但是结果为 1 的时候,其实还说不明白到底表里都是 S 状态的,还是说也有别的状态的
展老师出的考题,就是不满足于上面这种简单的判断,表里是不是全都是 S 状态的数据对他来说很重要当然想看看表里有没有非 S 状态的数据,只消把上面的 SQL 改成 status != 'S' 罢了可展老师说的是用一个 SQL 来统计,他还强调这很重要,因为这个 SQL 将来可能只是一个更大的 SQL 里面的一个 DECODE
然而勇敢小刘,不怕困难!两个 SELECT 合成一个,还不算难事:UNION ALL 起来,外面再 SUM 一下便是如此这两个 SQL 也不能用 COUNT 了,因为 SUM 出来是 1,判断不出来是哪个的 1这里我们回头想一下为什么用 COUNT,其实是基于 PL/SQL 块的一个默认背景了,用聚合函数不会发生 NO_DATA_FOUND 报错;如今我们本来就是放在 SQL 里面用,所以可以去掉 COUNT,取而代之的是两个“写死”的数,以便 SUM 出来的要么是它俩的和,要么是其中一个值,便于判断SELECT nvl(SUM(c), 0) c
FROM (SELECT 1 c
FROM demo_transactions
WHERE status = 'S'
AND rownum = 1
UNION ALL
SELECT 2 c
FROM demo_transactions
WHERE status != 'S'
AND rownum = 1);
老爷们请看这个 SQL 是不是就很妙,当结果为 0,那说明表是空的;1 说明表里全是 S 状态的;2 说明表里全不是 S 状态的;3 说明表里既有 S 状态的,也有别的状态的
展老师看了这个答案,甚是欣慰,感觉小刘是个可教之才,于是他趁兴又问我,如果再加一个关注的状态呢,比如 NSELECT nvl(SUM(c), 0) c
FROM (SELECT 1 c
FROM demo_transactions
WHERE status = 'S'
AND rownum = 1
UNION ALL
SELECT 2 c
FROM demo_transactions
WHERE status = 'N'
AND rownum = 1
UNION ALL
SELECT 4 c
FROM demo_transactions
WHERE status NOT IN ('S', 'N')
AND rownum = 1);

大家注意,这次我用的组合是 1、2、4,为什么不是 1、2、3 呢?因为 1 加 2 等于 3,和只有 3 的情况分不开了小刘既然发推吹牛了,原因自然不会这么简单。其实这里面蕴含着深刻的道理,这个道理允许我们不断地增加更多关注的状态值1、2、4 分别是 2 的 0 次方、1 次方和 2 次方,也就是说它们分别是二进制数字个、十、百位转十进制的结果那么将来有了更多想要关注的状态后,每个状态 UNION ALL 的 SELECT 就是 1、2、4、8...最后那个 NOT IN 这些状态的 SELECT 就是前一个数再乘 2这样做就能避免某几个数的和不正好等于一个更大的数了吗?答案是肯定的,因为该公式可由归纳演绎法推理,但其实回归到二进制也可以逆向解释一波:二进制里每加一就进位,2 的 n 次方正是 n + 1 位,它本来要在前面的位都是 1 时,再加一才进位到它
那么问题又来了,就不能是某两个数的和等于另几个数的和吗,比如 A + B = C + D,C 比 A 小点,D 比 B 大点回归到二进制来说,我们做的其实是二进制转十进制的工作,任何一个子查询的结果都影响的是一个数位的 0 或 1,最终肯定不会造成重复,请大家放心使用
感谢@风语供稿,改编自真实事件,为文章效果有艺术创作成分
