web-dev-qa-db-fra.com

SQL Server - Comment insérer un enregistrement et s'assurer qu'il est unique

J'essaie de trouver le meilleur moyen d'insérer un enregistrement dans une seule table, mais uniquement si l'élément n'existe pas déjà. La clé dans ce cas est un champ NVARCHAR (400). Pour cet exemple, supposons que c'est le nom d'un Word dans le dictionnaire anglais Oxford/insérez votre dictionnaire préféré ici. En outre, je suppose que je devrai faire du champ Word une clé primaire. (La table aura également un identifiant unique PK également).

Donc .. je pourrais obtenir ces mots que je dois ajouter à la table ...

par exemple.

  • Chat 
  • Chien 
  • Foo
  • Bar
  • Pan Pan 
  • etc...

Donc, traditionnellement, je voudrais essayer ce qui suit (pseudo code)

SELECT WordID FROM Words WHERE Word = @Word
IF WordID IS NULL OR WordID <= 0
    INSERT INTO Words VALUES (@Word)

c'est à dire. Si le mot n'existe pas, insérez-le.

Maintenant, le problème qui me préoccupe est que nous obtenons BEAUCOUP de hits. Il est donc possible que le mot puisse être inséré à partir d'un autre processus entre SELECT et INSERT. Ce qui jetterait alors une erreur de contrainte. ? (c.-à-d. a Race Condition ).

J'ai alors pensé que je pourrais peut-être faire ce qui suit ...

INSERT INTO Words (Word)
SELECT @Word
WHERE NOT EXISTS (SELECT WordID FROM Words WHERE Word = @Word)

fondamentalement, insère un mot quand il n’existe pas.

Mauvaise syntaxe mise à part, je ne suis pas sûr que ce soit bon ou mauvais à cause de la façon dont il verrouille la table (si c'est le cas) et n'est pas aussi performant sur une table qu'il obtient des lectures massives et beaucoup d'écritures.

Alors, qu'est-ce que vous, les gourous de la Sql, pensez/faites?

J'espérais avoir un simple insert et «attraper» cela pour toutes les erreurs jetées.

22
Pure.Krome

Votre solution:

INSERT INTO Words (Word)
    SELECT @Word
WHERE NOT EXISTS (SELECT WordID FROM Words WHERE Word = @Word)

... est à peu près aussi bon qu'il obtient. Vous pouvez le simplifier à ceci:

INSERT INTO Words (Word)
    SELECT @Word
WHERE NOT EXISTS (SELECT * FROM Words WHERE Word = @Word)

... car EXISTS n'a pas besoin de renvoyer d'enregistrements. L'optimiseur de requêtes ne se soucie plus des champs que vous avez demandés.

Comme vous le mentionnez cependant, ce n'est pas particulièrement performant, car cela verrouille toute la table pendant l'insertion. Sauf que, si vous ajoutez un index unique (il n'est pas nécessaire que ce soit la clé primaire) à Word, il vous suffira de verrouiller les pages correspondantes.

Votre meilleure option consiste à simuler la charge attendue et à examiner les performances avec SQL Server Profiler. Comme dans tout autre domaine, l'optimisation prématurée est une mauvaise chose. Définissez des mesures de performance acceptables, puis mesurez avant de faire quoi que ce soit d'autre.

Si cela ne vous permet toujours pas d'obtenir des performances suffisantes, plusieurs techniques du domaine de l'entreposage de données pourraient vous aider.

28
Roger Lipscombe

Je pense avoir trouvé une meilleure (ou au moins une réponse plus rapide) à cette . Créer un index comme

CREATE UNIQUE NONCLUSTERED INDEX [IndexTableUniqueRows] ON [dbo].[table] 
(
    [Col1] ASC,
    [Col2] ASC,

)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = ON, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Incluez toutes les colonnes qui définissent l'unicité. La partie importante est IGNORE_DUP_KEY = ON. Cela transforme des insertions non uniques en avertissements. SSIS ignore ces avertissements et vous pouvez toujours utiliser fastload.

4
WebWeasel

Si vous utilisez MS SQL Server, vous pouvez créer un index unique sur les colonnes de votre table, qui doit être unique (documentation ici ):

CREATE UNIQUE [ CLUSTERED | NONCLUSTERED ] INDEX <index_name>
    ON Words ( Word [ ASC | DESC ])

Spécifiez Clustered ou NonClustered, selon votre cas. De même, si vous souhaitez le trier (pour permettre une recherche plus rapide), spécifiez ASC ou DESC pour l'ordre de tri.

Voir ici , si vous voulez en savoir plus sur l’architecture des index.

Sinon, vous pourriez utiliser UNIQUE CONSTRAINTS comme documenté ici :

ALTER TABLE Words
ADD CONSTRAINT UniqueWord
UNIQUE (Word); 
3
Bogdan Maxim

J'ai eu le même problème et voici comment je l'ai résolu

insert into Words
( selectWord , Fixword)
SELECT Word,'theFixword'
FROM   OldWordsTable
WHERE 
(
    (Word LIKE 'junk%') OR
     (Word LIKE 'orSomthing') 

)
and Word not in 
    (
        SELECT selectWord FROM words WHERE selectWord = Word
    ) 
3
Pbearne

alors que la contrainte unique est certainement une façon de faire, vous pouvez également l’utiliser pour votre logique d’insertion: http://www.sqlteam.com/article/application-locks-or-mutexes-in-sql-server- 2005

fondamentalement, vous ne mettez pas de verrou sur la table ci-dessous, vous vous inquiétez donc pas des lectures.

c'est un mutex en code SQL.

1
Mladen Prajdic

Je ne peux pas parler des détails de MS SQL, mais l'un des points d'une clé primaire en SQL est d'assurer l'unicité. Donc, par définition, en termes génériques SQL, une clé primaire est un ou plusieurs champs uniques à une table. Bien qu’il existe différentes manières d’appliquer ce comportement (remplacer l’ancienne entrée par la nouvelle par opposition à la nouvelle), je serais surpris que MS SQL n’ait pas de mécanisme permettant d’appliquer ce comportement et qu’il ne l’était pas. rejette la nouvelle entrée. Assurez-vous simplement que vous définissez la clé primaire sur le champ Word et qu'il devrait fonctionne.

Encore une fois, je nie que tout cela soit à ma connaissance de la programmation MySQL et de ma classe de bases de données, alors excuses-moi si je suis sur les subtilités de MS SQL.

0
Dan Fego