J'essaie de calculer le total cumulé. Mais il devrait se réinitialiser lorsque la somme cumulée supérieure à une autre valeur de colonne
create table #reset_runn_total
(
id int identity(1,1),
val int,
reset_val int,
grp int
)
insert into #reset_runn_total
values
(1,10,1),
(8,12,1),(6,14,1),(5,10,1),(6,13,1),(3,11,1),(9,8,1),(10,12,1)
SELECT Row_number()OVER(partition BY grp ORDER BY id)AS rn,*
INTO #test
FROM #reset_runn_total
Détails de l'index:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
exemples de données
+----+-----+-----------+-----+
| id | val | reset_val | Grp |
+----+-----+-----------+-----+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 1 |
| 3 | 6 | 14 | 1 |
| 4 | 5 | 10 | 1 |
| 5 | 6 | 13 | 1 |
| 6 | 3 | 11 | 1 |
| 7 | 9 | 8 | 1 |
| 8 | 10 | 12 | 1 |
+----+-----+-----------+-----+
Résultat attendu
+----+-----+-----------------+-------------+
| id | val | reset_val | Running_tot |
+----+-----+-----------------+-------------+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 9 | --1+8
| 3 | 6 | 14 | 15 | --1+8+6 -- greater than reset val
| 4 | 5 | 10 | 5 | --reset
| 5 | 6 | 13 | 11 | --5+6
| 6 | 3 | 11 | 14 | --5+6+3 -- greater than reset val
| 7 | 9 | 8 | 9 | --reset -- greater than reset val
| 8 | 10 | 12 | 10 | --reset
+----+-----+-----------------+-------------+
Requete:
J'ai obtenu le résultat en utilisant Recursive CTE
. La question d'origine est ici /programming/42085404/reset-running-total-based-on-another-column
;WITH cte
AS (SELECT rn,id,
val,
reset_val,
grp,
val AS running_total,
Iif (val > reset_val, 1, 0) AS flag
FROM #test
WHERE rn = 1
UNION ALL
SELECT r.*,
Iif(c.flag = 1, r.val, c.running_total + r.val),
Iif(Iif(c.flag = 1, r.val, c.running_total + r.val) > r.reset_val, 1, 0)
FROM cte c
JOIN #test r
ON r.grp = c.grp
AND r.rn = c.rn + 1)
SELECT *
FROM cte
Existe-t-il une meilleure alternative T-SQL
sans utiliser CLR
.?
50000
groupes avec des60
identifiants . le nombre total d'enregistrements sera donc d'environ3000000
. Je suis sûr queRecursive CTE
cela ne sera pas bien adapté3000000
. Mettra à jour les mesures à mon retour au bureau. Pouvons-nous y parvenir en utilisantsum()Over(Order by)
comme vous l'avez utilisé dans cet article sqlperformance.com/2012/07/07/t-sql-queries/running-totalsRéponses:
J'ai examiné des problèmes similaires et je n'ai jamais été en mesure de trouver une solution de fonction de fenêtre qui effectue un seul passage sur les données. Je ne pense pas que ce soit possible. Les fonctions de fenêtre doivent pouvoir être appliquées à toutes les valeurs d'une colonne. Cela rend les calculs de réinitialisation comme celui-ci très difficiles, car une réinitialisation modifie la valeur de toutes les valeurs suivantes.
Une façon de penser au problème est que vous pouvez obtenir le résultat final souhaité si vous calculez un total cumulé de base tant que vous pouvez soustraire le total cumulé de la ligne précédente correcte. Par exemple, dans vos exemples de données, la valeur de
id
4 est lerunning total of row 4 - the running total of row 3
. La valeur deid
6 est lerunning total of row 6 - the running total of row 3
car une réinitialisation n'a pas encore eu lieu. La valeur deid
7 est lerunning total of row 7 - the running total of row 6
et ainsi de suite.J'aborderais cela avec T-SQL en boucle. Je me suis un peu emporté et je pense avoir une solution complète. Pour 3 millions de lignes et 500 groupes, le code s'est terminé en 24 secondes sur mon bureau. Je teste avec SQL Server 2016 Developer Edition avec 6 vCPU. Je profite des insertions parallèles et de l'exécution parallèle en général, vous devrez donc peut-être modifier le code si vous utilisez une version antérieure ou si vous avez des limitations DOP.
Ci-dessous le code que j'ai utilisé pour générer les données. Les plages
VAL
etRESET_VAL
doivent être similaires à vos exemples de données.L'algorithme est le suivant:
1) Commencez par insérer toutes les lignes avec un total cumulé standard dans une table temporaire.
2) En boucle:
2a) Pour chaque groupe, calculez la première ligne avec un total cumulé au-dessus de la valeur reset_value restante dans la table et stockez l'id, le total cumulé qui était trop grand et le total cumulé précédent qui était trop grand dans une table temporaire.
2b) Supprimez les lignes de la première table temporaire dans une table temporaire de résultats dont la valeur est
ID
inférieure ou égale à celleID
de la seconde table temporaire. Utilisez les autres colonnes pour ajuster le total cumulé selon vos besoins.3) Après que la suppression ne traite plus les lignes, exécutez-en une supplémentaire
DELETE OUTPUT
dans le tableau des résultats. Il s'agit des lignes à la fin du groupe qui ne dépassent jamais la valeur de réinitialisation.Je vais passer en revue une implémentation de l'algorithme ci-dessus dans T-SQL étape par étape.
Commencez par créer quelques tables temporaires.
#initial_results
contient les données d'origine avec le total cumulé standard,#group_bookkeeping
est mise à jour à chaque boucle pour déterminer quelles lignes peuvent être déplacées et#final_results
contient les résultats avec le total cumulé ajusté pour les réinitialisations.Je crée l'index cluster sur la table temporaire après pour que l'insertion et la construction de l'index puissent se faire en parallèle. Cela a fait une grande différence sur ma machine mais peut-être pas sur la vôtre. La création d'un index sur la table source n'a pas semblé aider mais cela pourrait aider sur votre machine.
Le code ci-dessous s'exécute dans la boucle et met à jour la table de comptabilité. Pour chaque groupe, nous devons obtenir la recherche du maximum
ID
qui doit être déplacé dans le tableau des résultats. Nous avons besoin du total cumulé de cette ligne pour pouvoir le soustraire du total cumulé initial. Lagrp_done
colonne est définie sur 1 lorsqu'il n'y a plus de travail à faire pour agrp
.Vraiment pas un fan de l'
LOOP JOIN
indice en général, mais c'est une requête simple et c'était le moyen le plus rapide d'obtenir ce que je voulais. Pour vraiment optimiser le temps de réponse, je voulais des jointures de boucles imbriquées parallèles au lieu de jointures de fusion DOP 1.Le code ci-dessous s'exécute dans la boucle et déplace les données de la table initiale vers la table de résultats finale. Remarquez l'ajustement du total cumulé initial.
Pour votre commodité, voici le code complet:
la source
Recursive CTE
a pris 2 minutes et 15 secondesUtilisation d'un CURSEUR:
Vérifiez ici: http://rextester.com/WSPLO95303
la source
Version non fenêtrée, mais pure SQL:
Je ne suis pas un spécialiste du dialecte de SQL Server. Il s'agit d'une version initiale pour PostrgreSQL (si je comprends bien je ne peux pas utiliser LIMIT 1 / TOP 1 en partie récursive dans SQL Server):
la source
grp
colonne.Il semble que vous ayez plusieurs requêtes / méthodes pour attaquer le problème, mais vous ne nous avez pas fourni - ou même envisagé? - les index sur la table.
Quels index y a-t-il dans le tableau? Est-ce un tas ou a-t-il un index clusterisé?
J'essaierais les différentes solutions suggérées après avoir ajouté cet index:
Ou tout simplement changer (ou faire) l'index cluster pour être
(grp, id)
.Avoir un index qui cible la requête spécifique devrait améliorer l'efficacité - de la plupart des méthodes sinon de toutes.
la source