Esta consulta mostra a contagem de usuários ativos efetiva no final do mês.
Como funciona:
-
Converta cada linha de entrada (comStartDateeEndDatevalor) em dois linhas que representam um ponto no tempo quando a contagem de usuários ativos foi incrementada (emStartDate) e decrementado (emEndDate). Precisamos converterNULLpara um valor de data distante porqueNULLos valores são classificados antes em vez de depois de nãoNULLvalores:
Isso faz com que seus dados fiquem assim:
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 -
Então nós simplesmenteSUM OVERaChangevalores (após a classificação) para obter a contagem de usuários ativos a partir dessa data específica:
Então, primeiro, classifique porOnThisDate:
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
EntãoSUM 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 -
Então nósPARTITION(não agrupar!) as linhas por mês e classificá-las por data para que possamos identificar o últimoActiveCountlinha para esse mês (isso realmente acontece noWHEREda consulta mais externa, usandoROW_NUMBER()eCOUNT()para cada mêsPARTITION):
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 -
Em seguida, filtre ondeIsLastInMonth = 1(na verdade, ondeROW_COUNT() = COUNT(*)dentro de cadaPARTITION) para nos fornecer os dados de saída finais:
At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Isso resulta em "lacunas" no conjunto de resultados porque o
At-end-of-month coluna mostra apenas linhas onde o Active-count valor realmente mudou em vez de incluir todos os meses do calendário possíveis - mas isso é ideal (no que me diz respeito) porque exclui dados redundantes. O preenchimento das lacunas pode ser feito dentro do código do aplicativo simplesmente repetindo as linhas de saída para cada mês adicional até atingir o próximo At-end-of-month valor. Aqui está a consulta usando T-SQL no SQL Server (não tenho acesso ao Oracle agora). E aqui está o SQLFiddle que usei para chegar a uma solução: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
Esta consulta pode ser achatado em menos consultas aninhadas usando funções de agregação e janela diretamente em vez de usar aliases (como
OtdYear , ActiveCount , etc), mas isso tornaria a consulta muito mais difícil de entender.