SQL Server einfügen, falls nicht Best Practice vorhanden

152

Ich habe eine CompetitionsErgebnistabelle, die einerseits die Namen der Teammitglieder und deren Rangliste enthält.

Auf der anderen Seite muss ich eine Tabelle mit eindeutigen Namen von Wettbewerbern führen :

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

Jetzt habe ich ungefähr 200.000 Ergebnisse in der 1. Tabelle und wenn die Wettbewerbstabelle leer ist, kann ich Folgendes ausführen:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

Die Abfrage dauert nur etwa 5 Sekunden, um etwa 11.000 Namen einzufügen.

Bisher ist dies keine kritische Anwendung, daher kann ich in Betracht ziehen, die Wettbewerbstabelle einmal im Monat abzuschneiden, wenn ich die neuen Wettbewerbsergebnisse mit etwa 10.000 Zeilen erhalte.

Aber was ist die beste Vorgehensweise, wenn neue Ergebnisse mit neuen UND bestehenden Wettbewerbern hinzugefügt werden? Ich möchte die vorhandene Wettbewerbstabelle nicht abschneiden

Ich muss die INSERT-Anweisung nur für neue Wettbewerber ausführen und nichts tun, wenn sie vorhanden sind.

Didier Levy
quelle
70
Bitte nicht machen eine NVARCHAR(64)Spalte Ihre primäre (und damit: Clustering) Schlüssel !! Zuallererst - es ist ein sehr breiter Schlüssel - bis zu 128 Bytes; und zweitens ist es eine variable Größe - wieder: nicht optimal ... Dies ist die schlechteste Wahl, die Sie treffen können - Ihre Leistung wird die Hölle sein und die Fragmentierung von Tabellen und Indizes wird die ganze Zeit bei 99,9% liegen .....
marc_s
4
Marc hat einen guten Punkt. Verwenden Sie den Namen nicht als Ihr Paket. Verwenden Sie eine ID, vorzugsweise int oder etwas Leichtes.
Richard
6
Lesen Sie in Kimberly Tripps Blogbeitrag, was einen guten Clustering-Schlüssel ausmacht : einzigartig, eng, statisch, immer größer. Ihr cNameFehler in drei von vier Kategorien ... (es ist nicht eng, es ist wahrscheinlich nicht statisch und es wird definitiv nicht immer größer)
marc_s
Ich sehe keinen Sinn darin, einen INT-Primärschlüssel zur Namenstabelle eines Mitbewerbers hinzuzufügen, in der sich ALLE Abfragen auf dem Namen befinden, z. B. 'WHERE-Name wie'% xxxxx% '', daher benötige ich immer einen eindeutigen Index für den Namen. Aber ja, ich kann den Punkt darin sehen, es NICHT variabel zu machen.
Didier Levy
3
a) Vermeidung von Fragmentierung und b) Wenn es sich um den Fremdschlüssel in anderen Tabellen handelt, sind die duplizierten Daten größer als erforderlich (was eine Geschwindigkeitsüberlegung darstellt)
JamesRyan

Antworten:

213

Semantisch fragen Sie "Konkurrenten einfügen, wo es noch keine gibt":

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
gbn
quelle
2
Nun, das wäre es, was ich getan hätte, bevor ich die Frage auf SO gestellt habe. Aber der Kern meines Gedankens ist: Wie gut wird dies gegen die Neuerstellung der Namenstabelle von Grund auf einmal pro Woche oder so funktionieren? (Denken Sie daran, dies dauert nur ein paar Sekunden)
Didier Levy
3
@Didier Levy: Effizienz? Warum abschneiden, neu erstellen, wenn Sie nur mit den Unterschieden aktualisieren können. Das heißt: BEGIN TRAN DELETE CompResults INSERT CompResults .. COMMIT TRAN = mehr Arbeit.
Gbn
@gbn - Gibt es eine Möglichkeit, die if-else-Logik hier sicher anstelle Ihrer Antwort zu verwenden? Ich habe eine verwandte Frage. Können Sie mir bitte dabei helfen? stackoverflow.com/questions/21889843/…
Steam
53

Eine andere Möglichkeit besteht darin, Ihre Ergebnistabelle mit der Tabelle Ihrer vorhandenen Konkurrenten zu verknüpfen und die neuen Konkurrenten zu finden, indem Sie die unterschiedlichen Datensätze filtern, die nicht mit dem Join übereinstimmen:

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

Die neue Syntax MERGE bietet auch eine kompakte, elegante und effiziente Möglichkeit, dies zu tun:

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
quelle
1
Merge ist in diesem Fall fantastisch, es macht genau das, was es sagt.
VorobeY1326
Ich bin definitiv der Meinung, dass dies der richtige Weg ist, um SQL Server im Gegensatz zum Sub-Query-Ansatz die bestmöglichen Tipps zur Optimierung zu geben.
Mads Nielsen
4
Die MERGE-Erklärung enthält noch viele Probleme. Google einfach "SQL Merge Problems" - viele Blogger haben dies ausführlich diskutiert.
David Wilson
Warum gibt es in der MERGE-Anweisung As Target, in der INSERT-Anweisung jedoch kein Target? Es gibt weitere Unterschiede, die es schwierig machen, die Äquivalenz zu erfassen.
Peter
32

Ich weiß nicht, warum das noch niemand gesagt hat.

NORMALISIEREN.

Sie haben einen Tisch, an dem Wettbewerbe modelliert werden? Wettbewerbe bestehen aus Wettbewerbern? Sie benötigen eine eindeutige Liste der Wettbewerber in einem oder mehreren Wettbewerben ......

Sie sollten die folgenden Tabellen haben .....

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

Mit Einschränkungen für CompetitionCompetitors.CompetitionID und CompetitorID, die auf die anderen Tabellen zeigen.

Mit dieser Art von Tabellenstruktur - Ihre Schlüssel sind alle einfache INTS - scheint es keinen guten NATÜRLICHEN SCHLÜSSEL zu geben, der zum Modell passt, daher denke ich, dass ein SURROGATE-SCHLÜSSEL hier gut passt.

Wenn Sie dies also hatten, um die eindeutige Liste der Wettbewerber in einem bestimmten Wettbewerb zu erhalten, können Sie eine Abfrage wie die folgende ausstellen:

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
        )

Und wenn Sie die Punktzahl für jeden Wettbewerb haben möchten, an dem ein Teilnehmer teilnimmt:

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]

Und wenn Sie einen neuen Wettbewerb mit neuen Wettbewerbern haben, überprüfen Sie einfach, welche bereits in der Wettbewerbstabelle vorhanden sind. Wenn sie bereits vorhanden sind, fügen Sie sie nicht in den Wettbewerber für diese Wettbewerber ein und fügen sie für die neuen ein.

Dann fügen Sie den neuen Wettbewerb in Wettbewerb ein und schließlich stellen Sie einfach alle Links in CompetitionCompetitors her.

Transaktions Charlie
quelle
2
Angenommen, der OP hat zu diesem Zeitpunkt die Leichtigkeit, alle seine Tabellen neu zu strukturieren, um ein zwischengespeichertes Ergebnis zu erhalten. Das Umschreiben Ihrer Datenbank und App, anstatt das Problem innerhalb eines definierten Bereichs zu lösen, ist jedes Mal, wenn etwas nicht einfach zusammenpasst, ein Rezept für eine Katastrophe.
Jeffrey Vest
1
Vielleicht haben Sie im Fall des OP wie meinem nicht immer Zugriff, um die Datenbank zu ändern. UND das Umschreiben / Normalisieren einer alten Datenbank ist nicht immer im Budget oder in der zugewiesenen Zeit enthalten.
eaglei22
10

Sie müssen die Tabellen zusammenfügen und eine Liste der eindeutigen Konkurrenten erhalten, die noch nicht vorhanden sind Competitors.

Dadurch werden eindeutige Datensätze eingefügt.

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

Es kann vorkommen, dass diese Einfügung schnell erfolgen muss, ohne auf die Auswahl eindeutiger Namen warten zu müssen. In diesem Fall können Sie die eindeutigen Namen in eine temporäre Tabelle einfügen und diese temporäre Tabelle dann zum Einfügen in Ihre reale Tabelle verwenden. Dies funktioniert gut, da die gesamte Verarbeitung zum Zeitpunkt des Einfügens in eine temporäre Tabelle erfolgt, sodass Ihre reale Tabelle nicht beeinträchtigt wird. Wenn Sie die gesamte Verarbeitung abgeschlossen haben, fügen Sie sie schnell in die reale Tabelle ein. Ich könnte sogar den letzten Teil, den Sie in die reale Tabelle einfügen, in eine Transaktion einbinden.

Richard
quelle
4

Die Antworten, die über Normalisierung sprechen, sind großartig! Aber was ist, wenn Sie sich in einer Position wie mir befinden, in der Sie das Datenbankschema oder die Datenbankstruktur in ihrer jetzigen Form nicht berühren dürfen? ZB sind die DBAs 'Götter' und alle vorgeschlagenen Revisionen gehen zu / dev / null?

In dieser Hinsicht habe ich das Gefühl, dass dies auch mit diesem Stack Overflow-Posting in Bezug auf alle oben genannten Benutzer beantwortet wurde , die Codebeispiele geben.

Ich reposte den Code von INSERT VALUES WHERE NOT EXISTS, was mir am meisten geholfen hat, da ich keine zugrunde liegenden Datenbanktabellen ändern kann:

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

Der obige Code verwendet andere Felder als das, was Sie haben, aber Sie erhalten den allgemeinen Überblick über die verschiedenen Techniken.

Beachten Sie, dass dieser Code gemäß der ursprünglichen Antwort auf Stack Overflow von hier kopiert wurde .

Mein Punkt ist jedenfalls, dass "Best Practice" oft darauf ankommt, was man kann und was nicht, genauso gut wie auf Theorie.

  • Wenn Sie in der Lage sind, Indizes / Schlüssel zu normalisieren und zu generieren - großartig!
  • Wenn nicht und Sie haben die Möglichkeit, Code-Hacks wie mich zu verwenden, hilft hoffentlich das oben Genannte.

Viel Glück!


quelle
Falls es nicht klar ist, sind dies vier verschiedene Ansätze für das Problem, wählen Sie also einen aus.
Nasch
3

Das Normalisieren Ihrer Betriebstabellen, wie von Transact Charlie vorgeschlagen, ist eine gute Idee und erspart im Laufe der Zeit viele Kopfschmerzen und Probleme. Es gibt jedoch Schnittstellentabellen , die die Integration in externe Systeme unterstützen, und Berichtstabellen , die beispielsweise analytische Funktionen unterstützen wird bearbeitet; und diese Arten von Tabellen sollten nicht unbedingt normalisiert werden - tatsächlich ist es sehr oft viel, viel bequemer und performanter, wenn sie es nicht sind .

In diesem Fall halte ich den Vorschlag von Transact Charlie für Ihre Operationstische für gut.

Ich würde jedoch einen Index (nicht unbedingt eindeutig) zu CompetitorName in der Competitors-Tabelle hinzufügen, um effiziente Verknüpfungen zu CompetitorName zum Zwecke der Integration (Laden von Daten aus externen Quellen) zu unterstützen, und ich würde eine Schnittstellentabelle in den Mix einfügen: CompetitionResults.

Wettbewerbsergebnisse sollten alle Daten enthalten, die Ihre Wettbewerbsergebnisse enthalten. Der Zweck einer solchen Schnittstellentabelle besteht darin, das Abschneiden und erneute Laden aus einer Excel-Tabelle, einer CSV-Datei oder einer beliebigen Form, in der Sie diese Daten haben, so schnell und einfach wie möglich zu gestalten.

Diese Schnittstellentabelle sollte nicht als Teil des normalisierten Satzes von Betriebstabellen betrachtet werden. Anschließend können Sie sich mit CompetitionResults verbinden, wie von Richard vorgeschlagen, um Datensätze in Wettbewerber einzufügen, die noch nicht vorhanden sind, und diejenigen zu aktualisieren, die dies tun (z. B. wenn Sie tatsächlich mehr Informationen über Wettbewerber haben, wie z. B. deren Telefonnummer oder E-Mail-Adresse).

Eine Sache, die ich beachten möchte - in Wirklichkeit ist es sehr unwahrscheinlich, dass der Name des Mitbewerbers in Ihren Daten eindeutig ist . Bei 200.000 Wettbewerbern können Sie beispielsweise zwei oder mehr David Smiths haben. Daher würde ich empfehlen, dass Sie mehr Informationen von Wettbewerbern sammeln, z. B. deren Telefonnummer oder E-Mail-Adresse oder etwas, das mit größerer Wahrscheinlichkeit eindeutig ist.

Ihre Betriebstabelle "Wettbewerber" sollte nur eine Spalte für jedes Datenelement enthalten, das zu einem zusammengesetzten natürlichen Schlüssel beiträgt. Beispielsweise sollte es eine Spalte für eine primäre E-Mail-Adresse geben. Die Schnittstellentabelle sollte jedoch einen Steckplatz für alte und neue Werte für eine primäre E-Mail-Adresse enthalten, damit der alte Wert verwendet werden kann, um den Datensatz in Mitbewerbern nachzuschlagen und diesen Teil auf den neuen Wert zu aktualisieren.

Daher sollten CompetitionResults einige "alte" und "neue" Felder enthalten - oldEmail, newEmail, oldPhone, newPhone usw. Auf diese Weise können Sie in Competitors einen zusammengesetzten Schlüssel aus CompetitorName, Email und Phone bilden.

Wenn Sie dann einige Wettbewerbsergebnisse haben, können Sie Ihre CompetitionResults-Tabelle von Ihrer Excel-Tabelle oder von allem, was Sie haben, abschneiden und neu laden und eine einzelne, effiziente Einfügung ausführen, um alle neuen Wettbewerber in die Competitors-Tabelle einzufügen, sowie eine einzelne, effiziente Aktualisierung, um sie zu aktualisieren Alle Informationen zu den bestehenden Wettbewerbern aus den Wettbewerbsergebnissen. Sie können eine einzelne Einfügung vornehmen, um neue Zeilen in die CompetitionCompetitors-Tabelle einzufügen. Diese Dinge können in einer gespeicherten Prozedur von ProcessCompetitionResults ausgeführt werden, die nach dem Laden der CompetitionResults-Tabelle ausgeführt werden kann.

Dies ist eine Art rudimentäre Beschreibung dessen, was ich in der realen Welt mit Oracle Applications, SAP, PeopleSoft und einer Wäscheliste anderer Unternehmenssoftware-Suiten immer wieder gesehen habe.

Ein letzter Kommentar, den ich machen möchte, ist einer, den ich zuvor zu SO gemacht habe: Wenn Sie einen Fremdschlüssel erstellen, der sicherstellt, dass ein Konkurrent in der Konkurrententabelle vorhanden ist, bevor Sie CompetitionCompetitors eine Zeile mit diesem Konkurrenten hinzufügen können, stellen Sie dies sicher Der Fremdschlüssel ist so eingestellt, dass Aktualisierungen und Löschungen kaskadiert werden . Auf diese Weise können Sie einen Konkurrenten löschen, und alle mit diesem Konkurrenten verknüpften Zeilen werden automatisch gelöscht. Andernfalls müssen Sie für den Fremdschlüssel standardmäßig alle zugehörigen Zeilen aus CompetitionCompetitors löschen, bevor Sie einen Mitbewerber löschen können.

(Einige Leute denken, dass nicht kaskadierende Fremdschlüssel eine gute Sicherheitsmaßnahme sind, aber meine Erfahrung ist, dass sie nur ein verdammter Schmerz im Hintern sind, der meistens nur das Ergebnis eines Versehens ist und eine Menge Arbeit schafft Für DBAs. Der Umgang mit Personen, die versehentlich Inhalte löschen, ist der Grund, warum Sie Dinge wie "Sind Sie sicher" -Dialoge und verschiedene Arten von regelmäßigen Sicherungen und redundanten Datenquellen haben. Es ist weitaus üblicher, einen Konkurrenten tatsächlich löschen zu wollen, dessen Daten alle sind vermasselt zum Beispiel, als es ist, versehentlich einen zu löschen und dann zu sagen: "Oh nein! Ich wollte das nicht tun! Und jetzt habe ich nicht ihre Wettbewerbsergebnisse! Aaaahh!" Letzteres ist sicherlich häufig genug, also Sie müssen darauf vorbereitet sein, aber ersteres ist weitaus häufiger.Der einfachste und beste Weg, sich auf das erstere vorzubereiten, besteht darin, nur Aktualisierungen und Löschungen für Fremdschlüsselkaskaden vorzunehmen.)

Shavais
quelle
1

Ok, dies wurde vor 7 Jahren gefragt, aber ich denke, die beste Lösung besteht darin, auf die neue Tabelle vollständig zu verzichten und dies einfach als benutzerdefinierte Ansicht zu tun. Auf diese Weise duplizieren Sie keine Daten, machen sich keine Sorgen um eindeutige Daten und berühren nicht die tatsächliche Datenbankstruktur. Etwas wie das:

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

Hier können andere Elemente hinzugefügt werden, z. B. Verknüpfungen in anderen Tabellen, WHERE-Klauseln usw. Dies ist höchstwahrscheinlich die eleganteste Lösung für dieses Problem, da Sie jetzt nur die Ansicht abfragen können:

SELECT *
FROM vw_competitions

... und fügen Sie der Ansichtsabfrage alle WHERE-, IN- oder EXISTS-Klauseln hinzu.

Beervenger
quelle