J'essaie de mettre à jour une table avec un tableau de valeurs. Chaque élément du tableau contient des informations qui correspondent à une ligne d'une table dans la base de données SQL Server. Si la ligne existe déjà dans la table, nous mettons à jour cette ligne avec les informations dans le tableau donné. Sinon, nous insérons une nouvelle ligne dans le tableau. J'ai essentiellement décrit upsert.
Maintenant, j'essaie d'y parvenir dans une procédure stockée qui prend un paramètre XML. La raison pour laquelle j'utilise XML et non un paramètre table est que, ce faisant, je devrai créer un type personnalisé en SQL et associer ce type à la procédure stockée. Si jamais je modifiais quelque chose dans ma procédure stockée ou mon schéma db sur la route, je devrais refaire à la fois la procédure stockée et le type personnalisé. Je veux éviter cette situation. En outre, la supériorité de TVP sur XML n'est pas utile dans ma situation car, la taille de mon tableau de données ne dépassera jamais 1000. Cela signifie que je ne peux pas utiliser la solution proposée ici: Comment insérer plusieurs enregistrements à l'aide de XML dans SQL Server 2008
En outre, une discussion similaire ici ( UPSERT - Existe-t-il une meilleure alternative à MERGE ou @@ rowcount? ) Est différente de ce que je demande parce que j'essaie d'insérer plusieurs lignes dans une table.
J'espérais que j'utiliserais simplement l'ensemble de requêtes suivant pour inverser les valeurs du xml. Mais cela ne fonctionnera pas. Cette approche est censée fonctionner uniquement lorsque l'entrée est une seule ligne.
begin tran
update table with (serializable) set select * from xml_param
where key = @key
if @@rowcount = 0
begin
insert table (key, ...) values (@key,..)
end
commit tran
La prochaine alternative consiste à utiliser un IF EXISTS exhaustif ou l'une de ses variantes de la forme suivante. Mais, je rejette cela au motif d'être d'une efficacité sous-optimale:
IF (SELECT COUNT ... ) > 0
UPDATE
ELSE
INSERT
L'option suivante utilisait l'instruction Merge comme décrit ici: http://www.databasejournal.com/features/mssql/using-the-merge-statement-to-perform-an-upsert.html . Mais, j'ai lu des informations sur les problèmes de fusion ici: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ . Pour cette raison, j'essaie d'éviter la fusion.
Donc, maintenant ma question est: existe-t-il une autre option ou une meilleure façon d'obtenir plusieurs upsert en utilisant le paramètre XML dans la procédure stockée SQL Server 2008?
Veuillez noter que les données du paramètre XML peuvent contenir certains enregistrements qui ne doivent pas être supprimés en raison de leur ancienneté par rapport à l'enregistrement actuel. Il existe un ModifiedDate
champ dans le XML et dans la table de destination qui doit être comparé afin de déterminer si l'enregistrement doit être mis à jour ou supprimé.
MERGE
lesquels Bertrand souligne sont principalement des cas marginaux et des inefficacités, pas des bouchons - MS ne l'aurait pas libéré s'il s'agissait d'un véritable champ de mines. Êtes-vous sûr que les circonvolutions que vous devez éviterMERGE
ne créent pas plus d'erreurs potentielles qu'elles n'en économisent?MERGE
. Les étapes INSERT et UPDATE de MERGE sont toujours traitées séparément. La principale différence dans mon approche est la variable de table qui contient les ID d'enregistrement mis à jour et la requête DELETE qui utilise cette variable de table pour supprimer ces enregistrements de la table temporaire des données entrantes. Et je suppose que la SOURCE pourrait être directement de @ XMLparam.nodes () au lieu de se déverser dans une table temporaire, mais ce n'est pas beaucoup de choses supplémentaires pour ne pas avoir à vous soucier de vous retrouver dans l'un de ces cas extrêmes; - ).Réponses:
Que la source soit XML ou TVP ne fait pas une énorme différence. L'opération globale est essentiellement:
Vous le faites dans cet ordre car si vous insérez d'abord, toutes les lignes existent pour obtenir la MISE À JOUR et vous effectuerez des travaux répétés pour toutes les lignes qui viennent d'être insérées.
Au-delà de cela, il existe différentes façons d'accomplir cela et diverses façons d'en modifier l'efficacité supplémentaire.
Commençons par le strict minimum. Étant donné que l'extraction du XML est susceptible d'être l'une des parties les plus coûteuses de cette opération (sinon la plus coûteuse), nous ne voulons pas avoir à le faire deux fois (car nous avons deux opérations à effectuer). Ainsi, nous créons une table temporaire et extrayons les données du XML dedans:
De là, nous faisons la MISE À JOUR, puis l'INSÉRER:
Maintenant que nous avons arrêté l'opération de base, nous pouvons faire quelques choses pour optimiser:
capturer @@ ROWCOUNT de l'insertion dans la table temporaire et comparer à @@ ROWCOUNT de la MISE À JOUR. S'ils sont identiques, nous pouvons sauter l'INSERT
capturer les valeurs d'ID mises à jour via la clause OUTPUT et SUPPRIMER celles de la table temporaire. L'INSERT n'a alors pas besoin du
WHERE NOT EXISTS(...)
S'il y a des lignes dans les données entrantes qui ne doivent pas être synchronisées (c'est-à-dire ni insérées ni mises à jour), alors ces enregistrements doivent être supprimés avant d'effectuer la MISE À JOUR
J'ai utilisé ce modèle plusieurs fois sur les importations / ETL qui ont bien plus de 1000 lignes ou peut-être 500 dans un lot sur un ensemble total de 20k - plus d'un million de lignes. Cependant, je n'ai pas testé la différence de performances entre la suppression des lignes mises à jour de la table temporaire et la simple mise à jour du champ [IsUpdate].
Veuillez noter la décision d'utiliser XML sur TVP car il y a au plus 1000 lignes à importer à la fois (mentionnées dans la question):
Si cela est appelé plusieurs fois ici et là, alors le gain de performance mineur dans TVP pourrait ne pas valoir le coût de maintenance supplémentaire (il faut abandonner le proc avant de changer le type de table défini par l'utilisateur, les changements de code d'application, etc.) . Mais si vous importez 4 millions de lignes, en envoyant 1000 à la fois, soit 4000 exécutions (et 4 millions de lignes de XML à analyser, quelle que soit la façon dont elles sont fractionnées), et même une différence de performance mineure lorsqu'elle est exécutée à quelques reprises seulement ajouter à une différence notable.
Cela étant dit, la méthode que j'ai décrite ne change pas en dehors du remplacement de SELECT FROM @XmlInputParam pour être SELECT FROM @TVP. Étant donné que les TVP sont en lecture seule, vous ne pourrez pas les supprimer. Je suppose que vous pouvez simplement ajouter un
WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)
à ce SELECT final (lié à l'INSERT) au lieu du simpleWHERE IsUpdate = 0
. Si vous deviez utiliser la@UpdateIDs
variable de table de cette manière, vous pourriez même vous en sortir sans vider les lignes entrantes dans la table temporaire.la source