Questa query mostra il conteggio degli utenti attivi a partire dalla fine del mese.
Come funziona:
-
Converti ogni riga di input (con
StartDate
eEndDate
valore) in due righe che rappresentano un momento in cui il conteggio degli utenti attivi è aumentato (suStartDate
) e decrementato (suEndDate
). Dobbiamo convertireNULL
a un valore di data lontano perchéNULL
i valori sono ordinati prima anziché dopo nonNULL
valori:In questo modo i tuoi dati assomiglieranno a questo:
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
-
Quindi semplicemente
SUM OVER
ilChange
valori (dopo l'ordinamento) per ottenere il conteggio degli utenti attivi a partire da quella data specifica:Quindi, prima, ordina per
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
Quindi
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
-
Quindi
PARTITION
(non raggruppare!) le righe per mese e ordinarle per data in modo da poter identificare l'ultimoActiveCount
riga per quel mese (questo accade effettivamente nelWHERE
della query più esterna, utilizzandoROW_NUMBER()
eCOUNT()
per ogni mesePARTITION
):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
-
Quindi filtra su quello dove
IsLastInMonth = 1
(in realtà, doveROW_COUNT() = COUNT(*)
all'interno di ogniPARTITION
) per fornirci i dati di output finali:At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Ciò comporta "lacune" nel set di risultati perché At-end-of-month
la colonna mostra solo le righe in cui il Active-count
il valore è effettivamente cambiato anziché includere tutti i possibili mesi di calendario, ma è l'ideale (per quanto mi riguarda) perché esclude i dati ridondanti. È possibile riempire gli spazi vuoti all'interno del codice dell'applicazione semplicemente ripetendo le righe di output per ogni mese aggiuntivo fino a raggiungere il At-end-of-month
successivo valore.
Ecco la query che utilizza T-SQL su SQL Server (non ho accesso a Oracle in questo momento). Ed ecco SQLFiddle che ho usato per trovare una soluzione: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
Questa query può essere ridotto in un minor numero di query nidificate utilizzando direttamente le funzioni di aggregazione e finestra invece di utilizzare alias (come OtdYear
, ActiveCount
, ecc.), ma ciò renderebbe la query molto più difficile da capire.