Comment convertir une boucle SQL en une logique basée sur Set

J'ai passé une bonne partie de la journée et hier à essayer de décider si utiliser une boucle ou un slider en SQL ou de comprendre comment utiliser la logique basée sur les sets pour résoudre le problème. Je ne suis pas nouveau à définir la logique, mais ce problème semble être particulièrement complexe.

Le problème

L'idée est que si j'ai une list de toutes les transactions (10, 100 ou millions) et une date à laquelle elles se sont produites, je peux commencer à combiner certaines de ces données dans un tableau des totaux quotidiens afin de pouvoir les visualiser plus rapidement. systèmes. Le pseudocode pour ceci est comme ceci:

foreach( row in transactions_table ) if( row in totals_table already exists ) update totals_table, add my totals to the totals row else insert into totals_table with my row as the base values delete ( or archive ) row 

Comme vous pouvez le constater, le bloc de la boucle est relativement sortingvial à implémenter, tout comme l'itération slider / bouclage. Cependant, le time d'exécution est assez lent et lourd et ma question est: existe-t-il une façon non itérative d'effectuer une telle tâche, ou est-ce une des rares exceptions où je dois juste "aspirer" et utiliser un slider ?

Il y a eu quelques discussions sur le sujet, dont certaines semblent similaires, mais pas utilisables en raison de l'instruction if / else et des opérations sur une autre table, par exemple:

Comment merge des lignes de données SQL sur une logique basée sur des colonnes? Cette question ne semble pas applicable car elle renvoie simplement une vue de toutes les sums et ne prend pas de décisions logiques sur les ajouts ou les mises à jour d'une autre table

SQL Looping semble avoir quelques idées sur la sélection avec quelques déclarations de cas qui semblent possibles, mais il y a deux opérations dont j'ai besoin en fonction du statut d'une autre table, donc cette solution ne semble pas correspondre.

SQL Call Procédure stockée pour chaque ligne sans utiliser de slider Cette solution semble être la plus proche de ce que j'ai besoin de faire, en ce sens qu'elle peut gérer un nombre arbitraire d'opérations sur chaque ligne, mais il ne semble pas y avoir de consensus groupe.

Des conseils pour résoudre ce problème frustrant?

Remarques

J'utilise SQL Server 2008

La configuration du schéma est la suivante:

Totaux: (id int pk, date totals_date, store_id int fk, id_machine int fk, total_in, total_out)

Transactions: (transaction_id int pk, transaction_date datetime, id_stockage int fk, id_machine int fk, transaction_type (IN ou OUT), transaction_amount decimal)

Les totaux doivent être calculés par magasin, par machine et par date, et doivent totaliser toutes les transactions IN dans total_in et les transactions OUT dans total_out. L'objective est d'get un pseudo cube de données.

Vous le feriez dans deux instructions basées sur un set:

 BEGIN TRANSACTION; DECLARE @keys TABLE(some_key INT); UPDATE tot SET totals += tx.amount OUTPUT inserted.some_key -- key values updated INTO @keys FROM dbo.totals_table AS tot WITH (UPDLOCK, HOLDLOCK) INNER JOIN ( SELECT t.some_key, amount = SUM(amount) FROM dbo.transactions_table AS t WITH (HOLDLOCK) INNER JOIN dbo.totals_table AS tot ON t.some_key = tot.some_key GROUP BY t.some_key ) AS tx ON tot.some_key = tx.some_key; INSERT dbo.totals_table(some_key, amount) OUTPUT inserted.some_key INTO @keys SELECT some_key, SUM(amount) FROM dbo.transactions_table AS tx WHERE NOT EXISTS ( SELECT 1 FROM dbo.totals_table WHERE some_key = tx.some_key ) GROUP BY some_key; DELETE dbo.transactions_table WHERE some_key IN (SELECT some_key FROM @keys); COMMIT TRANSACTION; 

(La gestion des erreurs, le niveau d'isolation applicable, les conditions de restauration, etc. ont été omis pour des raisons de concision.)

Vous effectuez d'abord la mise à jour pour ne pas insert de nouvelles lignes, puis les mettre à jour, en effectuant deux fois le travail et éventuellement en comptant deux fois. Vous pouvez utiliser la sortie dans les deux cas dans une table temporaire, peut-être, pour archiver / supprimer des lignes de la table tx.

Je vous conseille de ne pas être trop excité à propos de MERGE jusqu'à ce qu'ils aient résolu certains de ces bugs et que vous en ayez suffisamment lu pour être sûr que vous ne serez pas trompé dans une fausse confiance quant à la "meilleure" concomitance et l'atomicité sans indices supplémentaires . Les conditions de course que vous pouvez contourner les bugs tu ne peux pas.

Une autre alternative, à partir du commentaire de Nikola

 CREATE VIEW dbo.TotalsView WITH SCHEMABINDING AS SELECT some_key_column(s), SUM(amount), COUNT_BIG(*) FROM dbo.Transaction_Table GROUP BY some_key_column(s); GO CREATE UNIQUE CLUSTERED INDEX some_key ON dbo.TotalsView(some_key_column(s)); GO 

Maintenant, si vous voulez écrire des requêtes qui saisissent les totaux, vous pouvez referencer directement la vue ou – en fonction de la requête et de l'édition – la vue peut correspondre automatiquement même si vous faites reference à la table de base.

Remarque: si vous n'êtes pas sur Enterprise Edition, vous devrez peut-être utiliser l'indicateur NOEXPAND pour tirer parti des valeurs pré-agrégées matérialisées par la vue.

Je ne pense pas que vous ayez besoin de la boucle.

Tu peux juste

 Update all rows/sums that match your filters/ groups Archive/ delete previous. Insert all rows that do not match your filter/ groups Archive/ delete previous. 

SQL est censé utiliser datatables de masse pas les lignes une par une.