Вставка SQL Server, если не существует, лучшая практика - PullRequest
145 голосов
/ 13 марта 2011

У меня есть таблица результатов 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 только для новых конкурентов и ничего не делать, если они существуют.

Ответы [ 7 ]

197 голосов
/ 13 марта 2011

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

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
52 голосов
/ 13 марта 2011

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

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);
32 голосов
/ 13 марта 2011

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

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]

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

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

10 голосов
/ 13 марта 2011

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

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

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

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

3 голосов
/ 30 июня 2016

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

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

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

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

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

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

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

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

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

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

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

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

1 голос
/ 24 мая 2018

Хорошо, об этом спросили 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 к запросу представления.

1 голос
/ 24 октября 2017

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

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

Я репостирую код из ВСТАВИТЬ ЗНАЧЕНИЯ, ГДЕ НЕ СУЩЕСТВУЕТ , что помогло мне больше всего, так как я не могу изменить какие-либо базовые таблицы базы данных:

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

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

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

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

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

Удачи!

...