Diese Abfrage zeigt die Anzahl der aktiven Benutzer zum Monatsende.
Wie es funktioniert:
-
Konvertieren Sie jede Eingabezeile (mit
StartDate
undEndDate
Wert) in zwei Zeilen, die einen Zeitpunkt darstellen, an dem die Anzahl der aktiven Benutzer erhöht wurde (amStartDate
) und dekrementiert (amEndDate
). Wir müssenNULL
konvertieren zu einem fernen Datumswert, weilNULL
Werte werden vor statt nach Nicht-NULL
sortiert Werte:Dadurch sehen Ihre Daten wie folgt aus:
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
-
Dann
SUM OVER
Sie einfach dieChange
Werten (nach dem Sortieren), um die Anzahl der aktiven Benutzer zu diesem bestimmten Datum zu erhalten:Sortieren Sie also zuerst nach
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
Dann
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
-
Dann
PARTITION
(nicht gruppieren!) die Zeilen nach Monat und sortieren sie nach ihrem Datum, damit wir den letztenActiveCount
identifizieren können Zeile für diesen Monat (das passiert eigentlich imWHERE
der äußersten Abfrage mitROW_NUMBER()
undCOUNT()
für jeden MonatPARTITION
):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
-
Dann filtern Sie nach dem, wo
IsLastInMonth = 1
ist (eigentlich, woROW_COUNT() = COUNT(*)
innerhalb jederPARTITION
), um uns die endgültigen Ausgabedaten zu geben:At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Dies führt zu "Lücken" in der Ergebnismenge, da der At-end-of-month
Spalte zeigt nur Zeilen, in denen der Active-count
Wert tatsächlich geändert, anstatt alle möglichen Kalendermonate einzubeziehen - aber das ist (meiner Meinung nach) ideal, weil es redundante Daten ausschließt. Das Ausfüllen der Lücken kann in Ihrem Anwendungscode erfolgen, indem Sie einfach die Ausgabezeilen für jeden weiteren Monat wiederholen, bis der nächste At-end-of-month
erreicht wird Wert.
Hier ist die Abfrage mit T-SQL auf SQL Server (ich habe derzeit keinen Zugriff auf Oracle). Und hier ist das SQLFiddle, mit dem ich zu einer Lösung gekommen bin: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
Diese Abfrage kann in weniger verschachtelte Abfragen reduziert werden, indem Aggregat- und Fensterfunktionen direkt verwendet werden, anstatt Aliase (wie OtdYear
) zu verwenden , ActiveCount
, usw.), aber das würde die Abfrage viel schwerer verständlich machen.