Esta consulta muestra el recuento de usuarios activos a partir de fin de mes.
Cómo funciona:
-
Convierta cada fila de entrada (con
StartDate
yEndDate
valor) en dos filas que representan un punto en el tiempo cuando se incrementó el recuento de usuarios activos (enStartDate
) y decrementado (enEndDate
). Necesitamos convertirNULL
a un valor de fecha lejano porqueNULL
los valores se ordenan antes en lugar de después de noNULL
valores:Esto hace que sus datos se vean así:
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
-
Entonces simplemente
SUM OVER
elChange
valores (después de ordenar) para obtener el recuento de usuarios activos a partir de esa fecha específica:Primero, ordena por
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
Entonces
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
-
Luego
PARTITION
(¡no agrupe!) las filas por mes y ordénelas por su fecha para que podamos identificar el últimoActiveCount
fila para ese mes (esto realmente sucede en elWHERE
de la consulta más externa, usandoROW_NUMBER()
yCOUNT()
para cada mesPARTITION
):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
-
Luego filtre en eso donde
IsLastInMonth = 1
(en realidad, dondeROW_COUNT() = COUNT(*)
dentro de cadaPARTITION
) para darnos los datos finales de salida:At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Esto da como resultado "brechas" en el conjunto de resultados porque At-end-of-month
columna solo muestra filas donde el Active-count
el valor realmente cambió en lugar de incluir todos los meses calendario posibles, pero eso es ideal (en lo que a mí respecta) porque excluye datos redundantes. Puede completar los espacios en blanco dentro del código de su aplicación simplemente repitiendo las filas de salida para cada mes adicional hasta que llegue al siguiente At-end-of-month
valor.
Aquí está la consulta usando T-SQL en SQL Server (no tengo acceso a Oracle en este momento). Y aquí está el SQLFiddle que usé para llegar a una solución: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
Esta consulta puede aplanarse en menos consultas anidadas usando funciones agregadas y de ventana directamente en lugar de usar alias (como OtdYear
, ActiveCount
, etc.), pero eso haría que la consulta fuera mucho más difícil de entender.