一张表,里面有个字段:来源。这个表里的数据从两个来源同步过来的。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' 的排序规则

总结建议

  1. 如果你的数据库是 MySQL 8.0+ 或其他现代数据库,直接用 方法一,最标准。
  2. 如果你的数据库是 MySQL 5.7 或更老版本,用 方法二
  3. 确保 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;

关键点总结

  1. 优先级控制: 无论用哪种方法,核心都在于如何定义 k > c > s 的顺序。
    • 窗口函数里用 ORDER BY CASE...
    • UNION 里用查询顺序和 NOT EXISTS 排除。
  2. 性能优化: 确保 id 字段和 source 字段上有联合索引 (id, source),这样去重效率最高。
  3. 隐含冲突: 上面的方案都假设了 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;

关键点解析

  1. 为什么要包一层子查询?

    • 内层查询:负责去重。它利用 DISTINCT ON (id) 配合 ORDER BY 规则,为每个 id 只保留优先级最高(且时间最新)的那一行。
    • 外层查询:负责排序。因为内层查询的 ORDER BY 被 id 锁死了,无法按时间全局排序,所以必须在外层再排一次序。
  2. 内层 ORDER BY 为什么要加 import_time DESC

    • 在你的原始 SQL 中,如果同一个 id 有两条来源都是 'k' 的数据,数据库会随机保留其中一条(因为排序规则没区分它们)。
    • 加上 import_time DESC 后,如果有冲突,会确保保留导入时间最新的那条 'k' 数据,逻辑更严谨。
  3. 性能建议 为了让这条 SQL 跑得更快,建议在数据库上建立如下联合索引:

    CREATE INDEX idx_id_source_time ON your_table_name (id, source, import_time);

    这样 PostgreSQL 可以直接利用索引完成去重和排序,避免全表排序。

Union倒排序

在 PostgreSQL 中,对于 UNION ALL 查询,ORDER BY 必须放在整个语句的末尾,这样会对最终合并后的结果进行排序。

以下是修正后的完整 SQL:

修正后的 SQL(推荐)

修改点:

  1. 每一层增加了 NOT EXISTS 子查询,确保同一来源内 id 重复时只取时间最新的一条。
  2. 在语句最末尾增加了 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;

总结

  1. ORDER BY 位置: 必须放在整个 UNION 语句的最末尾
  2. 去重完整性: 务必检查同一来源内是否有重复 id。如果有,必须像上面代码那样增加时间比较逻辑,或者直接使用 DISTINCT ON 方案。
  3. 性能: 在 PostgreSQL 中,DISTINCT ON 方案通常比多层 UNION + NOT EXISTS 执行效率更高。

附加

--------------------------------------附加:

如果是两张表,可参考如下:

针对“两个来源(表 A 和表 B),ID 冲突时优先取表 A"的需求,主要有两种常用的 SQL 写法。具体选择取决于你的数据库类型(是否支持窗口函数)以及数据量大小。

假设两张表结构相同(都有 idnamevalue 等字段):

方法一: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 字段允许为 NULLNOT 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 数字)。
  • 注意: 外层查询需要把辅助字段(priorityrn)过滤掉。

方法三: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",请忽略此方法,使用前两种。)


总结建议

  1. 最推荐(通用): 使用 方法一 (UNION ALL + NOT EXISTS)。写法简单,不容易出错,适用于绝大多数业务场景。
  2. 大数据/数仓场景: 使用 方法二 (ROW_NUMBER)。在处理百万/千万级数据时,窗口函数的执行计划通常更优。
  3. 字段对齐: 确保 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
);
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐