【问题标题】:Why upsert is not a fundamental SQL operation [closed]为什么upsert不是基本的SQL操作[关闭]
【发布时间】:2014-09-28 04:05:28
【问题描述】:

为什么 SQL 不支持 upsert 用例?我不是在问如何在特定的 db(*) 中做。我想知道的是为什么 upsert 不是像插入和更新这样的基本操作。我的意思是这是一个非常简单的用例,对吧?我想一定有一些基本的数据库优先原则在执行 upsert 时会被打破,或者数据库引擎在面对 upsert 时面临的一些技术挑战。

*。我知道 mysql 语法和 SQL Merge 语法。顺便说一句,即使在使用这种特定于数据库的语法时,您也需要注意原子性和锁定。并且使用合并语法,创建一个伪表并不合适。

编辑:我正在编辑此内容以澄清我不是在征求意见。所以我觉得这个问题不应该被屏蔽。

【问题讨论】:

  • 你应该在 E.F. Codd 博士设计 SQL 之前问过他.....
  • 你不妨问为什么UPDATE被认为是基本操作,因为它总是可以由INSERTDELETE模拟,并适当考虑酸度.
  • 现在有几个 dbms 支持 merge 处理 upsert 用例。 [顺便说一句:不是科德,是张伯伦和博伊斯]
  • @Damien_The_Unbeliever 更新不能通过删除来模拟,因为记录可以有引用。删除和插入现在需要删除和重新创建整个引用链。
  • " ...了解设计决策.." 然后转到源代码cs.uakron.edu/~echeng/db/sequel.pdf

标签: sql upsert


【解决方案1】:

因为它在酸和语法方面都不容易处理。
如果存在则更新的条件尚不清楚。

例如,将以下查询中的“插入”替换为 upsert

insert into t_something 
select * from t_whatever

没有外键,没有主键。
你想怎么更新?
where 条件是选择还是更新?

最终,您必须编写条件,然后您也可以执行“update/insert if”...

通常,当您问自己 upsert 问题时,您处理的插入/更新错误。
你在考虑对象而不是固定的术语。

你想遍历一个对象数组,然后插入 if count(*) on exists is 0 else update。

这就是面向对象的命令式编程的工作原理,但 SQL 不是这样工作的。

在 SQL 中,您使用 SET 进行操作。
您可以轻松地进行内部连接 ​​- 更新 SET
和一个左连接,其中 null 在同一个 SET 上插入。
这就像合并一样舒适,而且可读性更高,调试也更简单。
而且它可能会更快。

您已经可以通过将更新和插入放入事务中来确保它是原子的。

想到 upsert,你接下来想要哪个白痴? “UpSertLeteTrunc”?默德尔?
或者可能是 truncsert ?

到目前为止,还有更重要的事情要做。

这就是我在 SQL-Server 上使用 MERGE 进行 Upsert 的方式:

-- How to create the XML 
/*
DECLARE @xml XML 
SET @xml = ( SELECT (SELECT * FROM T_Benutzer FOR XML PATH('row'), ROOT('table'),  ELEMENTS xsinil) AS outerXml )
-- SELECT @xml 
*/


DECLARE @xml xml 
SET @xml = '<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <row>
    <PLK_UID>7CA68E6E-E998-FF92-BE70-126064765EAB</PLK_UID>
    <PLK_Code>A2 Hoch</PLK_Code>
    <PLK_PS_UID>6CF3B5AB-C6C8-4A12-8717-285F95A1084B</PLK_PS_UID>
    <PLK_DAR_UID xsi:nil="true" />
    <PLK_Name_DE>Mit Legende</PLK_Name_DE>
    <PLK_Name_FR>Avec Légende</PLK_Name_FR>
    <PLK_Name_IT>Con Leggenda</PLK_Name_IT>
    <PLK_Name_EN>With Legend</PLK_Name_EN>
    <PLK_IsDefault>0</PLK_IsDefault>
    <PLK_Status>1</PLK_Status>
  </row>
</table>'


DECLARE @handle INT  
DECLARE @PrepareXmlStatus INT  

EXEC @PrepareXmlStatus = sp_xml_preparedocument @handle OUTPUT, @XML


;WITH CTE AS 
(
    SELECT 
         PLK_UID
        ,PLK_Code
        ,PLK_PS_UID
        ,PLK_DAR_UID
        ,PLK_Name_DE
        ,PLK_Name_FR
        ,PLK_Name_IT
        ,PLK_Name_EN
        ,PLK_IsDefault
        ,PLK_Status
    FROM OPENXML(@handle, '/table/row', 2) WITH 
    (
         "PLK_UID" uniqueidentifier 'PLK_UID[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_Code" character varying(10) 'PLK_Code[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_PS_UID" uniqueidentifier 'PLK_PS_UID[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_DAR_UID" uniqueidentifier 'PLK_DAR_UID[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_Name_DE" national character varying(255) 'PLK_Name_DE[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_Name_FR" national character varying(255) 'PLK_Name_FR[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_Name_IT" national character varying(255) 'PLK_Name_IT[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_Name_EN" national character varying(255) 'PLK_Name_EN[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_IsDefault" bit 'PLK_IsDefault[not(@*[local-name()="nil" and . ="true"])]'
        ,"PLK_Status" int 'PLK_Status[not(@*[local-name()="nil" and . ="true"])]'
    ) AS tSource 

    WHERE (1=1) 
    
    -- AND NOT EXISTS 
    -- (
    --  SELECT * FROM T_VWS_Ref_PdfLegendenKategorie 
    --  WHERE T_VWS_Ref_PdfLegendenKategorie.PLK_UID = tSource.PLK_UID 
    --)
)
-- SELECT * FROM CTE     
MERGE INTO T_VWS_Ref_PdfLegendenKategorie AS A 
USING CTE ON CTE.PLK_UID = A.PLK_UID 
WHEN MATCHED 
    THEN UPDATE
        SET  A.PLK_Code = CTE.PLK_Code
            ,A.PLK_PS_UID = CTE.PLK_PS_UID
            ,A.PLK_DAR_UID = CTE.PLK_DAR_UID
            ,A.PLK_Name_DE = CTE.PLK_Name_DE
            ,A.PLK_Name_FR = CTE.PLK_Name_FR
            ,A.PLK_Name_IT = CTE.PLK_Name_IT
            ,A.PLK_Name_EN = CTE.PLK_Name_EN
            ,A.PLK_IsDefault = CTE.PLK_IsDefault
            ,A.PLK_Status = CTE.PLK_Status
WHEN NOT MATCHED BY TARGET THEN 
INSERT 
(
     PLK_UID
    ,PLK_Code
    ,PLK_PS_UID
    ,PLK_DAR_UID
    ,PLK_Name_DE
    ,PLK_Name_FR
    ,PLK_Name_IT
    ,PLK_Name_EN
    ,PLK_IsDefault
    ,PLK_Status
)
VALUES
(
     CTE.PLK_UID
    ,CTE.PLK_Code
    ,CTE.PLK_PS_UID
    ,CTE.PLK_DAR_UID
    ,CTE.PLK_Name_DE
    ,CTE.PLK_Name_FR
    ,CTE.PLK_Name_IT
    ,CTE.PLK_Name_EN
    ,CTE.PLK_IsDefault
    ,CTE.PLK_Status
)
-- WHEN NOT MATCHED BY SOURCE THEN DELETE
;


EXEC sp_xml_removedocument @handle 

【讨论】:

  • 对不起,我认为您没有得到问题,或者我没有很好地提问。我不是在问如何做到这一点。但是到目前为止,在我的编程生涯中,我已经看到了 upsert 的多个用例(只是谷歌),我想知道为什么它还不存在。至于您的回复,我猜您对此并不熟悉 mysql 语法。看看 [Link]dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html 。不管你是在单行还是一组上操作。如果密钥已经存在,则更新,否则插入。
  • @ad77:是的,但是他们使用 ON DUPLICATE KEY,如果没有密钥,或者密钥不是唯一的标准,这将失败。如果它想要有用,您需要提供更新标准。有了这样一个最简单的例子,我看不出与 IF EXISTS(SELECT * FROM T_Whatever WHERE a = @a) UPDATE T_Whatever SET c = @c+1 ELSE INSERT INTO table (a,b,c )值 (1,2,3)
  • 请不要采取任何其他方式。只需在谷歌上搜索“upsert 方法”,即可了解此解决方案的问题以及许多其他人一直在尝试的问题。这是一个帮助您入门的链接。 link.
  • 也读过这个。Link
  • Postgres 增加了对 upsert link 的支持 .. 只是评论以供参考。
猜你喜欢
  • 2019-11-20
  • 2012-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 2011-12-22
  • 1970-01-01
相关资源
最近更新 更多