Как добавить рекурсивный компонент в мой запрос, чтобы добавить х количество дат? - PullRequest
0 голосов
/ 15 ноября 2018

Я должен разделить пользователей на 4 категории на основе их последних входов в систему.каждая категория отображает, каким был последний вход в систему (т.е. последние 28 дней, от 29 до 45 дней и т. д.). Если я хочу выяснить это на сегодня, это просто.Однако мне нужны снимки за последние 60 дней.Другими словами, сколько пользователей вошли в систему за последние 28 дней, 60 дней назад.Мне нужны данные за последние 60 дней.естественно, я пытаюсь сэкономить время и вместо того, чтобы использовать 60 UNION all и 60 объявлять даты.Это может быть достигнуто с помощью цикла WITH?

Вот пример с двумя произвольными датами (в идеале id должно быть все 60 дней до 13 ноября):

DECLARE @dDate AS DATE = '2018-11-13'
DECLARE @dDate2 AS DATE = '2018-10-13'
SELECT @dDate as First_Date
    ,COUNT(CASE WHEN subquery2.Total_Days <=28 THEN 1 ELSE NULL END) As GOOD
    ,COUNT(CASE WHEN subquery2.Total_Days BETWEEN 28 AND 85 THEN 1 ELSE NULL END) As BAD
FROM (
    SELECT *, DATEDIFF(dd,subquery1.Max_login_time,@dDate) As Total_Days
        FROM(
            SELECT DISTINCT emails
                ,MAX(login_time) AS Max_login_time
            FROM #test1
            WHERE login_time < @dDate
            GROUP BY emails
            ) AS subquery1
        ) as subquery2
UNION ALL
SELECT @dDate2 as First_Date
    ,COUNT(CASE WHEN subquery2.Total_Days <=28 THEN 1 ELSE NULL END) As GOOD
    ,COUNT(CASE WHEN subquery2.Total_Days BETWEEN 28 AND 85 THEN 1 ELSE NULL END) As BAD
FROM (
    SELECT *, DATEDIFF(dd,subquery1.Max_login_time,@dDate2) As Total_Days
        FROM(
            SELECT DISTINCT emails
                ,MAX(login_time) AS Max_login_time
            FROM #test1
            WHERE login_time < @dDate2
            GROUP BY emails
            ) AS subquery1
        ) as subquery2

Теперь, если я продолжу с этимЛогика Мне нужно будет включить еще 58 союзных дат и даты @declare, что явно крайне неэффективно.Я читал немного о рекурсивных таблицах CTE, которые, кажется, как раз то, что мне нужно, но я не могу применить логику.Вот пример вывода из объединения all.Вторая строка дает мне информацию в прошлом, которая не учитывала бы логины после этой даты.

+------------+------+-----+--+
| First_date | GOOD | BAD |  |
+------------+------+-----+--+
| 2018-11-13 |    2 |   2 |  |
| 2018-10-13 |    3 |   1 |  |
+------------+------+-----+--+

Вот пример данных, с которыми я работаю:

CREATE TABLE #test1 (
    login_time DATE
    ,emails CHAR(30)
    )

INSERT INTO #test1
VALUES  ('2018-11-10', 'a@gmail.com')
,('2018-10-01', 'a@gmail.com')
,('2018-09-01', 'a@gmail.com')
,('2018-01-01', 'a@gmail.com')
,('2018-08-01', 'b@gmail.com')
,('2018-07-01', 'b@gmail.com')
,('2018-09-01', 'b@gmail.com')
,('2018-04-01', 'c@gmail.com')
,('2018-10-01', 'c@gmail.com')
,('2018-10-02', 'c@gmail.com')
,('2018-11-10', 'd@gmail.com')
,('2018-09-18', 'd@gmail.com')
,('2018-11-09', 'd@gmail.com')
,('2018-07-01', 'd@gmail.com')

1 Ответ

0 голосов
/ 15 ноября 2018

Цикл - определенно неправильный путь.Вместо этого вы хотите использовать таблицу подсчета или чисел.У Джеффа Модена есть отличная статья на эту тему.http://www.sqlservercentral.com/articles/T-SQL/62867/

На самом деле я держу один в моей системе как вид.Это тупо быстро и невероятно полезно.Мой взгляд выглядит следующим образом.

create View [dbo].[cteTally] as

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
    cteTally(N) AS 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
    )
select N from cteTally
GO

Тогда использовать его с вашим образцом довольно просто.

select convert(date, DATEADD(day, t.N - 60, dateadd(day, -1, getdate())))
    , count(login_time)
from cteTally t
left join #test1 t1 on t1.login_time = convert(date, DATEADD(day, t.N - 60, dateadd(day, -1, getdate())))
where t.N <= 60
group by convert(date, DATEADD(day, t.N - 60, dateadd(day, -1, getdate())))

Этот запрос является полностью динамическим, так как возвращаемые даты будут меняться в зависимости от текущей системной даты.Также обратите внимание, что в размещенных вами образцах данных есть семь строк, которые не учитываются, поскольку они раньше, чем за 60 дней до сегодняшнего дня.

- РЕДАКТИРОВАТЬ -

Поскольку вы можете 't создать вид, который вы можете использовать cte inline.Я вынул последнее соединение, потому что тебе не нужно столько строк.Примерно так.

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
    cteTally(N) AS 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
    )

select convert(date, DATEADD(day, t.N - 60, dateadd(day, -1, getdate())))
    , count(login_time)
from cteTally t
left join #test1 t1 on t1.login_time = convert(date, DATEADD(day, t.N - 60, dateadd(day, -1, getdate())))
where t.N <= 60
group by convert(date, DATEADD(day, t.N - 60, dateadd(day, -1, getdate())))

Что касается включения этого в ваш код, я не понимаю, что вы хотите получить в качестве выходных данных, поэтому я спрашивал так много раз.Вы не хотите использовать свой код, как он есть.Это основано на цикле, что вы должны избегать.Его необходимо заменить подходом, основанным на множестве, в этом направлении.

...