多来源数据只取一条
如果你的数据库是MySQL 8.0+或其他现代数据库,直接用方法一,最标准。如果你的数据库是MySQL 5.7或更老版本,用方法二。确保id字段上有索引,否则去重性能会很慢。--------------------------------------附加:如果是两张表,可参考如下:针对“两个来源(表 A 和表 B),ID 冲突时优先取表 A"的需求,主要有两种常用的 SQL 写法。具体选择取决于你
一张表,里面有个字段:来源。这个表里的数据从两个来源同步过来的。a和b。a和b是来源字段的取值。两个来源的数据可能存在id相同,id相同的情况下,取a来源的数据返回。
单表,通过 source 字段区分数据来源('a' 或 'b'),需要去重,且 source='a' 的优先级高于 source='b'。
这里有两种最常用的写法,取决于你的数据库版本。
方法一:使用窗口函数 ROW_NUMBER()(推荐)
适用数据库: MySQL 8.0+、PostgreSQL、Oracle、SQL Server 等现代数据库。 原理: 按 id 分组,按 source 排序(因为字母 'a' 小于 'b',所以 'a' 会排在前面),取每组的第一条。
SELECT id, source, other_columns...
FROM (
SELECT *,
-- 按 id 分组,按 source 排序('a' 会排在 'b' 前面)
ROW_NUMBER() OVER (PARTITION BY id ORDER BY source) as rn
FROM your_table_name
WHERE source IN ('a', 'b') -- 确保只处理这两个来源
) t
WHERE rn = 1;
- 优点: 代码简洁,逻辑清晰,性能通常较好。
- 关键点: 因为字母顺序
'a' < 'b',所以ORDER BY source天然实现了优先取 'a' 的需求。如果来源标识不是字母(比如 1 和 2),可以用ORDER BY CASE WHEN source='a' THEN 1 ELSE 2 END。
方法二:使用 UNION + NOT EXISTS(兼容性好)
适用数据库: 所有数据库(包括 MySQL 5.7 及以下老版本)。 原理: 先查出所有 'a' 的数据,再查出 'b' 的数据,但 'b' 的数据要排除掉那些在 'a' 中已经存在的 id。
-- 1. 先取所有来源为 a 的数据
SELECT * FROM your_table_name WHERE source = 'a'
UNION ALL
-- 2. 再取来源为 b 的数据,但排除掉 id 已经在 a 中出现过的
SELECT * FROM your_table_name t1
WHERE source = 'b'
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source = 'a'
);
- 优点: 兼容性极强,老数据库也能跑。
- 注意: 这里用
NOT EXISTS比NOT IN更安全(防止id为 NULL 导致的问题),且性能通常优于NOT IN。
方法三:PostgreSQL 特有写法(最简洁)
如果你用的是 PostgreSQL,可以用 DISTINCT ON:
SELECT DISTINCT ON (id) *
FROM your_table_name
WHERE source IN ('a', 'b')
ORDER BY id, source; -- 同样利用 'a' < 'b' 的排序规则
总结建议
- 如果你的数据库是 MySQL 8.0+ 或其他现代数据库,直接用 方法一,最标准。
- 如果你的数据库是 MySQL 5.7 或更老版本,用 方法二。
- 确保
id字段上有索引,否则去重性能会很慢。
三个来源
如果是同一张表,三个来源,c、k、s,c和k可能存在相同id,s和k可能存在相同id,都是优先取k的数据,sql语句怎么实现
单表,通过 source 字段区分('c', 'k', 's'),核心规则是 'k' 优先级最高。如果同一个 id 有多条记录,只要其中有 'k',就保留 'k';如果没有 'k',再考虑 'c' 或 's'。
这里有一个隐含问题:如果同一个 id 既有 'c' 又有 's'(但没有 'k'),取哪个? 为了保证结果中 id 唯一,我们需要定义一个完整的优先级顺序。下面的方案假设优先级为 k > c > s(你可以根据实际需求调整 c 和 s 的顺序)。
方法一:窗口函数 ROW_NUMBER()(最推荐,逻辑最清晰)
适用: MySQL 8.0+、PostgreSQL、Oracle、SQL Server 等。 原理: 按 id 分组,通过 CASE WHEN 自定义排序规则,让 'k' 排在第一位,然后取每组的第一条。
SELECT id, source, other_columns...
FROM (
SELECT *,
-- 核心逻辑:自定义优先级排序
ROW_NUMBER() OVER (
PARTITION BY id
ORDER BY
CASE WHEN source = 'k' THEN 1 -- k 优先级最高 (排第 1)
WHEN source = 'c' THEN 2 -- c 优先级次之 (排第 2)
ELSE 3 -- s 优先级最低 (排第 3)
END
) as rn
FROM your_table_name
WHERE source IN ('c', 'k', 's')
) t
WHERE rn = 1;
- 修改优先级: 如果你希望
s比c优先,只需交换THEN 2和THEN 3的逻辑。 - 优点: 只需要扫描一次表,逻辑扩展性强(以后加来源 'd' 只需加一行 CASE)。
方法二:UNION + NOT EXISTS(兼容老版本数据库)
适用: MySQL 5.7 及以下,或不支持窗口函数的数据库。 原理: 按优先级分层查询。先查所有 'k',再查 'c'(排除掉 'k' 有的 id),最后查 's'(排除掉 'k' 和 'c' 有的 id)。
-- 1. 第一优先级:取所有 k 的数据
SELECT * FROM your_table_name WHERE source = 'k'
UNION ALL
-- 2. 第二优先级:取 c 的数据,但排除掉 id 已经在 k 中出现过的
SELECT * FROM your_table_name t1
WHERE source = 'c'
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source = 'k'
)
UNION ALL
-- 3. 第三优先级:取 s 的数据,但排除掉 id 已经在 k 或 c 中出现过的
SELECT * FROM your_table_name t1
WHERE source = 's'
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source IN ('k', 'c')
);
- 优点: 兼容性好,逻辑直观(层层过滤)。
- 缺点: 代码较长,如果来源再多,
UNION会写得很累。
方法三:PostgreSQL 特有简写
如果你使用的是 PostgreSQL,可以用 DISTINCT ON 实现最简写法:
SELECT DISTINCT ON (id) *
FROM your_table_name
WHERE source IN ('c', 'k', 's')
ORDER BY id,
CASE WHEN source = 'k' THEN 1
WHEN source = 'c' THEN 2
ELSE 3 END;
关键点总结
- 优先级控制: 无论用哪种方法,核心都在于如何定义
k>c>s的顺序。- 窗口函数里用
ORDER BY CASE... UNION里用查询顺序和NOT EXISTS排除。
- 窗口函数里用
- 性能优化: 确保
id字段和source字段上有联合索引(id, source),这样去重效率最高。 - 隐含冲突: 上面的方案都假设了
c和s冲突时的处理规则(方案中假设了c优于s)。如果c和s永远不会冲突,那逻辑更简单,但加上防御性逻辑(如方案所示)更安全。
按照导入时间倒序排序
在 PostgreSQL 中,DISTINCT ON 有一个严格的语法限制:ORDER BY 子句必须以 DISTINCT ON 中使用的字段(这里是 id)开头。
因此,你不能直接在原语句末尾添加 ORDER BY import_time DESC,否则报错。
要实现“先去重(按优先级),再按时间倒序展示”,你需要使用 子查询(Subquery) 或 CTE(公用表表达式) 包裹一层。
正确的 SQL 写法
SELECT * FROM (
SELECT DISTINCT ON (id) *
FROM your_table_name
WHERE source IN ('c', 'k', 's')
-- 内层 ORDER BY:决定“保留哪一条数据”
-- 1. 必须先按 id 排序 (DISTINCT ON 的要求)
-- 2. 再按来源优先级排序 (k > c > s)
-- 3. 建议加上 import_time DESC (确保同优先级下保留最新的一条)
ORDER BY id,
CASE WHEN source = 'k' THEN 1
WHEN source = 'c' THEN 2
ELSE 3 END,
import_time DESC
) AS deduped_data
-- 外层 ORDER BY:决定“最终结果怎么展示”
ORDER BY import_time DESC;
关键点解析
-
为什么要包一层子查询?
- 内层查询:负责去重。它利用
DISTINCT ON (id)配合ORDER BY规则,为每个id只保留优先级最高(且时间最新)的那一行。 - 外层查询:负责排序。因为内层查询的
ORDER BY被id锁死了,无法按时间全局排序,所以必须在外层再排一次序。
- 内层查询:负责去重。它利用
-
内层
ORDER BY为什么要加import_time DESC?- 在你的原始 SQL 中,如果同一个
id有两条来源都是'k'的数据,数据库会随机保留其中一条(因为排序规则没区分它们)。 - 加上
import_time DESC后,如果有冲突,会确保保留导入时间最新的那条'k'数据,逻辑更严谨。
- 在你的原始 SQL 中,如果同一个
-
性能建议 为了让这条 SQL 跑得更快,建议在数据库上建立如下联合索引:
CREATE INDEX idx_id_source_time ON your_table_name (id, source, import_time);这样 PostgreSQL 可以直接利用索引完成去重和排序,避免全表排序。
Union倒排序
在 PostgreSQL 中,对于 UNION ALL 查询,ORDER BY 必须放在整个语句的末尾,这样会对最终合并后的结果进行排序。
以下是修正后的完整 SQL:
修正后的 SQL(推荐)
修改点:
- 每一层增加了
NOT EXISTS子查询,确保同一来源内id重复时只取时间最新的一条。 - 在语句最末尾增加了
ORDER BY import_time DESC,实现最终结果倒序。
-- 1. 第一优先级:取 k 的数据(同 id 取最新)
SELECT * FROM your_table_name t1
WHERE source = 'k'
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source = 'k' AND t2.import_time > t1.import_time
)
UNION ALL
-- 2. 第二优先级:取 c 的数据(排除 k 有的 id,同 id 取最新)
SELECT * FROM your_table_name t1
WHERE source = 'c'
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source = 'k'
)
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source = 'c' AND t2.import_time > t1.import_time
)
UNION ALL
-- 3. 第三优先级:取 s 的数据(排除 k/c 有的 id,同 id 取最新)
SELECT * FROM your_table_name t1
WHERE source = 's'
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source IN ('k', 'c')
)
AND NOT EXISTS (
SELECT 1 FROM your_table_name t2
WHERE t2.id = t1.id AND t2.source = 's' AND t2.import_time > t1.import_time
)
-- 最终结果按导入时间倒序排序
ORDER BY import_time DESC;
更优方案:使用 DISTINCT ON (PostgreSQL 特有)
既然你使用的是 PostgreSQL,上面的 UNION 写法显得太冗长且性能一般。强烈建议改用 DISTINCT ON 配合子查询,代码更简洁,性能更好,逻辑也完全一致(优先级 k > c > s,同优先级取时间最新)。
SELECT * FROM (
SELECT DISTINCT ON (id) *
FROM your_table_name
WHERE source IN ('c', 'k', 's')
-- 内层排序:决定“保留哪一条”
-- 1. id 分组
-- 2. 来源优先级 (k=1, c=2, s=3)
-- 3. 时间倒序 (确保同来源下保留最新的)
ORDER BY id,
CASE WHEN source = 'k' THEN 1
WHEN source = 'c' THEN 2
ELSE 3 END,
import_time DESC
) AS deduped_result
-- 外层排序:决定“最终展示顺序”
ORDER BY import_time DESC;
总结
ORDER BY位置: 必须放在整个UNION语句的最末尾。- 去重完整性: 务必检查同一来源内是否有重复
id。如果有,必须像上面代码那样增加时间比较逻辑,或者直接使用DISTINCT ON方案。 - 性能: 在 PostgreSQL 中,
DISTINCT ON方案通常比多层UNION + NOT EXISTS执行效率更高。
附加
--------------------------------------附加:
如果是两张表,可参考如下:
针对“两个来源(表 A 和表 B),ID 冲突时优先取表 A"的需求,主要有两种常用的 SQL 写法。具体选择取决于你的数据库类型(是否支持窗口函数)以及数据量大小。
假设两张表结构相同(都有 id, name, value 等字段):
方法一:UNION ALL + NOT EXISTS(兼容性最好,逻辑最直观)
适用场景: 所有数据库(包括老版本 MySQL),逻辑简单易懂。 思路: 先查出表 A 的所有数据,再查出表 B 中“不存在于表 A"的数据,最后合并。
SELECT * FROM table_a
UNION ALL
SELECT * FROM table_b b
WHERE NOT EXISTS (
SELECT 1 FROM table_a a WHERE a.id = b.id
);
- 优点: 语义清晰,"A 全要,B 只要 A 没有的"。
- 注意: 推荐使用
NOT EXISTS而不是NOT IN,因为如果id字段允许为NULL,NOT IN可能会导致查询结果为空或出错。
方法二:ROW_NUMBER() 窗口函数(性能更好,适合大数据)
适用场景: MySQL 8.0+、PostgreSQL、Oracle、SQL Server 等支持窗口函数的数据库。 思路: 把两张表拼在一起,给每一行打上优先级标签,然后按 id 分组排序,只取优先级最高(排序第一)的那一行。
SELECT id, name, value -- 这里明确列出字段,不要选 priority 和 rn
FROM (
SELECT *,
1 as priority, -- 表 A 优先级高
ROW_NUMBER() OVER (PARTITION BY id ORDER BY priority) as rn
FROM table_a
UNION ALL
SELECT *,
2 as priority, -- 表 B 优先级低
ROW_NUMBER() OVER (PARTITION BY id ORDER BY priority) as rn
FROM table_b
) t
WHERE rn = 1;
- 优点: 只需要扫描一次合并后的数据集,通常在数据量很大时比
NOT EXISTS性能更优;且如果以后有表 C、表 D,扩展起来很方便(只需改 priority 数字)。 - 注意: 外层查询需要把辅助字段(
priority,rn)过滤掉。
方法三:LEFT JOIN + COALESCE(如果只需要合并特定字段)
适用场景: 如果表 A 和表 B 的字段不完全一样,或者你只想以 A 为主,A 没有的字段才从 B 补全。 思路: 以表 A 为主表,左连接表 B,如果 A 有值就用 A,A 没值就用 B。
SELECT
COALESCE(a.id, b.id) as id,
a.name, -- 优先取 A 的 name
COALESCE(a.value, b.value) as value -- A 没有 value 时才取 B 的
FROM table_a a
FULL OUTER JOIN table_b b ON a.id = b.id
-- 注意:MySQL 不支持 FULL OUTER JOIN,需用 UNION 模拟
(注:如果你的需求是“只要 ID 冲突,整行都取 A",请忽略此方法,使用前两种。)
总结建议
- 最推荐(通用): 使用 方法一 (
UNION ALL+NOT EXISTS)。写法简单,不容易出错,适用于绝大多数业务场景。 - 大数据/数仓场景: 使用 方法二 (
ROW_NUMBER)。在处理百万/千万级数据时,窗口函数的执行计划通常更优。 - 字段对齐: 确保
UNION前后的字段数量和类型顺序一致。
示例(方法一的完整可运行版):
-- 假设表结构:id (int), name (varchar), amount (decimal)
SELECT id, name, amount FROM source_a
UNION ALL
SELECT id, name, amount FROM source_b b
WHERE NOT EXISTS (
SELECT 1 FROM source_a a WHERE a.id = b.id
);更多推荐
所有评论(0)