Вставка SQL Server, если не существует

152

У меня есть Competitionsтаблица результатов, которая содержит имена членов команды и их рейтинг с одной стороны.

С другой стороны, мне нужно вести таблицу уникальных имен конкурентов :

CREATE TABLE Competitors (cName nvarchar(64) primary key)

Теперь у меня есть около 200 000 результатов в 1-й таблице, и когда таблица участников пуста, я могу выполнить это:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

И запрос занимает около 5 секунд, чтобы вставить около 11 000 имен.

Пока это не является критически важным приложением, поэтому я могу рассмотреть усечение таблицы «Конкуренты» один раз в месяц, когда я получаю новые результаты соревнования с примерно 10 000 строк.

Но какова лучшая практика, когда добавляются новые результаты с новыми И существующими конкурентами? Я не хочу урезать существующую таблицу конкурентов

Мне нужно выполнить инструкцию INSERT только для новых конкурентов и ничего не делать, если они существуют.

Дидье Леви
источник
70
Пожалуйста, не делайте NVARCHAR(64)столбец вашим первичным (и, следовательно, кластеризованным) ключом !! Прежде всего - это очень широкий ключ - до 128 байт; а во-вторых, это переменный размер - опять же: не оптимально ... Это самый худший из возможных вариантов - ваша производительность будет ужасной, а фрагментация таблиц и индексов будет постоянно на уровне 99,9% .....
marc_s
4
Марк имеет хорошую точку зрения. Не используйте имя в качестве вашего ПК. Используйте id, предпочтительно int или что-то более легкое.
Ричард
6
Посмотрите сообщение в блоге Кимберли Триппа о том, что делает хороший ключ кластеризации: уникальный, узкий, статичный, постоянно растущий. Ваш cNameпровал в трех из четырех категорий .... (он не узкий, он, вероятно, не статичен и определенно не увеличивается)
marc_s
Я не вижу смысла в добавлении первичного ключа INT в таблицу имени участника, где ВСЕ запросы будут относиться к имени, например, «ГДЕ имя, как«% xxxxx% », поэтому мне всегда нужен уникальный индекс для имени. Но да, я вижу смысл в том, чтобы НЕ делать переменную длину ..
Дидье Леви
3
а) избегая фрагментации и б) если это внешний ключ в других таблицах, дублированные данные больше, чем необходимые (что является соображением скорости)
JamesRyan

Ответы:

214

Семантически вы просите «вставить конкурентов там, где их еще нет»:

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
ГБН
источник
2
Ну, это то, что я бы сделал, прежде чем задавать вопрос на SO. Но суть моей мысли такова: насколько хорошо это будет работать против восстановления таблицы имен с нуля раз в неделю или около того? (помните, что это займет всего несколько секунд)
Дидье Леви
3
@Didier Levy: эффективность? Зачем усекать, воссоздавать, когда вы можете обновить только с различиями. То есть: BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN = больше работы.
ГБН
@ gbn - Есть ли способ безопасно использовать логику if-else вместо вашего ответа? У меня есть связанный вопрос. Можете ли вы помочь мне с этим? stackoverflow.com/questions/21889843/…
Steam
53

Другим вариантом является объединение таблицы результатов с таблицей существующих конкурентов слева и поиск новых конкурентов путем фильтрации отдельных записей, которые не соответствуют в объединении:

INSERT Competitors (cName)
SELECT  DISTINCT cr.Name
FROM    CompResults cr left join
        Competitors c on cr.Name = c.cName
where   c.cName is null

Новый синтаксис MERGE также предлагает компактный, элегантный и эффективный способ сделать это:

MERGE INTO Competitors AS Target
USING (SELECT DISTINCT Name FROM CompResults) AS Source ON Target.Name = Source.Name
WHEN NOT MATCHED THEN
    INSERT (Name) VALUES (Source.Name);
pcofre
источник
1
Слияние - это круто в этом случае, оно делает именно то, что говорит.
VorobeY1326
Я определенно считаю, что это правильный путь, предоставляющий SQL Server лучшие подсказки для оптимизации, в отличие от подхода с подзапросом.
Мадс Нильсен
4
В заявлении MERGE все еще много проблем. Просто Google "Проблемы слияния SQL" - многие блоггеры обсуждали это подробно.
Дэвид Уилсон
почему в операторе MERGE есть как цель, а в инструкции INSERT нет цели? Есть больше различий, которые затрудняют понимание эквивалентности.
Питер
32

Не знаю, почему кто-то еще не сказал этого;

NORMALIZE.

У вас есть стол, который моделирует соревнования? Конкурсы состоят из конкурентов? Вам нужен четкий список участников в одном или нескольких соревнованиях ......

Вы должны иметь следующие таблицы .....

CREATE TABLE Competitor (
    [CompetitorID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitorName] NVARCHAR(255)
    )

CREATE TABLE Competition (
    [CompetitionID] INT IDENTITY(1,1) PRIMARY KEY
    , [CompetitionName] NVARCHAR(255)
    )

CREATE TABLE CompetitionCompetitors (
    [CompetitionID] INT
    , [CompetitorID] INT
    , [Score] INT

    , PRIMARY KEY (
        [CompetitionID]
        , [CompetitorID]
        )
    )

С Ограничениями на CompetitionCompetitors.CompetitionID и CompetitorID, указывающими на другие таблицы.

С такой структурой таблиц - все ваши ключи - простые INTS - кажется, что не существует хорошего ЕСТЕСТВЕННОГО КЛЮЧА, который бы подходил модели, поэтому я думаю, что СЮРРОГАТИЧЕСКИЙ КЛЮЧ подходит здесь.

Таким образом, если у вас было это, чтобы получить отдельный список конкурентов в конкретном конкурсе, вы можете выполнить запрос, подобный этому:

DECLARE @CompetitionName VARCHAR(50) SET @CompetitionName = 'London Marathon'

    SELECT
        p.[CompetitorName] AS [CompetitorName]
    FROM
        Competitor AS p
    WHERE
        EXISTS (
            SELECT 1
            FROM
                CompetitionCompetitor AS cc
                JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]
            WHERE
                cc.[CompetitorID] = p.[CompetitorID]
                AND cc.[CompetitionName] = @CompetitionNAme
        )

И если вы хотите получить оценку за каждый турнир, в котором участвует участник:

SELECT
    p.[CompetitorName]
    , c.[CompetitionName]
    , cc.[Score]
FROM
    Competitor AS p
    JOIN CompetitionCompetitor AS cc ON cc.[CompetitorID] = p.[CompetitorID]
    JOIN Competition AS c ON c.[ID] = cc.[CompetitionID]

А когда у вас новый конкурс с новыми конкурентами, вы просто проверяете, какие из них уже существуют, в таблице «Конкуренты». Если они уже существуют, вы не вставляете их в список «Конкуренты» и добавляете новых.

Затем вы вставляете новый Конкурс в Конкурс и, наконец, просто делаете все ссылки в Конкурсе Конкурентов.

Transact Charlie
источник
2
Предполагая, что в данный момент у ОП есть возможность реструктурировать все свои таблицы, чтобы получить один кэшированный результат. Переписывание вашей базы данных и приложения вместо решения проблемы в рамках определенной области, каждый раз, когда что-то легко не встает на свои места, - это путь к катастрофе.
Джеффри Вест
1
Возможно, в случае с OP, как у меня, у вас не всегда есть доступ для изменения базы данных ... И переписывание / нормализация старой базы данных не всегда в бюджете или отведенном времени.
eaglei22
10

Вам нужно будет объединить столы и получить список уникальных конкурентов, которые еще не существуют в Competitors.

Это вставит уникальные записи.

INSERT Competitors (cName) 
SELECT DISTINCT Name
FROM CompResults cr LEFT JOIN Competitors c ON cr.Name = c.cName
WHERE c.Name IS NULL

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

Ричард
источник
4

Ответы выше, которые говорят о нормализации, великолепны! Но что, если вы окажетесь в таком положении, как я, где вам не разрешено касаться схемы или структуры базы данных в том виде, в каком она есть? Например, администраторы баз данных - «боги», и все предлагаемые изменения идут в / dev / null?

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

Я репостирую код из INSERT VALUES WHERE NOT EXISTS, который мне больше всего помог, так как я не могу изменить какие-либо базовые таблицы базы данных:

INSERT INTO #table1 (Id, guidd, TimeAdded, ExtraData)
SELECT Id, guidd, TimeAdded, ExtraData
FROM #table2
WHERE NOT EXISTS (Select Id, guidd From #table1 WHERE #table1.id = #table2.id)
-----------------------------------
MERGE #table1 as [Target]
USING  (select Id, guidd, TimeAdded, ExtraData from #table2) as [Source]
(id, guidd, TimeAdded, ExtraData)
    on [Target].id =[Source].id
WHEN NOT MATCHED THEN
    INSERT (id, guidd, TimeAdded, ExtraData)
    VALUES ([Source].id, [Source].guidd, [Source].TimeAdded, [Source].ExtraData);
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT id, guidd, TimeAdded, ExtraData from #table2
EXCEPT
SELECT id, guidd, TimeAdded, ExtraData from #table1
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT #table2.id, #table2.guidd, #table2.TimeAdded, #table2.ExtraData
FROM #table2
LEFT JOIN #table1 on #table1.id = #table2.id
WHERE #table1.id is null

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

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

В любом случае, моя точка зрения - «лучшая практика», часто сводится к тому, что вы можете и не можете сделать, а не к теории.

  • Если вы можете нормализовать и генерировать индексы / ключи - отлично!
  • Если нет, и у вас есть возможность взломать код, как я, надеюсь, что вышеупомянутое поможет.

Удачи!


источник
В случае, если неясно, это четыре разных подхода к проблеме, поэтому выберите один.
nasch
3

Нормализация ваших операционных таблиц, как предлагает Transact Charlie, является хорошей идеей и со временем избавит от многих головных болей и проблем, но есть такие вещи, как интерфейсные таблицы, которые поддерживают интеграцию с внешними системами, и таблицы отчетов , которые поддерживают такие вещи, как аналитика обработки; и эти типы таблиц не обязательно должны быть нормализованы - на самом деле, очень часто это гораздо, гораздо удобнее и эффективнее для них не быть .

В этом случае, я думаю, предложение Transact Charlie для ваших операционных столов является хорошим.

Но я бы добавил индекс (не обязательно уникальный) к CompetitorName в таблице Competitors для поддержки эффективных объединений на CompetitorName в целях интеграции (загрузки данных из внешних источников), и я бы добавил в интерфейс таблицу интерфейсов: CompetitionResults.

CompetitionResults должен содержать любые данные о ваших соревнованиях. Смысл таблицы интерфейса, подобной этой, состоит в том, чтобы максимально быстро и просто обрезать и перезагружать ее из листа Excel, файла CSV или любой другой формы, в которой вы храните эти данные.

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

Я хотел бы отметить одну вещь - на самом деле, имя конкурента, как мне кажется, вряд ли будет уникальным в ваших данных . Например, из 200 000 участников у вас может быть 2 или более Дэвида Смита. Поэтому я бы порекомендовал вам собрать больше информации от конкурентов, например, номер телефона или адрес электронной почты, или что-то, что с большей вероятностью будет уникальным.

В вашей операционной таблице «Конкуренты» должен быть только один столбец для каждого элемента данных, который вносит вклад в составной естественный ключ; например, он должен иметь один столбец для основного адреса электронной почты. Но в таблице интерфейса должен быть слот для старых и новых значений для основного адреса электронной почты, чтобы старое значение можно было использовать для поиска записи в конкурентах и ​​обновления этой части до нового значения.

Таким образом, CompetitionResults должны иметь некоторые «старые» и «новые» поля - oldEmail, newEmail, oldPhone, newPhone и т. Д. Таким образом, вы можете сформировать составной ключ в Competitors, от CompetitorName, Email и Phone.

Затем, когда у вас есть результаты соревнований, вы можете обрезать и перезагрузить таблицу CompetitionResults из таблицы Excel или чего-то еще, и запустить одну эффективную вставку, чтобы вставить всех новых конкурентов в таблицу Конкурентов, и одно эффективное обновление для обновления. Вся информация о существующих конкурентах из CompetitionResults. И вы можете сделать одну вставку, чтобы вставить новые строки в таблицу CompetitionCompetitors. Это можно сделать с помощью хранимой процедуры ProcessCompetitionResults, которая может быть выполнена после загрузки таблицы CompetitionResults.

Это своего рода элементарное описание того, что я много раз делал в реальном мире с Oracle Applications, SAP, PeopleSoft и списком других корпоративных программных пакетов.

Последний комментарий, который я хотел бы сделать, - это то, что я сделал ранее для SO: Если вы создаете внешний ключ, который гарантирует, что в таблице «Конкуренты» существует конкурент, прежде чем вы сможете добавить строку с этим «конкурентом» в CompetitionCompetitors, убедитесь, что внешний ключ установлен для каскадного обновления и удаления . Таким образом, если вам нужно удалить конкурента, вы можете сделать это, и все строки, связанные с этим конкурентом, будут автоматически удалены. В противном случае по умолчанию внешний ключ потребует от вас удаления всех связанных строк из CompetitionCompetitors, прежде чем он позволит вам удалить конкурента.

(Некоторые люди думают, что не каскадные внешние ключи - это хорошая мера предосторожности, но мой опыт показывает, что это просто ужасная боль в заднице, которая чаще всего не является результатом недосмотра, и они создают кучу трудовой работы. для администраторов баз данных. Работа с людьми, случайно удаляющими материал, - вот почему у вас есть такие вещи, как диалоги «Вы уверены» и различные типы регулярных резервных копий и избыточных источников данных? Гораздо чаще встречается фактическое желание удалить конкурента, чьи данные все напутал, к примеру, чем это случайно удалить одно и потом сказать: «О, нет! Я не хотел этого делать! А теперь у меня нет результатов их соревнований! А-а-а-а!» Последнее, конечно, достаточно распространено, поэтому Вы должны быть готовы к этому, но первое гораздо более распространено,так что самый простой и лучший способ подготовиться к первому, imo, это просто сделать каскадные обновления и удаления внешних ключей.)

Shavais
источник
1

Хорошо, об этом спросили 7 лет назад, но я думаю, что лучшее решение здесь - полностью отказаться от новой таблицы и просто сделать это как пользовательское представление. Таким образом, вы не дублируете данные, вам не нужно беспокоиться об уникальных данных, и это не затрагивает фактическую структуру базы данных. Что-то вроде этого:

CREATE VIEW vw_competitions
  AS
  SELECT
   Id int
   CompetitionName nvarchar(75)
   CompetitionType nvarchar(50)
   OtherField1 int
   OtherField2 nvarchar(64)  --add the fields you want viewed from the Competition table
  FROM Competitions
GO

Здесь можно добавить другие элементы, такие как объединения в другие таблицы, предложения WHERE и т. Д. Это, скорее всего, наиболее элегантное решение этой проблемы, поскольку теперь вы можете просто запросить представление:

SELECT *
FROM vw_competitions

... и добавьте любые предложения WHERE, IN или EXISTS в запрос представления.

Beervenger
источник