DATEDIFF 按创建日期划分范围,利用外部日期按月计算年龄历史

Posted

技术标签:

【中文标题】DATEDIFF 按创建日期划分范围,利用外部日期按月计算年龄历史【英文标题】:DATEDIFF ranges by created date utilizing outer date to count age history by month 【发布时间】:2020-12-08 14:59:12 【问题描述】:

利用 DATEDIFF 查找两个日期之间的天数差异很容易,我过去曾多次使用它。这次我似乎无法弄清楚我猜何时需要使用“外部”参考。我查询的目标是“回到过去”并查看我的 SQL Server 数据库中的票证。然后根据第一天计算他们在该月是否仍然营业的年龄,然后将计数放入“0 到 30 天”、“31 到 60 天”、“61 到 90 天”等桶中天”和“91 岁以上”。我这里有以下测试数据集:

USE [TestDB]
GO
/****** Object:  Table [dbo].[taskDB]    Script Date: 12/8/2020 9:49:25 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[taskDB](
    [ticket] [varchar](50) NULL,
    [created] [date] NULL,
    [closed] [date] NULL,
    [rating] [varchar](50) NULL,
    [status] [varchar](50) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023345', CAST(N'2019-09-01' AS Date), CAST(N'2020-01-17' AS Date), N'Low', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023346', CAST(N'2019-08-01' AS Date), CAST(N'2019-08-03' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023347', CAST(N'2019-09-01' AS Date), CAST(N'2019-09-20' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023348', CAST(N'2019-08-01' AS Date), CAST(N'2020-08-06' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023349', CAST(N'2020-08-01' AS Date), CAST(N'2020-08-05' AS Date), N'Medium', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023350', CAST(N'2019-08-01' AS Date), CAST(N'2019-08-05' AS Date), N'Medium', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023351', CAST(N'2019-12-22' AS Date), CAST(N'1900-01-01' AS Date), N'High', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023352', CAST(N'2019-11-07' AS Date), CAST(N'2020-08-05' AS Date), N'Medium', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023353', CAST(N'2020-08-02' AS Date), CAST(N'1900-01-01' AS Date), N'Low', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023354', CAST(N'2019-08-02' AS Date), CAST(N'2019-08-05' AS Date), N'Medium', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023356', CAST(N'2019-08-02' AS Date), CAST(N'2019-08-05' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023357', CAST(N'2019-08-06' AS Date), CAST(N'2020-07-05' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023358', CAST(N'2019-10-04' AS Date), CAST(N'1900-01-01' AS Date), N'Low', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023359', CAST(N'2019-12-02' AS Date), CAST(N'2020-02-25' AS Date), N'High', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023360', CAST(N'2019-08-05' AS Date), CAST(N'2019-08-05' AS Date), N'Medium', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023361', CAST(N'2020-08-02' AS Date), CAST(N'1900-01-01' AS Date), N'High', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023362', CAST(N'2019-09-02' AS Date), CAST(N'2019-10-06' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023363', CAST(N'2019-10-03' AS Date), CAST(N'2019-11-08' AS Date), N'High', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023365', CAST(N'2019-10-03' AS Date), CAST(N'2019-12-08' AS Date), N'Low', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023364', CAST(N'2019-11-03' AS Date), CAST(N'2019-11-05' AS Date), N'High', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023366', CAST(N'2020-06-03' AS Date), CAST(N'1900-01-01' AS Date), N'High', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023368', CAST(N'2019-08-03' AS Date), CAST(N'2019-08-05' AS Date), N'High', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023367', CAST(N'2019-11-03' AS Date), CAST(N'1900-01-01' AS Date), N'Low', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023371', CAST(N'2019-08-03' AS Date), CAST(N'2019-08-05' AS Date), N'Low', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023370', CAST(N'2019-08-03' AS Date), CAST(N'2019-08-05' AS Date), N'Critical', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023434', CAST(N'2020-09-03' AS Date), NULL, N'Low', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'012312', CAST(N'2020-08-14' AS Date), NULL, N'High', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'054312', CAST(N'2020-10-16' AS Date), NULL, N'Medium', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'023423', CAST(N'2020-01-18' AS Date), NULL, N'High', N'Open')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'032444', CAST(N'2019-12-22' AS Date), CAST(N'2020-02-22' AS Date), N'High', N'Resolved')
INSERT [dbo].[taskDB] ([ticket], [created], [closed], [rating], [status]) VALUES (N'043234', CAST(N'2020-02-24' AS Date), CAST(N'2020-06-07' AS Date), N'Critical', N'Resolved')
GO

目前我构建的查询如下:

declare @FromDate datetime, 
            @ToDate datetime;
    
    SET @FromDate = ('2020-08-01 00:00:00.000');
    SET @ToDate = (Select max(created) From [TestDB].[dbo].[taskDB]);
  
    declare @openTicketsBM table (firstDayOfMonth datetime, firstDayNextMonth datetime, year int, month int, Rating int, ZTT int, TTS int, STN int, ANN int)
    
    Insert into @openTicketsBM(firstDayOfMonth, firstDayNextMonth, year, month)
    
    Select top  (datediff(month, @FromDate, @ToDate) + 1) 
                        dateadd(month, number, @FromDate),
                  dateadd(month, number + 1, @FromDate),
                 year(dateadd(month, number, @FromDate)),
                  month(dateadd(month, number, @FromDate))
                  from [master].dbo.spt_values 
                  where [type] = N'P' order by number;
    
    update R
    Set  R.ZTT = (Select COUNT(CASE WHEN DATEDIFF(day, created, R.firstDayOfMonth) > 0 AND DATEDIFF(day, created, R.firstDayOfMonth) <= 30 THEN 1 ELSE NULL END) from [TestDB].[dbo].[taskDB] where created < R.firstDayNextMonth and NOT status like 'Risk Accepted' and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
         R.TTS = (Select COUNT(CASE WHEN DATEDIFF(day, created, R.firstDayOfMonth) >= 31 AND DATEDIFF(day, created, R.firstDayOfMonth) <= 60 THEN 1 ELSE NULL END) from [TestDB].[dbo].[taskDB] where created < R.firstDayNextMonth and NOT status like 'Risk Accepted' and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
         R.STN = (Select COUNT(CASE WHEN DATEDIFF(day, created, R.firstDayOfMonth) >= 61 AND DATEDIFF(day, created, R.firstDayOfMonth) <= 90 THEN 1 ELSE NULL END) from [TestDB].[dbo].[taskDB] where created < R.firstDayNextMonth and NOT status like 'Risk Accepted' and (closed >= R.firstDayNextMonth or closed = '' or closed is null)),
         R.ANN = (Select COUNT(CASE WHEN DATEDIFF(day, created, R.firstDayOfMonth) > 90 THEN 1 ELSE NULL END) from [TestDB].[dbo].[taskDB] where created < R.firstDayNextMonth and NOT status like 'Risk Accepted' and (closed >= R.firstDayOfMonth or closed = '' or closed is null))
  
    From @openTicketsBM R
    
    select  CAST(year AS VARCHAR(50)) + '/' + CAST(month AS VARCHAR(50)) AS date,
            ZTT,
            TTS,
            STN,
            ANN
          
            
    from @openTicketsBM

运行此查询时会生成以下错误:

Msg 8124, Level 16, State 1, Line 20
Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.

这看起来很简单,但我显然遗漏了一些东西。

EDITT 2020 年 12 月 18 日上午 10:18 东部时间

为了澄清我的逻辑,让我们回到 2020 年 2 月。2 月 1 日开放了多少张门票,它们的年龄是多少?结果应该是这样的

date       ZTT(0-30)  TTS(31-60)  STN(61-90) ANN(91+)
2020/2        5          3           3           1

【问题讨论】:

对您想要做什么的简单解释会有所帮助。我想你可以用一个更简单的例子重现这个问题。 嗯,我们肯定有样本数据,@GordonLinoff,并且是迄今为止最好的形式; DDL 和 DML 语句。 根据我目前的问题,这很简单。 是的,但我们不知道您在这里尝试执行的逻辑 @mister.cake ,也不知道预期的结果。这也是我们需要的。 好的,我添加了一个编辑。 【参考方案1】:

所以我纯粹根据您帖子的一部分留下这个答案:

[2020 年 2 月 1 日] 开放了多少张门票,它们的年龄有多大?

仅基于此引用...您的问题听起来很简单。

您有一张包含createdclosed 日期的票证表。因此,您的第一步是获取 2 月 1 日或之前存在的所有票证的列表。

这很简单:

SELECT *
FROM dbo.taskDB
WHERE created <= '2020-02-01'

现在,我们要消除当时关闭的所有票证。根据您的示例中的数据,对于关闭日期,我看到了 NULL 值和 1900-01-01,我将假设在这两种情况下,这意味着票仍然是开放的。

这意味着,为了让票在特定日期被认为是开放的,我们称之为@AsOfDate,那么其中一个必须是真的......

closed IS NULL closed = '1900-01-01' closed &gt;= @AsOfDate - 如果门票在开放的同一天关闭,我们将在当天算作开放。但您可以将其更改为 &gt; 以排除这些票证。这取决于你。

现在让我们将这一切组合成一个查询:

DECLARE @AsOfDate date = '2020-02-01'
SELECT *, TicketAge = DATEDIFF(DAY, created, @AsOfDate)
FROM dbo.taskDB
WHERE created <= @AsOfDate -- Only look at tickets that existed at this point in time
    AND (closed IS NULL OR closed >= @AsOfDate OR closed < created) -- Only look at tickets that were open
ORDER BY created DESC

此查询生成的数字与您在帖子中预期的数字不同...但我也不确定您的数字是否准确。 2020 年 2 月 1 日,您的号码加起来是 12 张门票……但我看到当时有 9 张门票存在并开放。


编辑 1:

您问如何针对您的日期表实施我的解决方案...这是一种方法:

SELECT ot.firstDayOfMonth
    , [0-30]    = SUM(IIF(x.TicketAge BETWEEN 0 AND 30, 1, 0))
    , [31-60]   = SUM(IIF(x.TicketAge BETWEEN 31 AND 60, 1, 0))
    , [61-90]   = SUM(IIF(x.TicketAge BETWEEN 61 AND 90, 1, 0))
    , [91+]     = SUM(IIF(x.TicketAge >= 91, 1, 0))
FROM @openTicketsBM ot
    LEFT JOIN #taskDB t ON t.created <= ot.firstDayOfMonth -- Only look at tickets that existed at this point in time
        AND (t.closed IS NULL OR t.closed >= ot.firstDayOfMonth OR t.closed < t.created) -- Only look at tickets that were open
    CROSS APPLY (SELECT TicketAge = DATEDIFF(DAY, t.created, ot.firstDayOfMonth)) x
GROUP BY ot.firstDayOfMonth

* 编辑查询,使其使用LEFT JOIN,这样,您将看到没有开票的月份


一些不相关的观察和个人建议...

不要将日期称为'',然后将其转换为'1900-01-01'。如果您使用它作为指示票已打开的方式,为什么不NULL? 使用清晰/自我描述的列名。 “创建”或“关闭”声音(对我来说)像标志/位列,而不是日期列。我会使用 CreateDateUTCCloseDateUTC 之类的东西。 为什么使用存储为字符串的票号,而不是int IDENTITY(1,1)? 您应该规范化您的表格。不要为ratingstatus 使用字符串,而是使用链接到查找表的外键列。 不要依赖 spt_values 作为计数表,而是创建一个永久日期/日历表 (here's an example),或使用动态计数表,如下所示:
--IF OBJECT_ID('tempdb..#tally','U') IS NOT NULL DROP TABLE #tally; --SELECT * FROM #tally
WITH c1 AS (SELECT x.x FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) x(x)) -- 10
    , c2(x) AS (SELECT 1 FROM c1 x CROSS JOIN c1 y) -- 10 * 10
    , c3(rn) AS (SELECT 0 UNION ALL SELECT ROW_NUMBER() OVER (ORDER BY 1/0) FROM c2) -- Add zero record, and row numbers
INTO #tally
SELECT x.rn
FROM c3 x

【讨论】:

所以在我的问题中,我没有使用@AsOfDate 作为日期差异,而是使用生成的名为@openTicketsBM R 的表中每个月的第一天的值(R.firstDayOfMonth)。 SQL 抛出一个错误,表明这是一个“外部引用”。有没有办法我仍然可以利用这个价值? @mister.cake 请参阅帖子中的Edit 1,我已将我的解决方案应用于您的日期表。

以上是关于DATEDIFF 按创建日期划分范围,利用外部日期按月计算年龄历史的主要内容,如果未能解决你的问题,请参考以下文章

第四十三章 SQL函数 DATEDIFF

建立一个参照约束(check约束),要求表“Employees”的字段“雇佣日期”必须比“生日”晚18年

在mysql和php中按日期范围分组

为日期范围选择每个日期并插入

利用批处理实现按当前日期创建目录并备份

SQL Server 在日期范围内聚合