SQL Server - Comment insérer un enregistrement et s'assurer qu'il est unique
-
06-07-2019 - |
Question
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 qu'il s'agisse du nom d'un mot 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 avoir ces mots que je dois ajouter à la table ...
par exemple.
- chat
- Chien
- Foo
- Bar
- PewPew
- etc ...
Traditionnellement, j'essaierais 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 soit inséré à partir d'un autre processus entre SELECT et INSERT .. qui jetterait alors une erreur de contrainte? (c'est-à-dire une une situation de concurrence ).
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érez 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'en pensez-vous, gourous de la SQL?
J'espérais avoir un simple insert et «rattraper» celui des erreurs renvoyées.
La solution
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 comme suit:
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 l'avez dit, ce n'est pas particulièrement performant, car il 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, une 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, de nombreuses techniques du domaine de l'entreposage de données pourraient vous aider.
Autres conseils
Je pense avoir trouvé une réponse meilleure (ou au moins plus rapide) à cette question. Créez un index tel que:
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 le chargement rapide.
Si vous utilisez MS SQL Server, vous pouvez créer un index unique sur les colonnes de votre table (documenté ici ):
CREATE UNIQUE [ CLUSTERED | NONCLUSTERED ] INDEX <index_name>
ON Words ( word [ ASC | DESC ])
Spécifiez en cluster
ou non en cluster
, 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 souhaitez 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);
J'ai eu un problème similaire 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
)
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-ou-mutexes-in-sql-server-2005
En gros, vous ne mettez pas de verrou sur la table ci-dessous, vous vous inquiétez donc pas des lectures. alors que vos contrôles d’existence seront effectués correctement.
c'est un mutex en code SQL.
Je ne peux pas parler des détails de MS SQL, mais l'un des points d'une clé primaire en SQL est de garantir 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 de constater que MS SQL ne dispose pas d'un mécanisme pour appliquer ce comportement et qu'il ne le ferait pas. rejette la nouvelle entrée. Assurez-vous simplement que vous définissez la clé primaire sur le champ Word et que cela devrait fonctionner.
Encore une fois, je nie que tout cela est dû à ma connaissance de la programmation MySQL et de ma classe de bases de données, alors excuses-moi si je me laisse aller à la complexité de MS SQL.
declare @Error int
begin transaction
INSERT INTO Words (Word) values(@word)
set @Error = @@ERROR
if @Error <> 0 --if error is raised
begin
goto LogError
end
commit transaction
goto ProcEnd
LogError:
rollback transaction