web-dev-qa-db-fra.com

SQL Server insère s'il n'existe pas de meilleure pratique

J'ai une table de résultats Competitions qui contient noms des membres de l'équipe et leur classement d'une part.

Par contre, je dois maintenir un tableau des noms de concurrents uniques:

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

Maintenant, j'ai environ 200 000 résultats dans la 1ère table et quand la table des concurrents est vide je peux effectuer ceci:

INSERT INTO Competitors SELECT DISTINCT Name FROM CompResults

Et la requête ne prend que 5 secondes pour insérer environ 11 000 noms.

Jusqu'à présent, il ne s'agit pas d'une application critique et je peux donc envisager de tronquer le tableau des concurrents une fois par mois, lorsque je reçois les nouveaux résultats du concours contenant environ 10 000 lignes.

Mais quelle est la meilleure pratique lorsque de nouveaux résultats sont ajoutés, avec de nouveaux concurrents ET existants? Je ne veux pas tronquer la table des concurrents existants

Je dois exécuter l'instruction INSERT pour les nouveaux concurrents uniquement et ne rien faire s'ils existent.

147
Didier Levy

Sémantiquement, vous demandez "insérer des concurrents là où il n’existe pas déjà":

INSERT Competitors (cName)
SELECT DISTINCT Name
FROM CompResults cr
WHERE
   NOT EXISTS (SELECT * FROM Competitors c
              WHERE cr.Name = c.cName)
202
gbn

Une autre option consiste à laisser rejoindre votre table de résultats à votre table de concurrents existants et à rechercher les nouveaux concurrents en filtrant les enregistrements distincts qui ne correspondent pas à la jointure:

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

La nouvelle syntaxe MERGE offre également un moyen compact, élégant et efficace de le faire:

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);
52
pcofre

Je ne sais pas pourquoi quelqu'un d'autre ne l'a pas encore dit.

NORMALISER.

Vous avez une table qui modélise les compétitions? Les compétitions sont composées de concurrents? Vous avez besoin d'une liste distincte de concurrents dans une ou plusieurs compétitions ......

Vous devriez avoir les tables suivantes .....

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

Avec des contraintes sur CompetitionCompetitors.CompetitionID et CompetitorID pointant vers les autres tables.

Avec ce type de structure de table - vos clés sont toutes simples INTS - il ne semble pas y avoir une bonne clé naturelle qui conviendrait au modèle, donc je pense qu'une clé SURROGATE est un bon choix ici.

Donc, si vous aviez ceci, alors pour obtenir la liste distincte des concurrents dans une compétition particulière, vous pouvez émettre une requête comme celle-ci:

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
        )

Et si vous vouliez connaître le score de chaque compétition, un compétiteur est:

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]

Et lorsque vous avez une nouvelle compétition avec de nouveaux concurrents, il vous suffit de vérifier ceux qui existent déjà dans la table des concurrents. S'ils existent déjà, vous n'insérez pas dans Competitor pour ces concurrents et ne l'insérez pas pour les nouveaux.

Ensuite, vous insérez la nouvelle compétition en compétition et vous créez enfin tous les liens dans CompetitionCompetitors.

32
Transact Charlie

Vous devrez joindre les tables ensemble et obtenir une liste de concurrents uniques qui n'existent pas déjà dans Competitors.

Cela va insérer des enregistrements uniques.

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

À un moment donné, cette insertion devra peut-être être effectuée rapidement sans pouvoir attendre la sélection de noms uniques. Dans ce cas, vous pouvez insérer les noms uniques dans une table temporaire, puis utiliser cette table temporaire pour l'insérer dans votre table réelle. Cela fonctionne bien car tout le traitement a lieu au moment de l'insertion dans une table temporaire, de sorte que cela n'affecte pas votre table réelle. Ensuite, lorsque tous les traitements sont terminés, vous effectuez une insertion rapide dans la table réelle. Je pourrais même envelopper la dernière partie, où vous insérez dans la table réelle, à l'intérieur d'une transaction.

10
richard

La normalisation de vos tableaux opérationnels suggérés par Transact Charlie est une bonne idée et vous évitera de nombreux maux de tête et problèmes au fil du temps - mais il existe des éléments tels que des tables d'interface prenant en charge l'intégration avec des systèmes externes et des tables de reporting , qui soutenir des choses comme le traitement analytique; et ces types de tables devraient ne doit pas nécessairement être normalisé - en fait, très souvent, il est beaucoup, beaucoup plus pratique et performant pour eux de ne pas être .

Dans ce cas, je pense que la proposition de Transact Charlie pour vos tables opérationnelles est bonne.

Mais j'ajouterais un index (pas nécessairement unique) à CompetitorName dans la table Concurrents pour prendre en charge les jointures efficaces sur CompetitorName à des fins d'intégration (chargement de données provenant de sources externes), et je mettrais une table d'interface dans la combinaison: CompetitionResults.

CompetitionResults doit contenir toutes les données de vos résultats de compétition. L’intérêt d’une table d’interface comme celle-ci est de rendre aussi simple et rapide que possible le tronquer et le recharger à partir d’une feuille Excel, d’un fichier CSV ou de toute autre forme dans laquelle vous avez ces données.

Cette table d'interface ne doit pas être considérée comme faisant partie de l'ensemble normalisé de tables opérationnelles. Ensuite, vous pouvez vous joindre à CompetitionResults, comme suggéré par Richard, pour insérer des enregistrements dans les concurrents. qui n'existent pas déjà et mettez à jour ceux qui existent (par exemple, si vous avez plus d'informations sur les concurrents, comme leur numéro de téléphone ou leur adresse électronique).

Une chose que je voudrais noter - en réalité, le nom du concurrent, il me semble, est très peu probable d'être unique dans vos données . Sur 200 000 concurrents, vous pouvez très bien avoir 2 David Smith ou plus, par exemple. Je vous recommande donc de collecter davantage d'informations auprès des concurrents, telles que leur numéro de téléphone ou une adresse électronique, ou quelque chose qui est plus susceptible d'être unique.

Votre table opérationnelle, concurrents, ne devrait contenir qu'une colonne pour chaque élément de données contribuant à une clé naturelle composite. Par exemple, il devrait contenir une colonne pour une adresse électronique principale. Mais la table d’interface doit avoir un emplacement pour ancien et nouvelles valeurs pour une adresse électronique principale, de sorte que l'ancienne valeur puisse être utilisée pour rechercher l'enregistrement dans Concurrents et en mettre à jour cette partie avec la nouvelle valeur.

Ainsi, CompetitionResults devrait comporter des "anciens" et des "nouveaux" champs: ancienEmail, newEmail, ancienPhone, nouveauPhone, etc. De cette manière, vous pouvez former une clé composite dans Competitors, à partir de CompetitorName, Email et Phone.

Ensuite, lorsque vous obtenez des résultats de compétition, vous pouvez tronquer et recharger votre table CompetitionResults à partir de votre feuille Excel ou autre, et exécuter un seul insert efficace pour insérer tous les nouveaux concurrents dans la table Concurrents, ainsi qu'une mise à jour simple et efficace pour la mise à jour. toutes les informations sur les concurrents existants dans CompetitionResults. Et vous pouvez faire un seul insert pour insérer de nouvelles lignes dans la table CompetitionCompetitors. Ces opérations peuvent être effectuées dans une procédure stockée ProcessCompetitionResults, qui peut être exécutée après le chargement de la table CompetitionResults.

Il s’agit d’une sorte de description rudimentaire de ce que j’ai vu se répéter dans le monde réel avec Oracle Applications, SAP, PeopleSoft et une liste exhaustive de suites logicielles d’entreprise.

Un dernier commentaire que je ferais est celui que j’avais déjà fait sur SO: si vous créez une clé étrangère qui garantit qu’un concurrent existe dans la table Concurrents avant de pouvoir ajouter une ligne contenant ce concurrent aux concurrents, assurez-vous que la clé étrangère est définie sur les mises à jour et les suppressions en cascade . Ainsi, si vous devez supprimer un concurrent, vous pouvez le faire et toutes les lignes associées à ce concurrent seront automatiquement supprimées. Sinon, par défaut, la clé étrangère vous demandera de supprimer toutes les lignes associées de CompetitionCompetitors avant de vous permettre de supprimer un concurrent.

(Certaines personnes pensent que les clés étrangères sans cascade sont une bonne mesure de sécurité, mais d’après mon expérience, c’est une douleur insupportable qui résulte le plus souvent d’un simple oubli et qui crée beaucoup de travail. C'est pourquoi vous avez des choses comme "êtes-vous sûr" de dialogues et divers types de sauvegardes régulières et de sources de données redondantes? C'est beaucoup plus courant de vouloir supprimer un concurrent dont les données sont toutes foiré, par exemple, que supprimer accidentellement un mot, puis aller "Oh non! Je ne voulais pas faire cela! Et maintenant je n'ai pas les résultats de la compétition! Aaaahh!" Ce dernier est certainement assez commun, alors , vous devez vous y préparer, mais le premier est beaucoup plus courant. Le moyen le plus simple et le meilleur de se préparer au premier, imo, consiste simplement à mettre à jour et à supprimer des clés étrangères.)

3
Shavais

Ok, cela a été demandé il y a 7 ans, mais je pense que la meilleure solution est de renoncer entièrement à la nouvelle table et de le faire comme une vue personnalisée. Ainsi, vous ne dupliquez pas les données, vous ne craignez pas les données uniques et elles ne touchent pas la structure réelle de la base de données. Quelque chose comme ça:

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

D'autres éléments peuvent être ajoutés ici, tels que des jointures sur d'autres tables, des clauses WHERE, etc. Il s'agit probablement de la solution la plus élégante à ce problème, car vous pouvez simplement interroger la vue:

SELECT *
FROM vw_competitions

... et ajoutez les clauses WHERE, IN ou EXISTS à la requête d'affichage.

1
Beervenger

Les réponses ci-dessus qui parlent de normalisation sont excellentes! Mais que se passe-t-il si vous vous trouvez dans une position telle que moi où vous n'êtes pas autorisé à toucher au schéma de la base de données ou à la structure actuelle? Par exemple, les administrateurs de bases de données sont des "dieux" et toutes les révisions suggérées vont dans/dev/null?

À cet égard, je me sens comme ceci il a été répondu avec cette publication de débordement de pile aussi en ce qui concerne tous les utilisateurs ci-dessus donnant des exemples de code.

Je republie le code de INSERT VALUES WHERE NOT EXISTS , ce qui m'a le plus aidé car je ne peux modifier aucune table de base de données sous-jacente:

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

Le code ci-dessus utilise des champs différents de ceux que vous avez, mais vous obtenez le résumé général avec les différentes techniques.

Notez que conformément à la réponse d'origine sur Stack Overflow, ce code était copié à partir d'ici .

Quoi qu'il en soit, mon propos est que la "meilleure pratique" revient souvent à ce que vous pouvez et ne pouvez pas faire aussi bien que la théorie.

  • Si vous êtes capable de normaliser et de générer des index/clés, c'est parfait!
  • Si ce n'est pas le cas et que vous avez recours à des codes comme moi, j'espère que ce qui précède aide.

Bonne chance!

1
user3810913