Den här frågan visar antalet aktiva användare som gäller i slutet av månaden.
Hur det fungerar:
-
Konvertera varje inmatningsrad (med
StartDate
ochEndDate
värde) till två rader som representerar en tidpunkt när antalet aktiva användare ökade (påStartDate
) och minskas (påEndDate
). Vi måste konverteraNULL
till ett långt borta datumvärde eftersomNULL
värden sorteras före istället för efter icke-NULL
värden:Detta får din data att se ut så här:
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
-
Sedan
SUM OVER
helt enkeltChange
värden (efter sortering) för att få antalet aktiva användare från det specifika datumet:Så först, sortera efter
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
Sedan
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
-
Sedan
PARTITION
(inte gruppera!) raderna efter månad och sortera dem efter deras datum så att vi kan identifiera den senasteActiveCount
rad för den månaden (detta händer faktiskt iWHERE
för den yttersta frågan medROW_NUMBER()
ochCOUNT()
för varje månadPARTITION
):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
-
Filtrera sedan på det där
IsLastInMonth = 1
(faktiskt därROW_COUNT() = COUNT(*)
inuti varjePARTITION
) för att ge oss de slutliga utdata:At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Detta resulterar i "luckor" i resultatuppsättningen eftersom At-end-of-month
kolumnen visar bara rader där Active-count
värdet ändrades faktiskt snarare än att inkludera alla möjliga kalendermånader - men det är idealiskt (såvitt jag är orolig) eftersom det utesluter överflödig data. Fylla i luckorna kan göras i din applikationskod genom att helt enkelt upprepa utmatningsraderna för varje ytterligare månad tills den når nästa At-end-of-month
värde.
Här är frågan med T-SQL på SQL Server (jag har inte tillgång till Oracle just nu). Och här är SQLFiddle som jag använde för att komma fram till en lösning: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
Den här frågan kan plattas till färre kapslade frågor genom att använda aggregat- och fönsterfunktioner direkt istället för att använda alias (som OtdYear
, ActiveCount
, etc) men det skulle göra frågan mycket svårare att förstå.