J'ai besoin de mettre à jour une très grande table périodiquement et SQLBulkCopy est parfait pour cela, seulement que j'ai un index à 2 colonnes qui empêche les doublons. Existe-t-il un moyen d'utiliser SQLBulkCopy comme "insérer ou mettre à jour s'il existe"?
Sinon, quelle est la manière la plus efficace de le faire? Encore une fois, je parle d'une table avec des millions d'enregistrements.
Je vous remercie
Je chargerais les données en bloc dans une table de transfert temporaire, puis ferais une conversion dans la table finale. Voir http://www.databasejournal.com/features/mssql/article.php/3739131/UPSERT-Functionality-in-SQL-Server-2008.htm pour un exemple de réalisation d'un upsert.
J'ai publié un package nuget (SqlBulkTools) pour résoudre ce problème.
Voici un exemple de code qui permettrait d'obtenir un upsert en vrac.
var bulk = new BulkOperations();
var books = GetBooks();
using (TransactionScope trans = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager
.ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
{
bulk.Setup<Book>()
.ForCollection(books)
.WithTable("Books")
.AddAllColumns()
.BulkInsertOrUpdate()
.MatchTargetOn(x => x.ISBN)
.Commit(conn);
}
trans.Complete();
}
Pour les très grandes tables, il existe des options pour ajouter des verrous de table et désactiver temporairement les index non clusterisés. Voir Documentation SqlBulkTools pour plus d'exemples.
Pas en une seule étape, mais dans SQL Server 2008 , vous pouvez:
MERGE
pour mettre à jour/insérer dans votre vraie tableEn savoir plus sur instruction MERGE
Au lieu de créer une nouvelle table temporaire, BTW consomme plus d'espace et de mémoire.
J'ai créé un déclencheur avec INSTEAD OF INSERT et utilisé à l'intérieur de l'instruction MERGE.
Mais n'oubliez pas d'ajouter le paramètre SqlBulkCopyOptions.FireTriggers dans le SqlBulkCopy.
Ceci est mes deux cents.
Une autre alternative serait de ne pas utiliser une table temporaire mais d'utiliser une procédure stockée avec un paramètre de valeur de table. Passez un datatable au sp et faites la fusion là.
Vous avez un indice de @Ivan. Pour ceux qui pourraient en avoir besoin, voici ce que j'ai fait.
create trigger yourschma.Tr_your_triger_name
on yourschma.yourtable
instead of INSERT
as
merge into yourschma.yourtable as target
using inserted as source
on (target.yourtableID = source.yourtableID)
when matched then
update
set target.ID = source.ID,
target.some_column = source.some_column,
target.Amount = source.Amount
when not matched by target then
insert (some_column, Amount)
values (source.some_column, source.Amount);
go