在sql事务中多次插入,避免重复键

时间:2014-10-13 11:02:16

标签: sql sql-server

我有一个用于创建单个用户的查询事务。

comapny_id + email应该是唯一的

BEGIN TRANSACTION

IF NOT EXISTS( 
    SELECT * FROM user WHERE email = @email AND company = 'company_id'
) BEGIN

    INSERT INTO user (id, company_id, email, password) 
    VALUES   ( NEWID(), 'company_id', 'email');

    INSERT INTO user_log( id, date, type) VALUES ( 'user_id', SYSUTCDATETIME(), 'created');
    SELECT @i as 'id', 'email' as 'email';

END ELSE BEGIN
  SELECT NULL as 'id', 'email' as email;
END

END TRANSACTION


COMMIT TRANSACTION

如何修改此查询以支持多次插入,以便丢弃重复值并仅插入非重复项。

我有大量用户(5000-10000)。我正在循环user object并使用上面的查询。但它很慢,所以我需要使用多个插入。

所以查询可能是

 // transaction 
 INSERT INTO user(id, company_id, email, password) VALUES (),(),(),() .....

 And also return the ids of the inserted rows

1 个答案:

答案 0 :(得分:3)

您可以使用表值参数将多个值作为参数传递。第一步是创建表类型:

CREATE TYPE dbo.ListOfString AS TABLE (Value VARCHAR(MAX));

然后,您可以创建接受多个电子邮件地址的程序:

CREATE PROCEDURE dbo.InsertUsers @Emails dbo.ListOfString READONLY
AS
BEGIN
    ...
END

然后仅在不存在的情况下执行UPSERT,我所知道的最安全的方法是将MERGEHOLDLOCK一起使用:

MERGE [user] WITH (HOLDLOCK) AS u
USING @Emails AS e
    ON e.Email = u.Email
    AND u.Company = 'company_id'
WHEN NOT MATCHED THEN 
    INSERT (id, company, email) 
    VALUES (NEWID(), 'company_id', e.Email)
OUTPUT inserted.id, SYSUTCDATETIME(), 'created' INTO user_log( id, date, type);

output子句也处理日志中事件的添加。

您可以使用以下内容调用此过程:

DECLARE @NewUser dbo.ListOfString;
INSERT @NewUser (Value)
VALUES ('Test@Test.com'), ('Test2@test.com');

EXECUTE dbo.InsertUser @NewUser;

我可能倾向于将公司作为参数,使您的完整程序如下:

CREATE PROCEDURE dbo.InsertUsers @Emails dbo.ListOfString READONLY, @Company VARCHAR(50)
AS
BEGIN
    MERGE [user] WITH (HOLDLOCK) AS u
    USING @Emails AS e
        ON e.Email = u.Email
        AND u.Company = @Company
    WHEN NOT MATCHED THEN 
        INSERT (id, company, email) 
        VALUES (NEWID(), @Company, e.Email)
    OUTPUT inserted.id, SYSUTCDATETIME(), 'created' INTO user_log( id, date, type);
END

或者,您可以使您的表值参数也包含公司:

CREATE TYPE dbo.NewUser AS TABLE (Email VARCHAR(255), Company VARCHAR(100));

这允许一次将用户添加到两个不同的公司:

CREATE PROCEDURE dbo.InsertUsers @Emails dbo.NewUser READONLY
AS
BEGIN
    MERGE [user] WITH (HOLDLOCK) AS u
    USING @Emails AS e
        ON e.Email = u.Email
        AND u.Company = e.Company
    WHEN NOT MATCHED THEN 
        INSERT (id, company, email) 
        VALUES (NEWID(), e.Company, e.Email)
    OUTPUT inserted.id, SYSUTCDATETIME(), 'created' INTO user_log( id, date, type);
END

最后,如评论中所述。所有这些确保唯一性的工作都很好,但它不能取代独特的约束!无论您的插入方法如何,这都应该到位:

ALTER TABLE dbo.[User]
ADD CONSTRAINT UQ_User__Company_Email UNIQUE (Company, Email);