SQL Server - Как вставить запись и убедиться, что она уникальна - PullRequest
22 голосов
/ 06 ноября 2008

Я пытаюсь найти лучший способ вставить запись в одну таблицу, но только если элемент еще не существует. КЛЮЧ в этом случае является полем NVARCHAR (400). В этом примере давайте представим, что это имя word в Оксфордском словаре английского языка / вставьте здесь ваш любимый словарь. Кроме того, я думаю, мне нужно сделать поле Word первичным ключом. (таблица также будет иметь уникальный идентификатор PK).

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

например.

  • Кошка
  • Собака
  • Foo
  • Бар
  • ПьюПью
  • и т.д ...

Традиционно, я бы попробовал следующее (псевдокод)

SELECT WordID FROM Words WHERE Word = @Word
IF WordID IS NULL OR WordID <= 0
    INSERT INTO Words VALUES (@Word)

т. Если слово не существует, вставьте его.

Теперь ... проблема, о которой я беспокоюсь, заключается в том, что мы получаем МНОГО хитов ... поэтому возможно, что слово может быть вставлено из другого процесса между SELECT и INSERT ..., который затем выбрасывает ошибка ограничения? (т. е. Состояние гонки ).

Тогда я подумал, что смогу сделать следующее ...

INSERT INTO Words (Word)
SELECT @Word
WHERE NOT EXISTS (SELECT WordID FROM Words WHERE Word = @Word)

в основном, вставить слово, когда оно не существует.

Помимо плохого синтаксиса, я не уверен, является ли это плохим или хорошим из-за того, как он блокирует таблицу (если он это делает) и не настолько эффективен для таблицы, что он получает массовое чтение и большое количество записей.

Итак - что вы, Sql гуру, думаете / делаете?

Я надеялся получить простую вставку и «поймать» ее для любых сгенерированных ошибок.

Ответы [ 7 ]

28 голосов
/ 06 ноября 2008

Ваше решение:

INSERT INTO Words (Word)
    SELECT @Word
WHERE NOT EXISTS (SELECT WordID FROM Words WHERE Word = @Word)

... примерно так же хорошо, как и получается. Вы могли бы упростить это до этого:

INSERT INTO Words (Word)
    SELECT @Word
WHERE NOT EXISTS (SELECT * FROM Words WHERE Word = @Word)

... поскольку EXISTS фактически не нужно возвращать какие-либо записи, поэтому оптимизатор запросов не будет беспокоиться о том, какие поля вы запрашивали.

Как вы упоминаете, однако, это не особенно эффективно, потому что оно заблокирует всю таблицу во время INSERT. Кроме того, если вы добавляете уникальный индекс (он не должен быть первичным ключом) в Word, то ему нужно будет только заблокировать соответствующие страницы.

Лучший вариант - смоделировать ожидаемую нагрузку и посмотреть на производительность с помощью SQL Server Profiler. Как и в любой другой области, преждевременная оптимизация - это плохо. Определите приемлемые показатели производительности, а затем измерьте, прежде чем делать что-либо еще.

Если это все еще не дает вам достаточной производительности, то из области хранилища данных может быть полезен целый ряд методов.

4 голосов
/ 20 октября 2011

Я думаю, что нашел лучший (или хотя бы более быстрый) ответ на этот вопрос. Создайте индекс как:

CREATE UNIQUE NONCLUSTERED INDEX [IndexTableUniqueRows] ON [dbo].[table] 
(
    [Col1] ASC,
    [Col2] ASC,

)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = ON, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Включите все столбцы, которые определяют уникальность. Важной частью является IGNORE_DUP_KEY = ON. Это превращает неуникальные вставки в предупреждения. Служба SSIS игнорирует эти предупреждения, и вы по-прежнему можете использовать fastload.

3 голосов
/ 27 мая 2009

У меня была похожая проблема, и вот как я ее решил

insert into Words
( selectWord , Fixword)
SELECT word,'theFixword'
FROM   OldWordsTable
WHERE 
(
    (word LIKE 'junk%') OR
     (word LIKE 'orSomthing') 

)
and word not in 
    (
        SELECT selectWord FROM words WHERE selectWord = word
    ) 
3 голосов
/ 06 ноября 2008

Если вы используете MS SQL Server, вы можете создать уникальный индекс для столбцов таблицы, которые должны быть уникальными (задокументировано здесь ):

CREATE UNIQUE [ CLUSTERED | NONCLUSTERED ] INDEX <index_name>
    ON Words ( word [ ASC | DESC ])

Укажите Clustered или NonClustered, в зависимости от вашего случая. Кроме того, если вы хотите, чтобы он был отсортирован (для ускорения поиска), укажите ASC или DESC для порядка сортировки.

См. здесь , если вы хотите узнать больше об архитектуре индексов.

В противном случае, вы можете использовать UNIQUE CONSTRAINTS, как задокументировано здесь :

ALTER TABLE Words
ADD CONSTRAINT UniqueWord
UNIQUE (Word); 
1 голос
/ 06 ноября 2008

в то время как уникальное ограничение определенно является одним из способов, вы также можете использовать это для логики вставки: http://www.sqlteam.com/article/application-locks-or-mutexes-in-sql-server-2005

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

это мьютекс в коде sql.

0 голосов
/ 06 ноября 2008
declare @Error int

begin transaction
  INSERT INTO Words (Word) values(@word)
  set @Error = @@ERROR
  if @Error <> 0 --if error is raised
  begin
      goto LogError
  end
commit transaction
goto ProcEnd

LogError:
rollback transaction
0 голосов
/ 06 ноября 2008

Я не могу говорить об особенностях MS SQL, но один из пунктов первичного ключа в SQL - это обеспечение уникальности. Таким образом, по определению в общих терминах SQL первичный ключ - это одно или несколько полей, уникальных для таблицы. Несмотря на то, что существуют разные способы обеспечить это поведение (заменить старую запись новой или отвергнуть новую), я был бы удивлен, если бы в MS SQL не было механизма для принудительного применения этого поведения, и что он не был отклонить новую запись. Просто убедитесь, что вы установили первичный ключ в поле Word, и он должен работать.

Еще раз, однако, я отказываюсь, что это все из моих знаний из программирования MySQL и моего класса баз данных, поэтому извиняюсь, если я отключился от тонкостей MS SQL.

...