T-Sql更新并避免冲突

时间:2018-04-03 12:39:42

标签: tsql sql-update sql-server-2016 sql-merge

我尝试将Tomcat应用程序从使用Postgres 9.5迁移到SQL Server 2016,并且我遇到了一个问题陈述,我似乎无法复制。

它基本上是一个upsert,但其中一个复杂因素是请求提供了更新的参数,但是当存在冲突时,我需要使用冲突行中的一些现有值来插入/更新。 表中的主键有时会导致冲突,这需要更新行并删除旧行。

MS SQL中的表模式如下所示:

CREATE TABLE [dbo].[signup](
       [site_key] [varchar](32) NOT NULL,
       [list_id] [bigint] NOT NULL,
       [email_address] [varchar](256) NOT NULL,
       [customer_id] [bigint] NULL,
       [attribute1] [varchar](64) NULL,
       [date1] [datetime] NOT NULL,
       [date2] [datetime] NULL,
CONSTRAINT [pk_signup] PRIMARY KEY CLUSTERED 
(
       [site_key] ASC,
       [list_id] ASC,
       [email_address] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

旧的Postgres SQL看起来像这样:

WITH updated_rows AS (
INSERT INTO signup
(site_key, list_id, email_address, customer_id, attribute1, date1, date2)
SELECT site_key, list_id, :emailAddress, customer_id, attribute1, date1, date2
FROM signup WHERE customer_id = :customerId and email_address <> :emailAddress
ON CONFLICT (site_key, list_id, email_address) DO UPDATE SET customer_id = excluded.customer_id
RETURNING site_key, customer_id, email_address, list_id
)
DELETE FROM signup AS signup_delete USING updated_rows
WHERE
signup_delete.site_key = updated_rows.site_key
AND signup_delete.customer_id = updated_rows.customer_id
AND signup_delete.list_id = updated_rows.list_id
AND signup_delete.email_address <> :emailAddress;

提供了两个参数,客户ID和电子邮件地址,此处显示为Spring NamedParameterJdbcTemplate值:customerId :emailAddress

它尝试将客户ID的电子邮件地址更改为提供的电子邮件地址,但有时提供的电子邮件地址已存在于主键约束中。 在这种情况下,它需要更改现有的客户ID,并删除与新电子邮件地址不匹配的行。

我还需要尝试保持隔离,以便在我更新时不会改变数据。 我尝试用MERGE语句来做这件事,但我似乎无法让它发挥作用,它抱怨我不能使用不在条款范围内的值,但我我想我在这里也可能遇到其他问题。

这是我到目前为止所做的。它甚至没有解决删除部分 - 只有upserting,但我甚至无法使这部分工作。我打算使用OUTPUT作为输入来删除类似于postgres版本的行。

WITH source AS (
   SELECT cs.[site_key] as existing_site_key, 
       cs.list_id as existing_list_id, 
       cs.email_address as existing_email,
       cs.customer_id as existing_customer_id,
       cs.attribute1 as existing_attribute1,
       cs.date1 as existing_date1,
       cs.date2 as existing_date2,
       cs2.email_address as conflicting_email,
       cs2.customer_id AS conflicting_customer_id
FROM [dbo].[signup] cs
LEFT JOIN [dbo].[signup] cs2 ON cs2.email_address = :emailAddress
                            AND cs.site_key = cs2.site_key
                            AND cs.list_id = cs2.list_id
WHERE cs.customer_id = :customerId
)
 MERGE signup WITH (HOLDLOCK) AS target 
 USING source 
 ON ( source.conflicting_customer_id is not null ) 
 WHEN MATCHED AND source.existing_site_key = target.site_key AND source.existing_list_id = target.list_id AND source.conflicting_email = target.email_address THEN UPDATE 
    SET customer_id = :customerId
 WHEN NOT MATCHED BY target AND source.existing_site_key = target.site_key AND source.existing_list_id = target.list_id AND source.conflicting_customer_id = :customerId THEN INSERT 
   (site_key, list_id, email_address, customer_id, attribute1, date1, date2) VALUES 
   (source.existing_site_key, source.existing_list_id, :emailAddress, source.customer_id, source.existing_attribute1, source.existing_date1, source.existing_date2)

谢谢, mikee

0 个答案:

没有答案