Questa query mostra il conteggio degli utenti attivi a partire dalla fine del mese.
Come funziona:
-
Converti ogni riga di input (con
StartDateeEndDatevalore) in due righe che rappresentano un momento in cui il conteggio degli utenti attivi è aumentato (suStartDate) e decrementato (suEndDate). Dobbiamo convertireNULLa un valore di data lontano perchéNULLi valori sono ordinati prima anziché dopo nonNULLvalori: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 OVERilChangevalori (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 -1Quindi
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'ultimoActiveCountriga per quel mese (questo accade effettivamente nelWHEREdella 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:https://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.