Implémentation SQL Server 2005 de MySQL REMPLACER EN?

86

MySQL a cette REPLACE INTOcommande SQL incroyablement utile mais propriétaire .

Cela peut-il être facilement émulé dans SQL Server 2005?

Démarrer une nouvelle Transaction, faire un Select()et puis soit UPDATEou INSERTet COMMITest toujours un peu pénible, surtout quand on le fait dans l'application et donc toujours en gardant 2 versions de l'instruction.

Je me demande s'il existe un moyen simple et universel d'implémenter une telle fonction dans SQL Server 2005?

Michael Stum
la source

Réponses:

60

C'est quelque chose qui me dérange à propos de MSSQL ( diatribe sur mon blog ). Je souhaite que MSSQL soit pris en charge upsert.

Le code de @ Dillie-O est un bon moyen dans les anciennes versions de SQL (+1 vote), mais il s'agit toujours essentiellement de deux opérations d'E / S (le existspuis le updateou insert)

Il existe un moyen légèrement meilleur sur ce post , en gros:

--try an update
update tablename 
set field1 = 'new value',
    field2 = 'different value',
    ...
where idfield = 7

--insert if failed
if @@rowcount = 0 and @@error = 0
    insert into tablename 
           ( idfield, field1, field2, ... )
    values ( 7, 'value one', 'another value', ... )

Cela le réduit à une opération d'E / S s'il s'agit d'une mise à jour, ou à deux s'il s'agit d'une insertion.

MS Sql2008 introduit à mergepartir de la norme SQL: 2003:

merge tablename as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

Maintenant, c'est vraiment juste une opération d'E / S, mais un code horrible :-(

Keith
la source
Grand merci! Enregistre le Select et n'a souvent même pas besoin d'une teransaction dans les situations où je peux être sûr qu'entre la mise à jour et "mon" insert, il n'y a pas d'autre insert pour cette clé.
Michael Stum
2
@Michael Vous feriez mieux d'avoir un index unique sur cette table et de gérer les erreurs de clé en double si vous comptez utiliser cette solution.
Sam Saffron
3
@Keith Votre instruction de fusion ne fonctionne pas. MERGEne prend pas en charge la WHEREclause, vous devez réécrire cela en utilisant USINGet ON. De plus, sauf si vous ajoutez WITH (HOLDLOCK), il y a une course et des INSERTs simultanés peuvent se produire, l'un d'entre eux échouant en raison du conflit de clé.
Evgeniy Berezovsky
Oui, comme indiqué ici: weblogs.sqlteam.com/dang/archive/2009/01/31 / ... MERGE n'est pas atomique. Il supprime un verrou de mise à jour implicite, mais le libère avant d'effectuer une insertion, ce qui provoque une condition de concurrence qui peut entraîner des violations de clé primaire. Vous devez utiliser un HOLDLOCK explicite en plus du UPDLOCK implicite pour que l'opération soit atomique. Dans l'état actuel des choses, ce n'est pas atomique, bien qu'il semble être une seule déclaration.
Triynko
1
La syntaxe MERGE est incorrecte et elle est corrigée dans une réponse plus récente du même auteur: stackoverflow.com/a/243670/24472
Larry
21

La fonctionnalité que vous recherchez est traditionnellement appelée UPSERT. Au moins savoir comment cela s'appelle peut vous aider à trouver ce que vous recherchez.

Je ne pense pas que SQL Server 2005 ait d'excellents moyens de faire cela. 2008 introduit l'instruction MERGE qui peut être utilisée pour accomplir cela comme indiqué dans: http://www.databasejournal.com/features/mssql/article.php/3739131 ou http://blogs.conchango.com/davidportas/archive/ 2007/11/14 / SQL-Server-2008-MERGE.aspx

Merge était disponible dans la version bêta de 2005, mais ils l'ont supprimé dans la version finale.

Karl Seguin
la source
18

Ce que fait l'upsert / fusion est quelque chose à l'effet de ...

IF EXISTS (SELECT * FROM [Table] WHERE Id = X)
   UPDATE [Table] SET...
ELSE
   INSERT INTO [Table]

J'espère donc que la combinaison de ces articles et de ce pseudo code pourra faire bouger les choses.

Dillie-O
la source
10

J'ai écrit un article de blog sur ce problème.

L'essentiel est que si vous voulez des mises à jour bon marché et que vous voulez être sûr pour une utilisation simultanée, essayez:

update t
set hitCount = hitCount + 1
where pk = @id

if @@rowcount < 1 
begin 
   begin tran
      update t with (serializable)
      set hitCount = hitCount + 1
      where pk = @id
      if @@rowcount = 0
      begin
         insert t (pk, hitCount)
         values (@id,1)
      end
   commit tran
end

De cette façon, vous avez 1 opération pour les mises à jour et un maximum de 3 opérations pour les insertions. Donc, si vous mettez généralement à jour, c'est une option sûre et bon marché.

Je ferais également très attention de ne pas utiliser quoi que ce soit qui n'est pas sûr pour une utilisation simultanée. Il est très facile d'obtenir des violations de clé primaire ou de dupliquer des lignes en production.

Sam Safran
la source