当源 table 中有复合主键时如何在 SQL 服务器中使用 MERGE

How to Use MERGE in SQL Server when you have a compound primary key in the source table

我正在尝试使用 MERGE 语句从源 table 更新目标 table 但出现错误,因为 SQL 正在尝试更新或删除多个记录。对于背景 - 我有 'source' table 本质上是一个滚动 table,它记录了业务添加的每个数据实例 - 它仅由自动增量 'record id' 键控。 'target' table 只能有一次主键实例 - 即 'string'。这个想法是查看源 table - 如果主键匹配,则用新数据更新所述行,如果不匹配 - 插入新行中的所有信息。问题是我不断收到错误消息,提示我尝试更新或删除同一条记录两次。我相信这种情况正在发生,因为在源 table 中确实有两个(或更多)字符串(主键)实例。唯一的区别是 'date_added' 字段。我怎样才能重写这个以合并两者?我对这个 SQL 函数很陌生,我尝试了一些方法,但都是 return 错误。所有的功劳都归功于首先给我 MERGE 建议的另一个用户 - 我试图使用 IF/THEN.

我尝试在 ON 和 MATCH 子句中使用 MAX 记录日期,这两个子句都因语法return而出现错误

MERGE 
    SCM_Top_Up_Operational O 
    USING SCM_Top_Up_Rolling R ON (O.String = R.string)
WHEN MATCHED 
    THEN UPDATE SET 
        O.Date_Added    = R.Date_Added,
        O.Real_Exfact   = R.Real_Exfact,
        O.Excess_Top_Up = R.Excess_Top_Up 
WHEN NOT MATCHED BY TARGET 
    THEN INSERT (  String,   Date_Added,   Real_Exfact,   Article_ID,   Excess_Top_Up,   Plant)
         VALUES (R.String, R.Date_Added, R.Real_Exfact, R.Article_ID, R.Excess_Top_Up, R.Plant);

这是一些样本数据。如果我在 scm_top_up_rolling 中查询 ('B418496220','B111116220') 中的字符串,我会得到这些结果:

RECORD_ID   String     Date_Added Real_Exfact Article_ID Excess_Top_Up                           Plant
----------- ---------- ---------- ----------- ---------- --------------------------------------- -----
3108        B418496220 2019-02-25 2019-05-15  B41849     1235                                    6220
3211        B418496220 2019-03-28 2019-03-28  B41849     1                                       6220
3212        B111116220 2019-03-28 2019-03-28  B11111     1                                       6220

现在,如果我查询 scm_top_up_operational 相同的字符串,我会得到:

String     Date_Added Real_Exfact Article_ID Excess_Top_Up                           Plant
---------- ---------- ----------- ---------- --------------------------------------- -----
B418496220 2019-02-25 2019-05-15  B41849     1235                                    6220

我的目标是 scm_top_up_operational 更新为 B418496220 的最新条目,因为它已经存在于操作 table 中。然后我想插入 B111116220 的新记录,因为它在操作 table 中不存在。

希望对您有所帮助,谢谢。

您不能在合并语句中直接使用 SCM_Top_Up_Rolling,因为用于连接的键(即字符串)必须是唯一的。

您需要做的是通过在合并语句之上添加几层 CTE(通用 Table 表达式)来准备源数据。此步骤的目的是删除重复项和 return 一个唯一的行列表。

看看下面的解决方案:

;with cte
as
(
    select String, Date_Added, Real_Exfact, Article_ID, Excess_Top_Up, Plant
        , row_number() over (partition by String order by Date_Added desc) as 'rank'
    from dbo.SCM_Top_Up_Rolling
)
, cte_source
as
(
    select *
    from cte
    where rank = 1
)
merge SCM_Top_Up_Operational O 
using cte_source R              on (O.String = R.String)
when matched then 
    update set 
    O.Date_Added    = R.Date_Added,
    O.Real_Exfact   = R.Real_Exfact,
    O.Excess_Top_Up = R.Excess_Top_Up 
when not matched by target then 
    insert (  String,   Date_Added,   Real_Exfact,   Article_ID,   Excess_Top_Up,   Plant)
    values (R.String, R.Date_Added, R.Real_Exfact, R.Article_ID, R.Excess_Top_Up, R.Plant);

查询的关键组成部分是 window 函数:

row_number() over (partition by String order by Date_Added desc) as 'rank'

生成一个新列 rank:

String     Date_Added   rank    
----------------------------    
B418496220 2019-02-25   2
B418496220 2019-03-28   1       <= To be used in the merge, e.g. where rank = 1
B111116220 2019-03-28   1       <= To be used in the merge, e.g. where rank = 1

一旦我们生成了一个唯一列表(即具有最新 Date_Added 的字符串列表),我们就可以使用此列表作为我们的合并源。

merge SCM_Top_Up_Operational O 
using cte_source R              on (O.String = R.String)

希望答案足够清楚。祝你好运。