我需要知道用户被记录了多少时间(天)以及未记录时间之间的间隔。在此表中,我仅存储ID及其开始和结束日期(分别为ID,INI,FIN)。通过将用户与行号分组,然后将最新日志与以下日志进行比较,我已经设法根据条件检测出三条记录之间的差距。
问题是,我的人历史上有n个日志,我不能写n个左联接和n个条件语句。我希望使我当前的代码更具可伸缩性,从而以递归方式检测这些差距,并使用户“更容易理解”。
CREATE TABLE [dbo].[baseRecurrentes](
[ID] [nvarchar](8) NULL,
[INI] [datetime] NULL,
[FIN] [datetime] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA1', CAST(0x0000A9C800000000 AS DateTime), CAST(0x0000A9E600000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA1', CAST(0x0000A9E700000000 AS DateTime), CAST(0x0000AA0200000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA1', CAST(0x0000AA0300000000 AS DateTime), CAST(0x0000AA2100000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA1', CAST(0x0000AA2200000000 AS DateTime), CAST(0x0000AA3F00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA2', CAST(0x0000A9D600000000 AS DateTime), CAST(0x0000A9D900000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA2', CAST(0x0000A9EB00000000 AS DateTime), CAST(0x0000A9ED00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA2', CAST(0x0000A9F000000000 AS DateTime), CAST(0x0000A9F100000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA3', CAST(0x0000AA1A00000000 AS DateTime), CAST(0x0000AA5A00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA4', CAST(0x0000A9CA00000000 AS DateTime), CAST(0x0000A9CB00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A8DC00000000 AS DateTime), CAST(0x0000A8F100000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A8F200000000 AS DateTime), CAST(0x0000A90F00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A91000000000 AS DateTime), CAST(0x0000A92E00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A92F00000000 AS DateTime), CAST(0x0000A94D00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A94E00000000 AS DateTime), CAST(0x0000A96B00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A96C00000000 AS DateTime), CAST(0x0000A98A00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A98B00000000 AS DateTime), CAST(0x0000A9A800000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A9A900000000 AS DateTime), CAST(0x0000A9C700000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A9C800000000 AS DateTime), CAST(0x0000A87900000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000A9E700000000 AS DateTime), CAST(0x0000AA0200000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000AA0300000000 AS DateTime), CAST(0x0000AA2100000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000AA2200000000 AS DateTime), CAST(0x0000AA3F00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA5', CAST(0x0000AA4000000000 AS DateTime), CAST(0x0000AA5000000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA6', CAST(0x0000AA0900000000 AS DateTime), CAST(0x0000AA2900000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA7', CAST(0x0000A96C00000000 AS DateTime), CAST(0x0000A98A00000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA7', CAST(0x0000A98B00000000 AS DateTime), CAST(0x0000A9A800000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA7', CAST(0x0000A9A900000000 AS DateTime), CAST(0x0000A9C700000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA7', CAST(0x0000A85B00000000 AS DateTime), CAST(0x0000A87900000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA7', CAST(0x0000A9E700000000 AS DateTime), CAST(0x0000AA0200000000 AS DateTime))
INSERT [dbo].[baseRecurrentes] ([ID], [INI], [FIN]) VALUES (N'PERSONA7', CAST(0x0000AA0300000000 AS DateTime), CAST(0x0000AA2100000000 AS DateTime))
;WITH CTE AS (
SELECT *, RN = ROW_NUMBER()OVER(PARTITION BY ID ORDER BY INI DESC)
FROM BASERECURRENTES
), CTE2 AS (
--
SELECT DISTINCT T.ID,
--
CAST(T2.INI AS DATETIME) AS INI1, CAST(T2.FIN AS DATETIME) AS FIN1,
--
CAST(T3.INI AS DATETIME) AS INI2, CAST(T3.FIN AS DATETIME) AS FIN2,
--
CAST(T4.INI AS DATETIME) AS INI3, CAST(T4.FIN AS DATETIME) AS FIN3
--
FROM CTE T
LEFT JOIN CTE T2 ON T.ID = T2.ID AND T2.RN = 1
LEFT JOIN CTE T3 ON T.ID = T3.ID AND T3.RN = 2
LEFT JOIN CTE T4 ON T.ID = T4.ID AND T4.RN = 3
), CTE3 AS (
SELECT *, MSG = (CASE
--NO GAPS ON 3 LOGS
WHEN (INI1 - 1 BETWEEN INI2 AND FIN2) AND (INI2 - 1 BETWEEN INI3 AND FIN3) THEN 'SEC2'
--NO GAPS ON 2 LOGS
WHEN (INI1 - 1 BETWEEN INI2 AND FIN2) THEN 'SEC1'
--NO GAP AT ALL
ELSE 'NO SEC'
END)
FROM CTE2
)
SELECT * FROM CTE3
ORDER BY ID ASC
我希望有一个显示用户ID的表格,“间隔天数”(未记录时间的总和)和一条消息,显示间隔在哪里。
ID GD MSG
-------------------
PERSONA2 5 GAP ON X-Y
答案 0 :(得分:0)
我认为,通过将每个用户的跨度与用户的先前跨度进行比较,这将为您提供所需的“递归”结果。这将所有结果汇总给用户。从CTE2中选择可以返回带间隙或Ini / Fin反转的跨度。
;WITH CTE AS (
SELECT *,
Person_RN = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY INI, FIN),
Person_Count = COUNT(*) OVER (PARTITION BY ID)
FROM BASERECURRENTES
), CTE2 AS (
SELECT T.*,
N_Days_Logged_On =
CASE
WHEN DATEDIFF(DAY, T.Ini, T.Fin) < 0 THEN NULL -- Reversed span
ELSE DATEDIFF(DAY, T.Ini, T.Fin)
END,
N_Days_Gap_From_Prev =
CASE
WHEN T.Person_RN = 1 THEN NULL -- First span
WHEN DATEADD(DAY, 1, tPrev.Fin) = t.Ini THEN 0 -- OP allowed a one-day gap
ELSE DATEDIFF(DAY, tPrev.Fin, T.Ini)
END,
tPrev.Ini AS Ini_Prev, tPrev.Fin AS Fin_Prev
FROM CTE T
LEFT JOIN CTE TPrev ON T.ID = TPrev.ID AND TPrev.Person_RN = (T.Person_RN - 1)
-- LEFT JOIN CTE TNext ON T.ID = TNext.ID AND TNext.Person_RN = (T.Person_RN + 1)
), CTE3 AS (
SELECT ID,
GD = SUM(N_Days_Gap_From_Prev),
N_Days_Logged_On = SUM(N_Days_Logged_On),
N_Logs = MAX(Person_Count),
N_Spans_WO_Gaps = COUNT(CASE WHEN N_Days_Gap_From_Prev = 0 THEN ID ELSE NULL END),
N_Spans_W_Gaps = COUNT(NULLIF(N_Days_Gap_From_Prev, 0)),
-- Captures reversed/invalid(?) spans, plus incomplete spans (null Ini or Fin)
N_Spans_Suspect = COUNT(CASE WHEN N_Days_Logged_On IS NULL THEN ID ELSE NULL END)
FROM CTE2
GROUP BY ID
)
SELECT *
FROM CTE3
ORDER BY ID
我对此做了两个假设。在您的示例中,登录/注销看起来是存储为datetime的日期,但是时间部分不是问题(例如,00:00的注销与23:59相同)。正如您在CTE2中看到的那样,DATEDIFF的结果不一定直观(例如,2019年1月1日至2019年1月31日的DATEDIFF是30天)。
从示例数据中返回:
答案 1 :(得分:0)
您可以使用ROW_NUMBER()
来加入相同的CTE
,并寻找差距。这是我的建议:
;WITH CTE AS (
SELECT *, RN = ROW_NUMBER()OVER(PARTITION BY ID ORDER BY INI DESC)
FROM BASERECURRENTES
), CTE2 AS (
SELECT *
, (SELECT TOP 1 FIN FROM CTE b WHERE a.ID = b.ID and b.RN > a.RN) ENDOFLAST
FROM CTE a
), CTE3 AS (
SELECT *
, DATEDIFF(DAY, INI, ENDOFLAST) GAPDAYS
FROM CTE2
)
SELECT *
FROM CTE3
WHERE GAPDAYS < -1
ORDER BY ID ASC
以及指向SQL Fiddle的链接。我不完全了解实际的消息,但我认为结果集包含创建消息所需的一切。