merge不支持三方直接对齐,因using仅接受单源,多源需先通过cte统一归一化处理。

MERGE 本身不支持三方直接对齐,必须把“三方”先归一为一个逻辑源,否则语法报错或行为不可控。
为什么不能直接 MERGE 三个表?
MERGE 的 USING 子句只接受单个表、视图、CTE 或子查询,不支持 USING table1, table2, table3 这类多源写法。强行拼 JOIN 到 USING 里,会因数据重复、NULL 匹配歧义、ON 条件膨胀等问题触发 The MERGE statement attempted to UPDATE or DELETE the same row more than once 错误。
- 三方数据通常存在字段命名不一致(如
user_id/uid/customer_code)、时间戳精度不同、空值语义冲突(空字符串 vs NULL vs 'N/A') - 业务主键往往不是物理主键:比如三方都用邮箱去重,但其中一方邮箱可为空,另一方用手机号兜底——这种逻辑必须在
USING前显式定义,不能靠 ON 临时判断 - SQL Server 的
MERGE不允许在ON中用COALESCE(t.email, t.phone)这类表达式,否则索引失效,执行计划退化为全表扫描
正确做法:用 CTE 预聚合三方为统一源
核心是把三方数据“对齐 → 去重 → 选优”三步压缩进一个 CTE,再喂给 MERGE。例如:
WITH UnifiedSource AS (
SELECT
COALESCE(a.email, b.email, c.email) AS email,
COALESCE(a.tenant_id, b.tenant_id, c.tenant_id) AS tenant_id,
-- 优先取最新更新的 name,按 source 权重降序
FIRST_VALUE(COALESCE(a.name, b.name, c.name))
OVER (PARTITION BY COALESCE(a.email, b.email, c.email), COALESCE(a.tenant_id, b.tenant_id, c.tenant_id)
ORDER BY COALESCE(a.updated_at, '1970-01-01'),
COALESCE(b.updated_at, '1970-01-01'),
COALESCE(c.updated_at, '1970-01-01') DESC) AS name,
GETDATE() AS merged_at
FROM src_a a
FULL JOIN src_b b ON a.email = b.email AND a.tenant_id = b.tenant_id
FULL JOIN src_c c ON COALESCE(a.email, b.email) = c.email AND COALESCE(a.tenant_id, b.tenant_id) = c.tenant_id
),
Cleaned AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY email, tenant_id
ORDER BY merged_at DESC
) AS rn
FROM UnifiedSource
WHERE email IS NOT NULL AND tenant_id IS NOT NULL
)
MERGE INTO dim_customer AS tgt
USING (SELECT * FROM Cleaned WHERE rn = 1) AS src
ON (tgt.email = src.email AND tgt.tenant_id = src.tenant_id)
WHEN MATCHED THEN
UPDATE SET name = src.name, updated_at = src.merged_at
WHEN NOT MATCHED THEN
INSERT (email, tenant_id, name, created_at, updated_at)
VALUES (src.email, src.tenant_id, src.name, src.merged_at, src.merged_at);
-
FULL JOIN确保三方任意一方有数据都能进入中间结果,避免漏掉仅存在于某一方的记录 -
FIRST_VALUE(...) OVER (ORDER BY ...)实现“字段选优”,比 CASE WHEN 更易维护权重逻辑 -
ROW_NUMBER() ... WHERE rn = 1是硬性去重,防止 MERGE 因重复 key 报错 - 所有参与
ON的字段(email,tenant_id)必须在dim_customer上建唯一索引,否则 MERGE 可能失败
容易被忽略的并发与事务陷阱
三方对齐入库常用于定时同步任务,但没人提的是:如果两个实例同时跑这个 MERGE,哪怕加了唯一索引,仍可能因 READ COMMITTED 隔离级别下的“读-判-写”窗口导致重复插入或丢失更新。
- 必须在存储过程中显式加事务,并用
WITH (TABLOCKX)提示(仅限 SQL Server),或改用UPDLOCK, HOLDLOCK在 ON 条件上加范围锁 - 不要依赖
WHEN NOT MATCHED BY SOURCE THEN DELETE清理旧数据——三方源本身就不全量,删了可能把仅存于目标表的合法记录干掉 - 日志表必须记录每次 MERGE 的输入行数、匹配数、插入数、更新数,否则出问题时无法定位是哪方数据异常
- 若三方之一延迟超 5 分钟,应跳过本次合并并告警,而不是拿脏数据覆盖线上
真正难的从来不是写 MERGE 语句,而是把三方业务规则翻译成可执行、可验证、可回滚的 CTE 逻辑——字段怎么对齐、冲突怎么仲裁、空值怎么兜底,这些细节一旦写死在 SQL 里,改起来比重构应用代码还疼。










