SQL 找出两个表中未出现的最小数值(三种方法)
2025-03-18 08:50:29
找出两个 SQL 表中未出现的最小数值
咱今天要聊的是一个数据库查询的难题:如何从两个表里找出没有出现的最小数值。举个例子,表 1 里有 1、2、3、8,表 2 里有 4、10、11,咱想找出 5 这个数,因为它是两个表中都没出现的最小正整数。
一、问题难在哪儿?
直接想可能有点绕,你会发现简单的查询只能搞定单个表。 要想找出两个表里共同缺失的最小数字,就有点棘手了。 初始的代码虽然尝试找到缺失值,却无法保证它是两个表里都没出现的最小数,它找的是一个表里没出现而可能出现在另一表的数字。
二、解决思路:合、并、查!
咱的核心思路就三个字:合、并、查!
- 合: 把两个表的数据先合并到一起。
- 并: 把合并后的数据整理成一个包含所有可能缺失数值的序列。
- 查: 从这个序列里找出最小的、且不在两个表中出现的那个数。
三、实战演练:三种方法
下面,咱就一步步来实现这个思路,而且提供三种不同的方法,各有千秋。
1. 方法一:UNION ALL
+ GENERATE_SERIES
(PostgreSQL)
这种方法利用了PostgreSQL的 GENERATE_SERIES
函数,它可以生成一个数字序列,非常适合咱们的需求。
原理:
UNION ALL
:把两个表的目标列合并成一个结果集,不去重。GENERATE_SERIES(start, stop)
: 产生从start
到stop
的一系列整数。LEFT JOIN
:找到序列中缺失的数字。MIN()
:返回最小缺失值。
代码:
WITH CombinedValues AS (
SELECT columna AS value FROM Table1
UNION ALL
SELECT columnb AS value FROM Table2
),
MaxValues AS (
SELECT GREATEST((SELECT MAX(columna) FROM Table1),(SELECT MAX(columnb) FROM Table2)) as max_value
),
Series AS (
SELECT generate_series(1, (SELECT max_value from MaxValues)) AS num
)
SELECT MIN(s.num)
FROM Series s
LEFT JOIN CombinedValues cv ON s.num = cv.value
WHERE cv.value IS NULL;
解释:
CombinedValues
CTE:将Table1
的columna
和Table2
的columnb
合并。MaxValues
CTE:找出两个表中各自的最大值。Series
CTE: 通过两个表中找出的各自最大值,再用GREATEST
函数找到两者中更大的那一个作为GENERATE_SERIES
的stop参数。- 最后的主查询:用
LEFT JOIN
把Series
和CombinedValues
关联,WHERE cv.value IS NULL
筛选出Series
中存在但CombinedValues
中不存在的数字,最后用MIN()
找出最小的那个。
安全性建议: 无特别安全风险。
进阶用法: 可以灵活调整GENERATE_SERIES
中的起始和结束数字来适应各种场景。如果确定缺失的值在一个更小的范围内, 可以手动缩小范围提高效率。
2. 方法二:UNION ALL
+ 递归 CTE (通用方法)
这种方法利用递归 CTE 生成一个数字序列,兼容性更好,适用于多种数据库系统。
原理:
- 递归公用表表达式 (CTE): 建立一个基础的 case(第一个数字)。 并在循环里每次把这个数字 +1 直到循环条件达成(大于最大值)停止.
代码:
WITH RECURSIVE NumberSeries AS (
SELECT 1 AS num
UNION ALL
SELECT num + 1
FROM NumberSeries
WHERE num < (SELECT GREATEST(MAX(columna), (SELECT MAX(columnb) FROM Table2)) FROM Table1)
),
CombinedValues AS (
SELECT columna AS value FROM Table1
UNION ALL
SELECT columnb AS value FROM Table2
)
SELECT MIN(num)
FROM NumberSeries
WHERE num NOT IN (SELECT value FROM CombinedValues);
解释:
NumberSeries
CTE:递归生成从 1 开始的数字序列,上限是两个表中的最大值。CombinedValues
CTE:同上,合并两个表的数据。- 主查询:从
NumberSeries
中找出不在CombinedValues
中的最小数字。
安全性建议: 无特别安全风险。
进阶用法: 可以通过调整递归 CTE 中的初始值和递增步长来控制生成的序列。 如果最小值绝对不会从 1 开始, 可以改动 SELECT 1 AS num
来缩短循环时间。
3. 方法三:NOT EXISTS
(更简洁的方法)
这个办法不用生成数字序列,直接通过NOT EXISTS
来排除两个表内的已有数值,寻找第一个符合条件的最小值。
原理:
从1开始循环测试每个数字是不是在两个表内. 用NOT EXISTS
找到符合条件的数字,循环达成条件是两个NOT EXISTS
都通过.
代码:
SELECT MIN(num)
FROM (SELECT 1 AS num
UNION ALL
SELECT num + 1
FROM (SELECT 1 AS num) t --这个 FROM + (SELECT xxx) 组合只是确保有个表, 不让 sql 报错而已.
WHERE num < (SELECT GREATEST((SELECT MAX(columna) FROM Table1), (SELECT MAX(columnb) from Table2)))
) n
WHERE NOT EXISTS (SELECT 1 FROM Table1 WHERE columna = n.num)
AND NOT EXISTS (SELECT 1 FROM Table2 WHERE columnb = n.num);
解释:
子查询 (最里面的那个SELECT 1 as num
),我们得到一个包含初始值 1 的集合。
外部的SELECT num + 1
创建下一个数字(利用内部表的num
),外部通过WHERE
设置循环最大值,即两个表中最大数值。
然后最外部,对所有n内的数字都测试一轮。通过则表示找到符合要求的数值.
MIN
取最小值.
安全性建议: 无特别安全风险。
进阶用法: 此方法避免了完整的序列生成,在某些情况效率也许不错(如果最小缺失值很小)。 如果已知缺失值靠近理论最小值,则表现会更好.
总结
这三种方法各有侧重。第一种在PostgreSQL里头用着方便。第二种有更好通用性,大多数数据库都能跑。第三种最简短,如果符合条件的值出现在较小的范围,可以更快的找到答案. 要用哪个,看你具体的情况来定就好!