Тази заявка показва броя на активните потребители в сила към края на месеца.
Как работи:
-
Преобразувайте всеки въведен ред (с
StartDate
иEndDate
стойност) на две редове, които представляват момент от време, когато броят на активните потребители се е увеличил (наStartDate
) и намален (наEndDate
). Трябва да конвертирамеNULL
към стойност на далечна дата, защотоNULL
стойностите са сортирани преди вместо след не-NULL
стойности:Това прави вашите данни да изглеждат така:
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
-
След това просто
SUM OVER
Change
стойности (след сортиране), за да получите броя на активните потребители към тази конкретна дата:Така че първо сортирайте по
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
След това
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
-
След това ние
PARTITION
(не групиране!) редовете по месеци и ги сортирайте по датата им, за да можем да идентифицираме последнияActiveCount
ред за този месец (това всъщност се случва вWHERE
на най-външната заявка, използвайкиROW_NUMBER()
иCOUNT()
за всеки месецPARTITION
):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
-
След това филтрирайте това, където
IsLastInMonth = 1
(всъщност, къдетоROW_COUNT() = COUNT(*)
във всекиPARTITION
), за да ни даде крайните изходни данни:At-end-of-month Active-count 2017-01 1 2018-01 3 2019-01 3 2019-03 2 2019-06 1 9999-12 0
Това наистина води до „празнини“ в набора от резултати, защото At-end-of-month
показва само редове, където Active-count
стойността всъщност се промени, вместо да включва всички възможни календарни месеци - но това е идеално (що се отнася до мен), защото изключва излишни данни. Попълването на празнините може да се направи в кода на приложението ви, като просто повтаряте изходните редове за всеки допълнителен месец, докато стигне до следващия At-end-of-month
стойност.
Ето заявката, използваща T-SQL на SQL Server (в момента нямам достъп до Oracle). И ето SQLFiddle, който използвах, за да стигна до решение: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
Тази заявка може да бъдат сведени до по-малко вложени заявки чрез директно използване на агрегатни и прозоречни функции, вместо използване на псевдоними (като OtdYear
, ActiveCount
, и т.н.), но това би направило заявката много по-трудна за разбиране.