Найти разрывы между диапазонами дат для пользователей в SQL Server 2008 - PullRequest
0 голосов
/ 02 мая 2019

Мне нужно знать, сколько времени (дней) было зарегистрировано пользователями и промежутки между незарегистрированным временем.В этой таблице я просто храню идентификатор и их начальную и конечную дату (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       GD  MSG
-------------------
PERSONA2 5   GAP ON X-Y

Ответы [ 2 ]

0 голосов
/ 05 мая 2019

Вы можете использовать свои ROW_NUMBER() s при присоединении к тому же 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 .Я не совсем понял реальное сообщение, но я думаю, что в наборе результатов есть все необходимое для создания сообщения.

0 голосов
/ 05 мая 2019

Я думаю, что это даст вам "рекурсивные" результаты, которые вы ищете, сравнивая интервал каждого пользователя с предыдущим интервалом пользователя.Это подводит итоги всех результатов для пользователя;Выбор из 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 не обязательно являются интуитивно понятными (например, DATEDIFF для периодов с 2019-01-01 по 2019-01-31 составляет 30 дней).

Из примеров данных это возвращает:

  • PERSONA1: 4 журнала, 0 дней разрыва, 116 дней входа в систему и 0 периодов с пробелами
  • PERSONA2: 3 журнала, 21 день разрыва, 6 дней входа в систему и всепромежутки имеют пробелы.
...