Тази заявка показва броя на активните потребители в сила към края на месеца.
Как работи:
-
Преобразувайте всеки въведен ред (с
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 OVERChangeстойности (след сортиране), за да получите броя на активните потребители към тази конкретна дата:Така че първо сортирайте по
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, който използвах, за да стигна до решение: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
Тази заявка може да бъдат сведени до по-малко вложени заявки чрез директно използване на агрегатни и прозоречни функции, вместо използване на псевдоними (като OtdYear , ActiveCount , и т.н.), но това би направило заявката много по-трудна за разбиране.