Как игнорировать ошибку «Дубликат ключа» в T-SQL (SQL Server) - PullRequest
48 голосов
/ 16 июля 2009

У меня есть транзакция, которая содержит несколько операторов SQL (INSERT, UPDATE и / или DELETES). При выполнении я хочу игнорировать операторы Duplicate Error и переходить к следующему оператору. Какой лучший способ сделать это?

Ответы [ 12 ]

63 голосов
/ 16 июля 2009

Я думаю, что вы ищете опцию IGNORE_DUP_KEY в вашем индексе. Посмотрите на опцию IGNORE_DUP_KEY ON, задокументированную на http://msdn.microsoft.com/en-us/library/ms186869.aspx, которая приводит к попыткам двойной вставки выдать предупреждение вместо ошибки.

19 голосов
/ 16 июля 2009

Расширяя свой комментарий к ответу SquareCog, вы можете сделать:

INSERT INTO X VALUES(Y,Z)    WHERE Y  NOT IN (SELECT Y FROM X)
INSERT INTO X2 VALUES(Y2,Z2) WHERE Y2 NOT IN (SELECT Y FROM X2)
INSERT INTO X3 VALUES(Y3,Z3) WHERE Y3 NOT IN (SELECT Y FROM X3)

Здесь я предполагаю, что столбец Y присутствует во всех трех таблицах. Обратите внимание, что производительность будет низкой, если таблицы не проиндексированы по Y.

О да, у Y есть уникальное ограничение - так что они проиндексированы, и это должно работать оптимально.

15 голосов
/ 16 июля 2009

Хотя мой категорический совет для вас состоит в том, чтобы структурировать sql так, чтобы не пытаться повторять вставки (фрагмент кода Филипа Келли, вероятно, вам нужен), я хочу отметить, что ошибка в выражении не обязательно вызывает откат.

Если XACT_ABORT не равно ON, транзакция не будет автоматически откатываться, если возникнет ошибка, если она не будет достаточно серьезной для разрыва соединения. XACT_ABORT по умолчанию OFF.

Например, следующий sql успешно вставляет три значения в таблицу:

create table x ( y int not null primary key )

begin transaction
insert into x(y)
values(1)
insert into x(y)
values(2)
insert into x(y)
values(2)
insert into x(y)
values(3)
commit

Если вы не установили XACT_ABORT, на клиенте возникает ошибка, вызывающая откат. Если по какой-то ужасной причине вы не можете избежать вставки дубликатов, вы должны иметь возможность перехватить ошибку на клиенте и игнорировать ее.

10 голосов
/ 16 июля 2009

Если с помощью «Игнорировать повторяющиеся статистики ошибок», чтобы прервать текущую инструкцию и перейти к следующей инструкции без прерывания операции trnsaction, просто поместите BEGIN TRY .. END TRY вокруг каждой инструкции:

BEGIN TRY
    INSERT ...
END TRY
BEGIN CATCH /*required, but you dont have to do anything */ END CATCH
...
8 голосов
/ 16 декабря 2010

Я бы хотел добавить следующее:

Если 99% ваших данных будут вставлены без ошибок при предварительном выборе, это приведет к значительному падению производительности (как, в моем случае, с 200 строк / сек до 20 строк / сек) по сравнению с «тупыми» вставками и ловит случайную ошибку.

После игнорирования ошибок "Нарушение ограничения PRIMARY KEY" все снова стало узким местом других ресурсов (запас определяется как "то, чего нет у ресурсов узкого места").

Именно по этой причине я и попал в эту дискуссию.

4 голосов
/ 16 июля 2009

OK. Попробовав некоторую обработку ошибок, я понял, как решить возникшую проблему.

Вот пример того, как сделать эту работу (дайте мне знать, если что-то мне не хватает):

SET XACT_ABORT OFF ; -- > really important to set that to OFF
BEGIN
DECLARE @Any_error int
DECLARE @SSQL varchar(4000)
BEGIN TRANSACTION
    INSERT INTO Table1(Value1) VALUES('Value1')
    SELECT @Any_error = @@ERROR
    IF @Any_error<> 0 AND @Any_error<>2627 GOTO ErrorHandler

    INSERT INTO Table1(Value1) VALUES('Value1')
    SELECT @Any_error = @@ERROR
    IF @Any_error<> 0 AND @Any_error<>2627 GOTO ErrorHandler

    INSERT INTO Table1(Value1) VALUES('Value2')
    SELECT @Any_error = @@ERROR
    IF @Any_error<> 0 AND @Any_error<>2627 GOTO ErrorHandler

    ErrorHandler: 
       IF @Any_error = 0 OR @Any_error=2627
       BEGIN 
           PRINT @ssql 
           COMMIT TRAN
       END
       ELSE 
       BEGIN 
           PRINT @ssql 
           ROLLBACK TRAN 
       END
END

В результате вышеупомянутой транзакции таблица 1 будет иметь следующие значения Value1, Value2.

2627, кстати, код ошибки для Duplicate Key.

Спасибо всем за быстрый ответ и полезные предложения.

1 голос
/ 05 июля 2013

Для SQL Server 2000:

     INSERT INTO t1 (ID, NAME)
      SELECT valueid, valuename
      WHERE  NOT EXISTS
               (SELECT 0
                FROM   t1 as t2
                WHERE  t2.ID = valueid AND t2.name = valuename)
1 голос
/ 06 июня 2013

Я пришел сюда, потому что пытался сделать то же самое; Я знал, что у меня есть дубликаты в исходных данных, но я хотел только обновить целевые данные, а не добавлять дубликаты.

Я думаю, что MERGE отлично работает здесь, потому что вы можете ОБНОВИТЬ или УДАЛИТЬ разные вещи и ВСТАВИТЬ пропавшие вещи.

Я закончил тем, что делал это, и это работало великолепно. Я использую SSIS, чтобы пройтись по файлам Excel и загрузить их в «RAW» таблицу SQL с дуплексами и всем остальным. Затем я запускаю MERGE, чтобы объединить «сырую» таблицу с рабочей таблицей. Затем я TRUNCATE «сырой» таблицы и перейти к следующему файлу Excel.

1 голос
/ 08 марта 2011
INSERT INTO KeyedTable(KeyField, Otherfield)
SELECT n.* FROM 
    (SELECT 'PossibleDupeLiteral' AS KeyField, 'OtherfieldValue' AS Otherfield
     UNION ALL
     SELECT 'PossibleDupeLiteral', 'OtherfieldValue2'
    )
LEFT JOIN KeyedTable k
    ON k.KeyField=n.KeyField
WHERE k.KeyField IS NULL

Это говорит серверу искать те же данные (возможно, так же быстро, как и при проверке дубликатов ключей) и ничего не вставлять, если он их находит.

Мне также нравится решение IGNORE_DUP_KEY, но тогда любой, кто полагается на ошибки при обнаружении проблем, будет озадачен, когда сервер молча игнорирует их ошибки двойного ключа.

Причина, по которой я выбрал это решение вместо Филиппа Келли, заключается в том, что вы можете предоставить несколько строк данных и получить только недостающие:

0 голосов
/ 24 июля 2013

Ну, вы можете решить эту проблему с помощью временной таблицы ..

DECLARE @RoleToAdds TABLE
([RoleID] int, [PageID] int)

INSERT INTO @RoleToAdds ([RoleID], [PageID])
    VALUES
    (1, 2),
    (1, 3),
    (1, 4),
    (2, 5)

INSERT INTO [dbo].[RolePages] ([RoleID], [PageID])
    SELECT rta.[RoleID], rta.[PageID] FROM @RoleToAdds rta WHERE NOT EXISTS 
        (SELECT * FROM [RolePages] rp WHERE rp.PageID = rta.PageID AND rp.RoleID = rta.RoleID)

Это может не работать для больших объемов данных, но для нескольких строк это должно работать!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...