Якщо не існує найкращої практики, вставити SQL Server


152

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

З іншого боку, мені потрібно підтримувати таблицю унікальних імен конкурентів :

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

Зараз я маю 200 000 результатів у першій таблиці, і коли таблиця конкурентів порожня, я можу виконати це:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

А на запит потрібно лише 5 секунд, щоб вставити близько 11 000 імен.

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

Але яка найкраща практика, коли додаються нові результати з новими І існуючими конкурентами? Я не хочу усікати таблицю конкурентів

Мені потрібно виконати заяву INSERT лише для нових конкурентів і нічого не робити, якщо вони є.


70
Будь ласка, не робіть NVARCHAR(64)стовпчик своїм основним (і таким чином: кластеризацією) ключем !! Перш за все - це дуже широкий ключ - до 128 байт; по-друге, це мінливий розмір - знову ж таки: не оптимальний ... Це про найгірший вибір, який ви можете мати - ваша продуктивність буде пекла, а фрагментація таблиць та індексів буде весь час на рівні 99,9% .....
marc_s

4
У Марка є хороший момент. Не використовуйте ім'я як свій ПК. Використовуйте ідентифікатор, бажано інт або щось легке.
Річард

6
Дивіться повідомлення в блозі Кімберлі Трипп про те, що робить хороший кластерний ключ: унікальний, вузький, статичний, постійно зростаючий. Ваш cNameзбій у трьох з чотирьох категорій .... (це не вузько, він, мабуть, не є статичним, і це, безумовно, не постійно збільшується)
marc_s

Я не бачу сенсу в додаванні первинного ключа INT до таблиці імен конкурента, де ВСІ запити будуть назви, наприклад "WHERE name like '% 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 CompResulta .. COMMIT TRAN = більше роботи.
gbn

@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);

1
Злиття в цьому випадку дивовижне, воно робить саме те, що говорить.
VorobeY1326

Я, безумовно, вважаю, що це правильний шлях, який дає SQL Server найкращі підказки для оптимізації на відміну від підпиту.
Мадс Нільсен

4
Заява MERGE все ще має багато питань. Просто в google "Проблеми злиття SQL" - багато блогерів обговорювали це детально.
Девід Вілсон

чому в операторі MERGE є As Target, а в операторі INSERT немає Target? Існує більше відмінностей, які ускладнюють розуміння еквівалентності.
Пітер

32

Не знаю, чому ще ніхто цього не сказав;

НОРМАЛІЗАЦІЯ.

У вас є таблиця, яка моделює змагання? Змагання складаються з конкурентів? Вам потрібен чіткий список учасників змагань на одному чи кількох змаганнях ......

У вас повинні бути такі таблиці .....

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]
        )
    )

З обмеженнями на конкуренцію конкурентів. КонкуренціяID та конкурент ID вказує на інші таблиці.

З такою структурою таблиці - ваші клавіші - це просто 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]

А коли у вас є нова конкуренція з новими конкурентами, ви просто перевіряєте, які з них вже є в таблиці конкурентів. Якщо вони вже існують, ви не вставляєте в Конкуренту цих конкурентів і не вставляєте нових.

Потім ви вставляєте новий конкурс у змаганнях, і, нарешті, ви просто робите всі посилання в конкурсі конкурентів.


2
Якщо припустити, що в цей час ОП має легкість реструктурувати всі свої таблиці, щоб отримати один кешований результат. Переписуючи свій db та додаток, замість того, щоб вирішувати проблему в певному обсязі, кожен раз, коли щось не стане на місце легко, - це рецепт катастрофи.
Jeffrey Vest

1
Можливо, у випадку з ОП, як у мене, ви не завжди маєте доступ до зміни бази даних. І переписування / нормалізація старої бази даних не завжди входить у бюджет або відведений час.
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

Відповіді, вище про які йдеться про нормалізацію, чудові! Але що робити, якщо ви опинитесь у такому положенні, як я, коли вам не дозволяється торкатися схеми або структури бази даних, як вона є? Наприклад, DBA є "богами", і всі запропоновані зміни переходять на / dev / null?

У цьому відношенні я відчуваю, що на це відповіли і в цій публікації про переповнення стека стосовно всіх користувачів, які надавали вище зразки коду.

Я повертаю код з INSERT VALUES, КОГО НЕ існує, що мені найбільше допомогло, оскільки я не можу змінити будь-які основні таблиці баз даних:

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.

Конкурсні результати повинні містити будь-які дані, що містять у вас результати змагань. Суть таблиці такої інтерфейсу, як ця, полягає в тому, щоб зробити її максимально швидкою і простою для обрізання та перезавантаження з аркуша Excel або файлу CSV або будь-якої форми, в якій ви маєте ці дані.

Ця інтерфейсна таблиця не повинна вважатися частиною нормованого набору операційних таблиць. Тоді ви можете приєднатися до CompetitionResults, як запропонував Річард, вставити записи в конкурентів, які вже не існують, та оновити ті, що є (наприклад, якщо у вас є додаткова інформація про конкурентів, наприклад їх номер телефону чи електронну адресу).

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

Ваш операційний стіл, Конкуренти, повинен мати лише один стовпець для кожного елемента даних, який сприяє складенню природного ключа; наприклад, він повинен мати один стовпчик для основної адреси електронної пошти. Але таблиця інтерфейсів повинна мати слот для старих і нових значень для первинної адреси електронної пошти, щоб старе значення можна було використовувати для пошуку запису у конкурентів та оновлення цієї частини до нового значення.

Тож у конкурсних результатів повинні бути деякі "старі" та "нові" поля - oldEmail, newEmail, oldPhone, newPhone тощо. Таким чином, ви можете сформувати складений ключ у конкурентів від CompetitorName, Email та Phone.

Потім, коли у вас є деякі результати змагань, ви можете скоротити та перезавантажити свою таблицю CompetitionResults зі свого листа excel або будь-якого іншого, і запустити єдину ефективну вставку для вставки всіх нових конкурентів у таблицю конкурентів та єдине ефективне оновлення для оновлення. всю інформацію про існуючих конкурентів з конкурсних результатів. І ви можете зробити одну вставку, щоб вставити нові рядки в таблицю CompetitionCompetitors. Ці речі можна зробити в збереженій процедурі ProcessCompressionResults, яка може бути виконана після завантаження таблиці CompetitionResults.

Це своєрідний рудиментарний опис того, що я спостерігав у реальному світі за допомогою Oracle Applications, SAP, PeopleSoft та списку білизни інших програмних програм для підприємств.

Останнє зауваження, яке я зробив, - це те, що я робив раніше про SO: Якщо ви створюєте зовнішній ключ, який гарантує існування конкурента в таблиці конкурентів, перш ніж ви зможете додати рядок із цим конкурентом до нього, переконайтеся, що зовнішній ключ встановлюється для каскадного оновлення та видалення . Таким чином, якщо вам потрібно видалити конкурента, ви можете це зробити, і всі рядки, пов’язані з цим конкурентом, будуть автоматично видалені. В іншому випадку за замовчуванням зовнішній ключ вимагатиме видалення всіх пов’язаних рядків із конкурентів конкурентів, перш ніж він дозволить вам видалити конкурента.

(Деякі люди вважають, що не каскадні закордонні ключі є гарною запобіжною безпекою, але мій досвід полягає в тому, що вони просто страхітливий біль у попці, які частіше за все є просто результатом нагляду, і вони створюють купу робочих місць Для користувачів DBA. У взаємодії з людьми, які випадково видаляють речі, тому у вас є такі речі, як "Ви впевнені", діалоги та різні типи регулярних резервних копій та зайвих джерел даних. Насправді, набагато частіше зустрічатися з видаленням конкурента, чиї дані - все заплутався, наприклад, ніж випадково видалити його, а потім перейти "О, ні! Я не мав цього робити! І тепер я не маю їх результатів змагань! Аааах!" Останнє, звичайно, досить поширене, так вам потрібно бути готовим до цього, але це набагато частіше,тож найпростіший і найкращий спосіб підготуватися до першого, imo, - це просто зробити оновлення та видалення каскаду зовнішніх ключів.)


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 до запиту перегляду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.