Imaginez le tableau suivant (appelé TestTable
):
id somedate somevalue
-- -------- ---------
45 01/Jan/09 3
23 08/Jan/09 5
12 02/Feb/09 0
77 14/Feb/09 7
39 20/Feb/09 34
33 02/Mar/09 6
Je voudrais une requête qui renvoie un total cumulé dans l'ordre des dates, comme:
id somedate somevalue runningtotal
-- -------- --------- ------------
45 01/Jan/09 3 3
23 08/Jan/09 5 8
12 02/Feb/09 0 8
77 14/Feb/09 7 15
39 20/Feb/09 34 49
33 02/Mar/09 6 55
Je sais qu'il existe différentes manières de faire cela dans SQL Server 2000/2005/2008.
Je suis particulièrement intéressé par ce type de méthode qui utilise l'astuce de la déclaration d'agrégation:
INSERT INTO @AnotherTbl(id, somedate, somevalue, runningtotal)
SELECT id, somedate, somevalue, null
FROM TestTable
ORDER BY somedate
DECLARE @RunningTotal int
SET @RunningTotal = 0
UPDATE @AnotherTbl
SET @RunningTotal = runningtotal = @RunningTotal + somevalue
FROM @AnotherTbl
... c'est très efficace, mais j'ai entendu dire qu'il y a des problèmes à ce sujet car vous ne pouvez pas nécessairement garantir que l' UPDATE
instruction traitera les lignes dans le bon ordre. Peut-être pouvons-nous obtenir des réponses définitives à ce sujet.
Mais peut-être y a-t-il d'autres façons que les gens peuvent suggérer?
edit: Maintenant avec un SqlFiddle avec la configuration et l'exemple 'update trick' ci-dessus
la source
Réponses:
Mettez à jour , si vous exécutez SQL Server 2012, voir: https://stackoverflow.com/a/10309947
Le problème est que l'implémentation SQL Server de la clause Over est quelque peu limitée .
Oracle (et ANSI-SQL) vous permettent de faire des choses comme:
SQL Server ne vous donne aucune solution propre à ce problème. Mon instinct me dit que c'est l'un de ces rares cas où un curseur est le plus rapide, même si je devrai faire des analyses comparatives sur de gros résultats.
L'astuce de mise à jour est pratique mais je la trouve assez fragile. Il semble que si vous mettez à jour une table complète, elle procédera dans l'ordre de la clé primaire. Donc, si vous définissez votre date comme clé primaire ascendante, vous serez
probably
en sécurité. Mais vous vous appuyez sur un détail d'implémentation SQL Server non documenté (également si la requête finit par être exécutée par deux processus, je me demande ce qui va se passer, voir: MAXDOP):Échantillon de travail complet:
Vous avez demandé un point de repère, c'est la vérité.
Le moyen le plus rapide de le faire serait le curseur, c'est un ordre de grandeur plus rapide que la sous-requête corrélée de jointure croisée.
Le moyen le plus rapide absolu est l'astuce UPDATE. Ma seule préoccupation est que je ne suis pas certain que, dans toutes les circonstances, la mise à jour se déroulera de manière linéaire. Il n'y a rien dans la requête qui l'indique explicitement.
En bout de ligne, pour le code de production, j'irais avec le curseur.
Données de test:
Test 1:
Test 2:
Test 3:
Test 4:
la source
Dans SQL Server 2012, vous pouvez utiliser SUM () avec la clause OVER () .
Violon SQL
la source
Bien que Sam Saffron ait fait un excellent travail, il n'a toujours pas fourni de code d' expression de table commune récursive pour ce problème. Et pour nous qui travaillons avec SQL Server 2008 R2 et non Denali, c'est toujours le moyen le plus rapide d'obtenir un total en cours d'exécution, c'est environ 10 fois plus rapide que le curseur sur mon ordinateur de travail pour 100000 lignes, et c'est aussi une requête en ligne.
Donc, le voici (je suppose qu'il y a une
ord
colonne dans le tableau et son numéro séquentiel sans lacunes, pour un traitement rapide, il devrait également y avoir une contrainte unique sur ce nombre):sql fiddle demo
mise à jour J'étais également curieux de connaître cette mise à jour avec une mise à jour variable ou décalée . Donc, généralement, cela fonctionne bien, mais comment pouvons-nous être sûrs que cela fonctionne à chaque fois? eh bien, voici un petit truc (trouvé ici - http://www.sqlservercentral.com/Forums/Topic802558-203-21.aspx#bm981258 ) - il vous suffit de vérifier les affectations actuelles et précédentes
ord
et d'utiliser les1/0
affectations au cas où elles seraient différentes de celles vous attendez:D'après ce que j'ai vu si vous avez un index cluster / clé primaire approprié sur votre table (dans notre cas, il serait indexé par
ord_id
), la mise à jour se déroulera de manière linéaire tout le temps (jamais rencontré de division par zéro). Cela dit, c'est à vous de décider si vous souhaitez l'utiliser dans le code de production :)update 2 Je lie cette réponse, car elle comprend des informations utiles sur le manque de fiabilité de la mise à jour excentrique - comportement inexplicable de concaténation nvarchar / index / nvarchar (max) .
la source
L'opérateur APPLY dans SQL 2005 et supérieur fonctionne pour cela:
la source
Vous pouvez également utiliser la fonction ROW_NUMBER () et une table temporaire pour créer une colonne arbitraire à utiliser dans la comparaison sur l'instruction SELECT interne.
la source
Utilisez une sous-requête corrélée. Très simple, voilà:
Le code n'est peut-être pas tout à fait correct, mais je suis sûr que l'idée l'est.
Le GROUP BY est au cas où une date apparaît plus d'une fois, vous ne voudriez la voir qu'une seule fois dans le jeu de résultats.
Si cela ne vous dérange pas de voir des dates répétées ou si vous souhaitez voir la valeur et l'ID d'origine, voici ce que vous voulez:
la source
Vous pouvez également dénormaliser - stocker les totaux cumulés dans le même tableau:
http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/01/23/denormalizing-to-enforce-business-rules-running-totals.aspx
Les sélections fonctionnent beaucoup plus rapidement que toute autre solution, mais les modifications peuvent être plus lentes
la source
En supposant que le fenêtrage fonctionne sur SQL Server 2008 comme il le fait ailleurs (que j'ai essayé), essayez ceci:
MSDN dit qu'il est disponible dans SQL Server 2008 (et peut-être aussi 2005?) Mais je n'ai pas d'instance à portée de main pour l'essayer.
EDIT: eh bien, apparemment, SQL Server n'autorise pas une spécification de fenêtre ("OVER (...)") sans spécifier "PARTITION BY" (diviser le résultat en groupes mais ne pas agréger tout à fait comme GROUP BY). Gênant - la référence de syntaxe MSDN suggère que c'est facultatif, mais je n'ai que des instances de SqlServer 2000 pour le moment.
La requête que j'ai donnée fonctionne à la fois dans Oracle 10.2.0.3.0 et PostgreSQL 8.4-beta. Alors dis à MS de se rattraper;)
la source
1 partitionme
et partitionnez par cela. En outre, la partition par est probablement nécessaire dans des situations réelles lors de la création de rapports.Si vous utilisez Sql Server 2008 R2 ci-dessus. Ensuite, ce serait le moyen le plus court à faire;
LAG est utilisé pour obtenir la valeur de la ligne précédente. Vous pouvez faire google pour plus d'informations.
[1]:
la source
SUM(somevalue) OVER(...)
ce qui me semble beaucoup plus propreJe crois qu'un total cumulé peut être atteint en utilisant la simple opération INNER JOIN ci-dessous.
la source
Ce qui suit produira les résultats requis.
Avoir un index clusterisé sur SomeDate améliorera considérablement les performances.
la source
Utilisation de la jointure Une autre variante consiste à utiliser la jointure. Maintenant, la requête pourrait ressembler à:
Pour en savoir plus, vous pouvez visiter ce lien http://askme.indianyouth.info/details/calculating-simple-running-totals-in-sql-server-12
la source
Bien que le meilleur moyen de le faire soit d'utiliser une fonction de fenêtre, cela peut également être fait en utilisant une simple sous-requête corrélée .
la source
la source
Voici 2 façons simples de calculer le total cumulé:
Approche 1 : Elle peut être écrite de cette façon si votre SGBD prend en charge les fonctions analytiques
Approche 2 : Vous pouvez utiliser OUTER APPLY si votre version de base de données / SGBD lui-même ne prend pas en charge les fonctions analytiques
Remarque: - Si vous devez calculer le total cumulé pour différentes partitions séparément, cela peut être fait comme indiqué ici: Calcul des totaux cumulés sur les lignes et regroupement par ID
la source