web-dev-qa-db-fra.com

Équivalent de MySQL ON DUPLICATE KEY UPDATE dans Sql Server

J'essaie de trouver un équivalent de la requête MySql suivante dans Sql Server (2012)?

INSERT INTO mytable (COL_A, COL_B, COL_C, COL_D)
VALUES ( 'VAL_A','VAL_B', 'VAL_C', 'VAL_D')
ON DUPLICATE KEY UPDATE COL_D= VALUES(COL_D);

Quelqu'un peut-il aider?

PS. J'ai lu que la requête MERGE a une fonction similaire, mais je trouve la syntaxe de celle-ci très différente.

23
Nemo

Vous recherchez essentiellement un modèle d'insertion ou de mise à jour parfois appelé Upsert.

Je recommande ceci: Insérer ou mettre à jour le modèle pour Sql Server - Sam Saffron

Pour une procédure qui traitera de lignes uniques, ces transactions fonctionneraient bien:

Première solution de Sam Saffron (adaptée à ce schéma):

begin tran
if exists (
  select * 
    from mytable with (updlock,serializable) 
    where col_a = @val_a
      and col_b = @val_b
      and col_c = @val_c
  )
  begin
    update mytable
      set col_d = @val_d
      where col_a = @val_a
        and col_b = @val_b
        and col_c = @val_c;
  end
else
  begin
    insert into mytable (col_a, col_b, col_c, col_d)
      values (@val_a, @val_b, @val_c, @val_d);
  end
commit tran

La deuxième solution de Sam Saffron (adaptée à ce schéma):

begin tran
  update mytable with (serializable)
    set col_d = @val_d
      where col_a = @val_a
        and col_b = @val_b
        and col_c = @val_c;
  if @@rowcount = 0
    begin
        insert into mytable (col_a, col_b, col_c, col_d)
          values (@val_a, @val_b, @val_c, @val_d);
     end
commit tran

Même avec une utilisation créative de IGNORE_DUP_KEY, vous seriez toujours obligé d'utiliser un bloc d'insertion/mise à jour ou une instruction de fusion.

update mytable
  set col_d = 'val_d'
  where col_a = 'val_a'
    and col_b = 'val_b'
    and col_c = 'val_c';

insert into mytable (col_a, col_b, col_c, col_d)
  select 'val_a','val_b', 'val_c', 'val_d'
  where not exists (select * 
    from mytable with (serializable) 
    where col_a = 'val_a'
      and col_b = 'val_b'
      and col_c = 'val_c'
      );

La réponse de fusion fournie par Spock devrait faire ce que vous voulez.

La fusion n'est pas nécessairement recommandée. Je l'utilise, mais je ne l'admettrais jamais à @AaronBertrand.

19
SqlZim

Essayez ceci ... J'ai ajouté des commentaires pour essayer d'expliquer ce qui se passe où dans une instruction SQL Merge. Source: MSDN: instruction de fusion

L'instruction de fusion est différente de l'instruction ON DUPLICATE KEY UPDATE en ce que vous pouvez lui indiquer les colonnes à utiliser pour la fusion.

CREATE TABLE #mytable(COL_A VARCHAR(10), COL_B VARCHAR(10), COL_C VARCHAR(10), COL_D VARCHAR(10))
INSERT INTO #mytable VALUES('1','0.1', '0.2', '0.3'); --<These are the values we'll be updating

SELECT * FROM #mytable --< Starting values (1 row)

    MERGE #mytable AS target --< This is the target we want to merge into
    USING ( --< This is the source of your merge. Can me any select statement
        SELECT '1' AS VAL_A,'1.1' AS VAL_B, '1.2' AS VAL_C, '1.3' AS VAL_D --<These are the values we'll use for the update. (Assuming column COL_A = '1' = Primary Key)
        UNION
        SELECT '2' AS VAL_A,'2.1' AS VAL_B, '2.2' AS VAL_C, '2.3' AS VAL_D) --<These values will be inserted (cause no COL_A = '2' exists)
        AS source (VAL_A, VAL_B, VAL_C, VAL_D) --< Column Names of our virtual "Source" table
    ON (target.COL_A = source.VAL_A) --< This is what we'll use to find a match "JOIN source on Target" using the Primary Key
    WHEN MATCHED THEN --< This is what we'll do WHEN we find a match, in your example, UPDATE COL_D = VALUES(COL_D);
        UPDATE SET
            target.COL_B = source.VAL_B,
            target.COL_C = source.VAL_C,
            target.COL_D = source.VAL_D
    WHEN NOT MATCHED THEN --< This is what we'll do when we didn't find a match
    INSERT (COL_A, COL_B, COL_C, COL_D)
    VALUES (source.VAL_A, source.VAL_B, source.VAL_C, source.VAL_D)
    --OUTPUT deleted.*, $action, inserted.* --< Uncomment this if you want a summary of what was inserted on updated.
    --INTO #Output  --< Uncomment this if you want the results to be stored in another table. NOTE* The table must exists
    ;
SELECT * FROM #mytable --< Ending values (2 row, 1 new, 1 updated)

J'espère que cela pourra aider

9
Spock

La procédure stockée sauvera la journée.

Ici, je suppose que COL_A et COL_B sont des colonnes uniques et sont de type INT NB! Ne pas avoir d'instance ATM de serveur SQL, donc ne peut pas garantir l'exactitude de la syntaxe. MISE À JOUR! Voici un lien vers SQLFIDDLE

 CREATE TABLE mytable
(
COL_A int UNIQUE,
COL_B int UNIQUE,
COL_C int,
COL_D int,
)

GO

INSERT INTO mytable (COL_A, COL_B, COL_C, COL_D)
VALUES (1,1,1,1),
(2,2,2,2),
(3,3,3,3),
(4,4,4,4);
GO

CREATE PROCEDURE updateDuplicate(@COL_A INT, @COL_B INT, @COL_C INT, @COL_D INT)
AS
BEGIN
    DECLARE @ret INT
    SELECT @ret = COUNT(*) 
    FROM mytable p 
    WHERE p.COL_A = @COL_A 
        AND p.COL_B = @COL_B

     IF (@ret = 0) 
        INSERT INTO mytable (COL_A, COL_B, COL_C, COL_D)
        VALUES ( @COL_A, @COL_B, @COL_C, @COL_D)

     IF (@ret > 0)
        UPDATE mytable SET COL_D = @COL_D WHERE col_A = @COL_A AND COL_B = @COL_B  
END;
GO

Appelez ensuite cette procédure avec les valeurs nécessaires au lieu de l'instruction Update

exec updateDuplicate 1, 1, 1, 2
GO
SELECT * from mytable
GO
2
Artjoman

Vous pouvez simuler un comportement presque identique en utilisant un INSTEAD OF TRIGGER:

CREATE TRIGGER tMyTable ON MyTable
INSTEAD OF INSERT
AS
    BEGIN
        SET NOCOUNT ON;

        SELECT i.COL_A, i.COL_B, i.COL_C, i.COL_D, 
            CASE WHEN mt.COL_D IS NULL THEN 0 ELSE 1 END AS KeyExists 
            INTO #tmpMyTable
            FROM INSERTED i
            LEFT JOIN MyTable mt
            ON i.COL_D = mt.COL_D;

        INSERT INTO MyTable(COL_A, COL_B, COL_C, COL_D)
            SELECT COL_A, COL_B, COL_C, COL_D
                FROM #tmpMyTable
                WHERE KeyExists = 0;

        UPDATE mt
            SET mt.COL_A = t.COL_A, mt.COL_B = t.COL_B, mt.COL_C = t.COL_C
            FROM MyTable mt 
                INNER JOIN #tmpMyTable t 
                ON mt.COL_D = t.COL_D AND t.KeyExists = 1;
    END;

SqlFiddle ici

Comment ça marche

  • Nous projetons d'abord une liste de toutes les lignes tentées d'être insérées dans la table dans une table #temp, en notant celles qui SONT déjà dans la table sous-jacente via un LEFT OUTER JOIN sur la ou les colonnes clés COL_D qui détectent les critères de duplication.
  • Nous devons ensuite répéter le travail réel d'une instruction INSERT, en insérant les lignes qui ne sont pas déjà dans la table (à cause de la INSTEAD OF, nous avons supprimé la responsabilité de l'insertion du moteur et devons le faire nous-mêmes).
  • Enfin, nous mettons à jour toutes les colonnes non clés des lignes correspondantes avec les données nouvellement "insérées".

Points saillants

  • Il fonctionne sous les couvertures, c'est-à-dire que tout insert dans la table alors que le déclencheur est activé sera soumis au déclencheur (par exemple, ORM d'application, autres procédures stockées, etc.). L'appelant ne sera généralement PAS CONSCIENT que le INSTEAD OF le déclencheur est en place.
  • Il doit y avoir une sorte de clé pour détecter le critère en double (naturel ou substitut). J'ai supposé COL_D dans ce cas, mais il pourrait s'agir d'une clé composite. (Clé mais ne peut pas être IDENTITY pour des raisons évidentes, car le client n'insérera pas d'identité)
  • Le déclencheur fonctionne pour les INSERTS à une et plusieurs lignes

[~ # ~] nb [~ # ~]

  • Les clauses de non-responsabilité standard avec déclencheurs s'appliquent, et plus encore avec INSTEAD OF déclencheurs - car cela peut provoquer des changements surprenants dans le comportement observable de Sql Server, comme celui-ci - même bien intentionnés INSTEAD OF les déclencheurs peuvent entraîner des heures d'efforts inutiles et de la frustration pour les développeurs et les administrateurs de base de données qui ne sont pas conscients de leur présence sur votre table.
  • Cela affectera TOUTES les insertions dans la table. Pas seulement le vôtre.
2
StuartLC

Il n'y a pas DUPLICATE KEY UPDATE équivalent dans le serveur sql, mais vous pouvez utiliser fusionné et quand il est mis en correspondance avec le serveur sql pour y arriver, jetez un œil ici: plusieurs opérations utilisant la fusion

0
Suchit kumar