70 votes

Insérer seulement une ligne si ce n'est déjà fait

J'ai toujours utilisé de quelque chose de similaire à la suivante, pour l'atteindre:

INSERT INTO TheTable
SELECT
    @primaryKey,
    @value1,
    @value2
WHERE
    NOT EXISTS
    (SELECT
        NULL
    FROM
        TheTable
    WHERE
        PrimaryKey = @primaryKey)

...mais une fois sous la charge, une violation de clé primaire est produite. C'est la seule instruction qui s'insère dans ce tableau. Donc, est-ce à dire que la déclaration ci-dessus n'est pas atomique?

Le problème, c'est que c'est presque impossible à recréer à volonté.

Je pourrais peut-être changer la chose comme suit:

INSERT INTO TheTable
WITH
    (HOLDLOCK,
    UPDLOCK,
    ROWLOCK)
SELECT
    @primaryKey,
    @value1,
    @value2
WHERE
    NOT EXISTS
    (SELECT
        NULL
    FROM
        TheTable
    WITH
        (HOLDLOCK,
        UPDLOCK,
        ROWLOCK)
    WHERE
        PrimaryKey = @primaryKey)

Bien que, peut-être que je suis en utilisant le mauvais serrures, ou d'utiliser trop de verrouillage ou de quelque chose.

J'ai vu d'autres questions sur stackoverflow.com où les réponses sont ce qui suggère un "IF (SELECT COUNT(*) ... INSÉRER", etc., mais j'ai toujours été en vertu de la (peut-être erronée) hypothèse qu'une seule instruction SQL serait atomique.

Quelqu'un aurait-il des idées?

Merci.

Adam

60voto

gbn Points 197263

Qu'en est-il du modèle "JFDI" ?

 BEGIN TRY
   INSERT etc
END TRY
BEGIN CATCH
    IF ERROR_NUMBER() <> 2627
      RAISERROR etc
END CATCH
 

Sérieusement, c'est le plus rapide et le plus simultané sans verrous, surtout à fort volume. Que se passe-t-il si UPDLOCK est remonté et que toute la table est verrouillée?

Lire la leçon 4

23voto

GSerg Points 33571

J'ai ajouté HOLDLOCK qui n'était pas présent à l'origine. Veuillez ignorer la version sans cet indice.

En ce qui me concerne, cela devrait suffire:

 INSERT INTO TheTable 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 0
     FROM TheTable WITH (UPDLOCK, HOLDLOCK)
     WHERE PrimaryKey = @primaryKey) 
 

En outre, si vous souhaitez réellement mettre à jour une ligne si elle existe et insérer si elle ne l’est pas, vous pouvez trouver cette question utile.

17voto

Chris Smith Points 2858

Vous pouvez utiliser MERGE:

 MERGE INTO Target
USING (VALUES (@primaryKey, @value1, @value2)) Source (key, value1, value2)
ON Target.key = Source.key
WHEN MATCHED THEN
    UPDATE SET value1 = Source.value1, value2 = Source.value2
WHEN NOT MATCHED BY TARGET THEN
    INSERT (Name, ReasonType) VALUES (@primaryKey, @value1, @value2)
 

1voto

Marcelo Cantos Points 91211

Je ne sais pas s'il s'agit de la manière "officielle", mais vous pouvez essayer le INSERT et retomber à UPDATE s'il échoue.

0voto

Robert Davidian Points 46

Pouvez-vous envelopper la déclaration dans une transaction? Cela devrait empêcher toute autre instruction d'essayer d'insérer jusqu'à la validation de la première transaction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X