如何在我的查询中添加一个递归组件以添加x个日期?

时间:2018-11-15 14:24:43

标签: sql sql-server recursion common-table-expression

我必须根据用户的上次登录将其划分为4个类别。 每个类别代表他们最近一次登录的时间(即过去28天,29到45天之间,等等) 如果我今天想弄清楚,它很简单。但是,我需要过去60天的快照。换句话说,最近60天前28天有多少用户登录。我需要过去60天的数据。 自然,我想节省一些时间,而不是全部使用60 UNION和60个声明日期。可以通过WITH循环实现吗?

这是一个带有两个仲裁日期的示例(理想情况下,id为11月13日之前的所有60天):

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表,这似乎正是我所需要的,但无法应用逻辑。 这是来自所有工会的输出的样本。第二行提供了过去的信息,该信息不会考虑该日期之后的登录记录。

+------------+------+-----+--+
| 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 :(得分:0)

循环绝对不是去这里的正确方法。您要使用的是一个理货或数字表。 Jeff Moden在这里有一篇很棒的文章。 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())))

此查询是完全动态的,因为返回的日期将基于当前系统日期而更改。还要注意,在您发布的样本数据中,有7行未计数,因为它们早于今天60天。

-编辑-

由于无法创建视图,因此可以使用cte内联。我删除了最后一个联接,因为您不需要那么多行。像这样的东西。

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())))

关于将其与您的代码合并,我不理解您想要输出什么,这就是为什么我问了很多遍的原因。您不想在代码中使用它。它是基于循环的,您应该避免。这些方面需要用基于集合的方法来代替。