Выполните итерацию запроса SQL по нескольким параметрам по одному и объедините результаты - PullRequest
0 голосов
/ 14 февраля 2020

У меня есть запрос, который я написал для SQL Server 2012, чтобы взять динамический список c сотрудников и определить, сколько времени они проводят каждый день в определенных областях. Когда я запускаю запрос от человека за раз, я получаю хорошие результаты. Однако, если я передам все параметры в предложении IN, то получу не только меньше результатов, но и некоторые значения, отличающиеся и просто неверные.

В конечном итоге это будет выполнено в отчете в SSRS, но даже если данные обрабатываются напрямую в SSMS, они демонстрируют то же поведение.

@People - это отдельный запрос, который собирает список людей, которые действительно были в этом районе в тот день. @Yesterday и @Today являются просто переменными для дат.

SELECT *
FROM
    (SELECT 
         AdmitType,
         Time,
         Person,
         Door,
         Direction,
         CASE 
            WHEN Direction LIKE 'In' AND LEAD(Direction, 1) OVER(ORDER BY Time ASC) LIKE 'Out'
               THEN DATEDIFF (second, Time, LEAD(Time, 1) OVER(ORDER BY Time ASC))
               ELSE NULL
         END AS 'Duration'
     FROM 
         ActivityLog
     WHERE 
         Door IN ('Room1', 'Room2', 'Room3')
         AND Time BETWEEN @Yesterday AND @Today
         AND Person IN @People) s1
ORDER BY 
    Person, Door, Time ASC

Если я выполню приведенный выше запрос и передам параметр @People со значением ('Tom', 'Dick', 'Harry'), я получу что-то подобное для записей Тома (исключая некоторые столбцы и несколько человек и двери для ясности):

Time    Direction    Duration  
-----------------------------
7:12:04 IN           922  
7:27:26 OUT          NULL  
8:40:37 IN           NULL  
9:07:04 OUT          NULL  

Но если я выполню запрос только с Томом, указанным в предложении IN, я получу следующее, что правильно:

Time    Direction    Duration  
-----------------------------
7:12:04 IN           922  
7:27:26 OUT          NULL  
8:40:37 IN           1587  
9:07:04 OUT          NULL  

Как я могу исправить свой запрос, чтобы он возвращал правильную информацию для всех выбранных (обычно примерно 25 человек), или взять параметр и запустить каждого человека по одному и объединить результаты?

Я видел запрос, который пропускает список по одному и объединяет все результаты, но я не знаю, как написать это так, чтобы принимать входные параметры, которые будут переданы из SSRS.

Дополнительные данные добавлены по предложению Алана Шофилда: когда я добавил предложение PARTITION BY, я теперь получаю данные для этих предыдущих значений NULL, но это не соответствует прямые данные.

SELECT *
FROM
    (SELECT 
         AdmitType,
         Time,
         Person,
         Door,
         Direction,
         CASE 
            WHEN Direction LIKE 'In' AND LEAD(Direction, 1) OVER(PARTITION BY Person, Door ORDER BY Time ASC) LIKE 'Out'
               THEN DATEDIFF (second, Time, LEAD(Time, 1) OVER(ORDER BY Time ASC))
               ELSE NULL
         END AS 'Duration'
     FROM 
         ActivityLog
     WHERE 
         Door IN ('Room1', 'Room2', 'Room3')
         AND Time BETWEEN @Yesterday AND @Today
         AND Person IN @People) s1
ORDER BY 
    Person, Door, Time ASC

Вот более крупный пример набора данных с результатами нового предложения PARTITION BY:

Time      Person     Door    Direction    Duration  
18:07:22  John       Room1   IN           1308  (Correct)
18:29:10  John       Room1   Out          NULL  
17:42:18  John       Room3   IN           1406  (Correct)
18:05:44  John       Room3   Out          NULL
7:12:04   Tom        Room1   IN           922   (Correct)
7:27:26   Tom        Room1   Out          NULL
8:40:37   Tom        Room1   IN           2    (Previously NULL, Now 2, should be 1587 and is when queried alone)
9:07:04   Tom        Room1   Out          NULL
9:09:07   Tom        Room1   IN           31   (Previously NULL, Now 31, should be 1256 and is when queried alone)
9:30:03   Tom        Room1   Out          NULL
9:38:48   Tom        Room1   IN           48   (Correct)
9:39:36   Tom        Room1   Out          NULL
9:39:45   Tom        Room1   Out          NULL
9:40:36   Tom        Room1   IN           90   (Correct)
9:42:06   Tom        Room1   Out          NULL
10:25:0   Tom        Room1   IN           47   (Previously 47, Now 47, Should be 8939 and is when queried alone)
12:54:08  Tom        Room1   Out          NULL
14:09:42  Tom        Room1   IN           39   (Correct)
14:10:21  Tom        Room1   Out          NULL
14:39:03  Tom        Room1   IN           1316 (Previously NULL, Now 1316, should be 4178 and is when queried alone)
15:48:41  Tom        Room1   Out          NULL
15:49:20  Tom        Room1   IN           52   (Previously NULL, Now 52, should be 1542 and is when queried alone)
16:15:02  Tom        Room1   Out          NULL
16:22:26  Tom        Room1   IN           292  (Previously 292, Now 292, should be 1504 and is when queried alone)
16:47:30  Tom        Room1   Out          NULL

Данные продолжаются намного дольше, но, надеюсь, это даст лучшую идею.

Ответы [ 2 ]

0 голосов
/ 14 февраля 2020

Ваше предложение WHERE неверно в том смысле, что оно не будет возвращать результаты, которые вы считаете.

Если я запусту приведенный выше запрос и передам параметр @People со значением ('Tom ',' Дик ',' Гарри ')

Это неверно. @People не имеет значения («Том», «Дик», «Гарри»). Он имеет значение («Том, Дик, Гарри»).

WHERE 
     Door IN ('Room1', 'Room2', 'Room3')
     AND Time BETWEEN @Yesterday AND @Today
     AND Person IN @People) s1

Поле IN Поле Door содержит три различных значения, разделенных запятыми T- SQL, и правильно фильтрует query.

Вам нужно иметь табличную функцию, чтобы разделить параметр @People в табличную переменную, и использовать ее для предложения Person IN. Примерно так:

CREATE function [dbo].[SplitListToTable]
(
    @List       AS NVARCHAR(MAX),
    @Delimiter  AS NVARCHAR(8) = ','
 )
RETURNS @SplitList TABLE (ListItem [nvarchar](4000))
BEGIN

DECLARE @NextString     AS NVARCHAR(4000)
DECLARE @Pos            AS INT
DECLARE @NextPos        AS INT
DECLARE @DelimiterCheck AS NVARCHAR(1)

--Initialize
SET @NextString = ''
SET @DelimiterCheck = RIGHT(@List, 1) 

IF (@DelimiterCheck <> @Delimiter )
    SET @List = @List + @Delimiter

--Get position of first Comma
SET @Pos = CHARINDEX(@Delimiter, @List)
SET @NextPos = 1

--Loop while there is still a comma in the String of levels
WHILE (@pos <>  0)  
    BEGIN
    SET @NextString = SUBSTRING(@List, 1, @Pos - 1)

    INSERT INTO @SplitList(ListItem) Values(@NextString)

    SET @List = SUBSTRING(@List, @Pos +1, LEN(@List))
    SET @NextPos = @Pos
    SET @Pos  = CHARINDEX(@Delimiter, @List)
    END

RETURN

END
GO

Теперь обновите предложение WHERE следующим образом:

AND Person IN dbo.SplitListToTable(@People, ',')
0 голосов
/ 14 февраля 2020

Похоже, вам нужно добавить PARTITION BY к вашим оконным функциям. В настоящий момент

LEAD(Direction, 1) OVER(ORDER BY Time ASC)

Ищет следующую запись, независимо от того, на какого человека есть ссылка в записи. Попробуйте добавить предложение PARTITION BY к соответствующим оконным функциям.

LEAD(Direction, 1) OVER(PARTITION BY Person ORDER BY Time ASC)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...