Cette requête affiche le nombre d'utilisateurs actifs en vigueur à la fin du mois.
Comment ça marche :
-
Convertir chaque ligne d'entrée (avec
StartDate
etEndDate
valeur) en deux lignes qui représentent un point dans le temps lorsque le nombre d'utilisateurs actifs a augmenté (leStartDate
) et décrémenté (leEndDate
). Nous devons convertirNULL
à une valeur de date lointaine carNULL
les valeurs sont triées avant au lieu d'après non-NULL
valeurs :Cela donne à vos données l'aspect suivant :
OnThisDate Change 2018-01-01 1 2019-01-01 -1 2018-01-01 1 9999-12-31 -1 2019-01-01 1 2019-06-01 -1 2017-01-01 1 2019-03-01 -1
-
Ensuite, nous avons simplement
SUM OVER
leChange
valeurs (après tri) pour obtenir le nombre d'utilisateurs actifs à cette date spécifique :Alors d'abord, triez par
OnThisDate
:OnThisDate Change 2017-01-01 1 2018-01-01 1 2018-01-01 1 2019-01-01 1 2019-01-01 -1 2019-03-01 -1 2019-06-01 -1 9999-12-31 -1
Puis
SUM OVER
:OnThisDate ActiveCount 2017-01-01 1 2018-01-01 2 2018-01-01 3 2019-01-01 4 2019-01-01 3 2019-03-01 2 2019-06-01 1 9999-12-31 0
-
Ensuite, nous
PARTITION
(ne pas regrouper !) les lignes par mois et les trier par date afin que nous puissions identifier le dernierActiveCount
ligne pour ce mois (cela se produit en fait dans leWHERE
de la requête la plus externe, en utilisantROW_NUMBER()
etCOUNT()
pour chaque moisPARTITION
):OnThisDate ActiveCount IsLastInMonth 2017-01-01 1 1 2018-01-01 2 0 2018-01-01 3 1 2019-01-01 4 0 2019-01-01 3 1 2019-03-01 2 1 2019-06-01 1 1 9999-12-31 0 1
-
Filtrez ensuite sur celui où
IsLastInMonth = 1
(en fait, oùROW_COUNT() = COUNT(*)
à l'intérieur de chaquePARTITION
) pour nous donner les données de sortie finale :At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Cela entraîne des "écarts" dans le jeu de résultats car le At-end-of-month
la colonne affiche uniquement les lignes où le Active-count
la valeur a en fait changé plutôt que d'inclure tous les mois civils possibles - mais c'est idéal (en ce qui me concerne) car cela exclut les données redondantes. Remplir les lacunes peut être fait à l'intérieur de votre code d'application en répétant simplement les lignes de sortie pour chaque mois supplémentaire jusqu'à ce qu'il atteigne le prochain At-end-of-month
valeur.
Voici la requête utilisant T-SQL sur SQL Server (je n'ai pas accès à Oracle pour le moment). Et voici le SQLFiddle que j'ai utilisé pour trouver une solution :http://sqlfiddle.com/# !18/ad68b7/24
SELECT
OtdYear,
OtdMonth,
ActiveCount
FROM
(
-- This query adds columns to indicate which row is the last-row-in-month ( where RowInMonth == RowsInMonth )
SELECT
OnThisDate,
OtdYear,
OtdMonth,
ROW_NUMBER() OVER ( PARTITION BY OtdYear, OtdMonth ORDER BY OnThisDate ) AS RowInMonth,
COUNT(*) OVER ( PARTITION BY OtdYear, OtdMonth ) AS RowsInMonth,
ActiveCount
FROM
(
SELECT
OnThisDate,
YEAR( OnThisDate ) AS OtdYear,
MONTH( OnThisDate ) AS OtdMonth,
SUM( [Change] ) OVER ( ORDER BY OnThisDate ASC ) AS ActiveCount
FROM
(
SELECT
StartDate AS [OnThisDate],
1 AS [Change]
FROM
tbl
UNION ALL
SELECT
ISNULL( EndDate, DATEFROMPARTS( 9999, 12, 31 ) ) AS [OnThisDate],
-1 AS [Change]
FROM
tbl
) AS sq1
) AS sq2
) AS sq3
WHERE
RowInMonth = RowsInMonth
ORDER BY
OtdYear,
OtdMonth
Cette requête peut être aplati en moins de requêtes imbriquées en utilisant directement les fonctions d'agrégation et de fenêtre au lieu d'utiliser des alias (comme OtdYear
, ActiveCount
, etc.), mais cela rendrait la requête beaucoup plus difficile à comprendre.