【问题标题】:Sync two tables with single SQL使用单个 SQL 同步两个表
【发布时间】:2023-03-16 01:13:01
【问题描述】:

我需要在 Oracle 中同步两个表。我使用 MERGE 来完成这项工作,但我需要帮助才能获得有效的 SQL 来完成这项工作。

我的目标表有一个 PK 和一些其他列。其中一些列没有空约束。

我的源表的布局和数据与我的目标表不同,所以我需要查询我的源表并将数据转换为目标布局。

我的实际代码是(简化的):

MERGE INTO TARGET t USING(
    WITH SRC AS ( --do the transformation
        SELECT ID, DECODE(VAL,'THIS','THAT','OTHER') VAL1, REGEXP_SUBSTR(VAL,'\d+') VAL2 FROM SOURCE
    )
    SELECT t.ROWID ROW_ID, s.* FROM SRC s
    FULL OUTER JOIN TARGET t ON s.ID=t.ID
) s ON (t.ROWID=s.ROW_ID)
WHEN MATCHED THEN UPDATE SET t.VAL1=s.VAL1 AND t.VAL2=s.VAL2
DELETE WHERE s.ID IS NULL
WHEN NOT MATCHED THEN INSERT(ID, VAL1, VAL2) VALUES (s.ID, s.VAL1, s.VAL2);

问题在于,这些符合 DELETE 条件的行会抛出 ORA-01407: cannot update (string) to NULL。看来,Oracle 首先尝试更新并稍后执行删除。这会导致错误。

MERGE 关键字对于同步删除表来说真的很糟糕,但我想使用单个查询,因为我的转换 SQL 真的很重。

是否有任何替代 MERGE 的方法或任何建议以使其正常工作?

谢谢

这就是我的解决方案。也许这可以帮助某人。

MERGE INTO TARGET t USING(
    WITH SRC AS ( --do the transformation
        SELECT ID, DECODE(VAL,'THIS','THAT','OTHER') VAL1, REGEXP_SUBSTR(VAL,'\d+') VAL2 FROM SOURCE
    )
    SELECT t.ROWID ROW_ID, NVL2(s.ID,null,1) delFlag, s.* 
    FROM SRC s
    FULL OUTER JOIN TARGET t ON s.ID=t.ID
) s ON (t.ROWID=s.ROW_ID)
WHEN MATCHED THEN UPDATE SET t.VAL1=NVL2(s.delFlag,t.VAL1,s.VAL1) AND t.VAL2=NVL2(s.delFlag,t.VAL1,s.VAL2)
DELETE WHERE s.delFlag IS NOT NULL
WHEN NOT MATCHED THEN INSERT(ID, VAL1, VAL2) VALUES (s.ID, s.VAL1, s.VAL2);

但是很奇怪,要删除的行必须通过约束检查。

【问题讨论】:

  • (当你提问出现错误时,请提供整个错误信息,而不仅仅是错误代码。我不了解你,但我不是甲骨文的行走百科全书错误代码!我已编辑您的问题以包含通用错误文本)
  • 查询正常,错误出现在那些应该被删除的行上。我想知道为什么要对那些将被删除的人进行 NOTNULL 检查。
  • @NicoRichter - 因为更新是先完成的,所以必须满足约束;然后删除子句 (a sub-clause of update 应用于该更新的结果。“受此子句影响的唯一行是目标表中由合并操作更新的行。DELETE WHERE 条件评估更新的值,而不是原始值...”。在进行非空检查时,Oracle 不知道/不关心它们将被删除。

标签: sql oracle merge


【解决方案1】:

为了在合并语句中删除行,您需要先更新行。

因此,您需要在更新语句中考虑空值(直接在set 子句中,或在源查询中),例如:

MERGE INTO target t
  USING (WITH src AS ( --do the transformation
                      SELECT id,
                             DECODE(val, 'THIS', 'THAT', 'OTHER') val1,
                             regexp_substr(val, '\d+') val2
                      FROM   SOURCE)
         SELECT t1.rowid row_id,
                s1.id,
                NVL(s1.val1, t1.val1) val1,
                NVL(s1.val2, t1.val2) val2
         FROM   src s1
         FULL   OUTER JOIN target t1
         ON     s1.id = t1.id) s ON (t.rowid = s.row_id)
WHEN MATCHED THEN
  UPDATE SET t.val1 = s.val1 AND t.val2 = s.val2
  DELETE WHERE s.id IS NULL
WHEN NOT MATCHED THEN
  INSERT (id, val1, val2)
  VALUES (s.id, s.val1, s.val2);

注意我假设源表和目标表中的 val1 和 val2 不可为空。根据您的表格进行适当修改。

【讨论】:

    【解决方案2】:

    看起来很奇怪,Oracle 的MERGE 并没有更新或删除一行,而是只删除它已经更新的行。似乎没有意义,但事情就是这样。 请参阅 Boneist 的答案以获得解决方案。

    更新:我在一些标准 SQL 草案中查找了这一点。据我所知,标准 SQL 2003 中的MERGE 仅支持插入和删除,WHEN MATCHED THENWHEN NOT MATCHED THEN 都不允许出现多次。

    Oracle 显然想提供一个删除选项,并决定采用奇怪的方法来扩展更新子句并仅对更新的行应用删除。

    在较新的草稿(应该是当前草稿,可在此处找到:https://www.wiscorp.com/SQLStandards.html)中添加了删除选项,WHEN 子句扩展到 WHEN MATCHED [ AND <search condition> ] THENWHEN NOT MATCHED [ AND <search condition> ] THEN,并且这些子句可以出现不止一次。这看起来好多了。让我们希望 Oracle 尽快采用这种新语法:-)

    罢工>

    问题是这些匹配 DELETE 条件的行抛出 ORA-01407: cannot update (string) to NULL。看来,Oracle 首先尝试更新并稍后执行删除。这会导致错误。

    那么也给 UPDATE 部分一个条件:

    WHEN MATCHED THEN
      UPDATE SET t.VAL1=s.VAL1 AND t.VAL2=s.VAL2 WHERE s.ID IS NOT NULL
      DELETE WHERE s.ID IS NULL
    

    【讨论】:

    • 这不起作用 - 如果您没有先更新该行,则无法删除它。有关演示,请参阅 this example
    • Boneist:真奇怪!我不知道。我已经相应地编辑了我的答案。
    猜你喜欢
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-29
    相关资源
    最近更新 更多