Присоединяйтесь к условиям и агрегатным функциям - PullRequest
2 голосов
/ 22 сентября 2009

У меня есть таблица, в которой есть записи о работах, ведущих к входным дверям.

DECLARE @doorStatistics TABLE
( id INT IDENTITY,
[user] VARCHAR(250),
accessDate DATETIME,
accessType VARCHAR(5)
)

Пример записи:

INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('John Wayne','2009-09-01 07:02:43.000','IN')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('Bruce Willis','2009-09-01 07:12:43.000','IN')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('Bruce Willis','2009-09-01 07:22:43.000','OUT')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('John Wayne','2009-09-01 07:32:43.000','OUT')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('John Wayne','2009-09-01 07:37:43.000','IN')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('Bruce Willis','2009-09-01 07:42:43.000','IN')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('John Wayne','2009-09-01 07:48:43.000','OUT')
INSERT INTO @doorStatistics([user],accessDate,accessType) VALUES ('Bruce Willis','2009-09-01 07:52:43.000','OUT')

То, что я хочу сделать, это запрос, который дает мне следующий результат (на основе приведенного выше примера):

| user         | date       | inHour   | outHour  |
|--------------|------------|----------|----------|
| John Wayne   | 2009-09-01 | 07:02:43 | 07:48:43 |
| Bruce Willis | 2009-09-01 | 07:12:43 | 07:22:43 |
| John Wayne   | 2009-09-02 | 07:37:43 | 07:48:43 |
| Bruce Willis | 2009-09-02 | 07:42:43 | 07:52:43 |

Я сделал следующий запрос:

SELECT [user], accessDate AS [in date], 
    (SELECT MIN(accessDate) 
        FROM @doorStatistics ds2 
        WHERE accessType = 'OUT' 
            AND ds2.accessDate > ds.accessDate 
            AND ds.[user] = ds2.[user]) AS [out date] 
FROM @doorStatistics ds 
WHERE accessType = 'IN'

Но это нехорошо, потому что когда пользователь забудет зарегистрировать свой вход, он выдаст, например, что-то вроде этого:

| user         | date       | inHour   | outHour  |
|--------------|------------|----------|----------|
| John Wayne   | 2009-09-02 | 07:02:43 | 07:48:43 |
| John Wayne   | 2009-09-02 | 07:02:43 | 09:26:43 |

Пока должно быть

| user         | date       | inHour   | outHour  |
|--------------|------------|----------|----------|
| John Wayne   | 2009-09-02 | 07:02:43 | 07:48:43 |
| John Wayne   | 2009-09-02 | NULL     | 09:26:43 |

Вторая причина, по которой запрос не является хорошим, - это производительность. У меня более 200 000 записей, и SELECT для каждой строки замедляет запрос.

Возможным решением может быть объединение двух таблиц

SELECT * FROM @doorStatistics WHERE accessType = 'IN'

с

SELECT * FROM @doorStatistics WHERE accessType = 'OUT'

но я понятия не имею, какие условия поставить, чтобы получить правильную дату. Может быть, там можно поставить некоторые функции MAX или MIN, но я понятия не имею.

Я не хочу создавать временную таблицу и использовать курсоры.

Ответы [ 3 ]

1 голос
/ 22 сентября 2009

Для повышения производительности на уровне структуры:

  • Я предлагаю переименовать столбец accessDate в accessDateTime
  • затем вы создаете PERSISTENT вычисляемый столбец на основе вашего accessDateTime (показано ниже). Тогда нужный вам индекс будет включать только столбец accessDate, который вы будете использовать для точного сравнения вместе с user
  • убедитесь, что у вас есть правильные индексы в таблице (из приведенного ниже кода вам, вероятно, понадобится индекс для "user", "accessDate" и включающий "accessType"

accessDate определение столбца:

accessDate AS CONVERT(SMALLDATETIME, CONVERT(CHAR(8), accessDateTime, 112), 112) PERSISTED

Теперь, учитывая, что вы сделали это и у вас есть SQL-2005 +, , этот ужасно длинный запрос должен выполнить работу :

WITH MatchIN (in_id, out_id)
AS (SELECT      s.id, CASE WHEN COALESCE(y.id, s.id) = s.id THEN x.id ELSE NULL END
    FROM        @doorStatistics s
    LEFT JOIN   @doorStatistics x
            ON  x.id = (SELECT  TOP 1 z.id
                        FROM    @doorStatistics z
                        WHERE   z."user" = s."user"
                            AND z.accessType = 'OUT'
                            AND z.accessDate =  s.accessDate
                            AND z.accessDateTime >= s.accessDateTime
                        ORDER BY z.accessDateTime ASC
                        )
    LEFT JOIN   @doorStatistics y
            ON  y.id = (SELECT  TOP 1 z.id
                        FROM    @doorStatistics z
                        WHERE   z."user" = s."user"
                            AND z.accessType = 'IN'
                            AND z.accessDate =  s.accessDate
                            AND z.accessDateTime >= s.accessDateTime
                            AND z.accessDateTime <= x.accessDateTime
                        ORDER BY z.accessDateTime DESC
                        )
    WHERE       s.accessType = 'IN'
)
,    MatchOUT (out_id, in_id)
AS (SELECT      s.id, CASE WHEN COALESCE(y.id, s.id) = s.id THEN x.id ELSE NULL END
    FROM        @doorStatistics s
    LEFT JOIN   @doorStatistics x
            ON  x.id = (SELECT  TOP 1 z.id
                        FROM    @doorStatistics z
                        WHERE   z."user" = s."user"
                            AND z.accessType = 'IN'
                            AND z.accessDate =  s.accessDate
                            AND z.accessDateTime <= s.accessDateTime
                        ORDER BY z.accessDateTime DESC
                        )
    LEFT JOIN   @doorStatistics y
            ON  y.id = (SELECT  TOP 1 z.id
                        FROM    @doorStatistics z
                        WHERE   z."user" = s."user"
                            AND z.accessType = 'OUT'
                            AND z.accessDate =  s.accessDate
                            AND z.accessDateTime <= s.accessDateTime
                            AND z.accessDateTime >= x.accessDateTime
                        ORDER BY z.accessDateTime ASC
                        )
    WHERE       s.accessType = 'OUT'
)

SELECT  COALESCE(i."user", o."user") AS "user",
        COALESCE(i.accessDate, o.accessDate) AS "date",
        CONVERT(CHAR(10), i.accessDateTime, 108) AS "inHour",
        CONVERT(CHAR(10), o.accessDateTime, 108) AS "outHour"
FROM   (SELECT in_id, out_id FROM MatchIN
        UNION -- this will eliminate duplicates as the same time
        SELECT in_id, out_id FROM MatchOUT
        ) x
LEFT JOIN   @doorStatistics i
        ON  i.id = x.in_id
LEFT JOIN   @doorStatistics o
        ON  o.id = x.out_id
ORDER BY    "user", "date", "inHour"

Чтобы проверить обработку пропущенных строк, просто закомментируйте некоторые из ваших операторов INSERT тестовых данных.

1 голос
/ 22 сентября 2009

Вам необходимо выбрать минимальную OUT-запись для каждой IN-записи для данного пользователя, убедившись, что нет промежуточной IN-записи (что соответствует тому, как кто-то дважды входил в IN, не выходя из здания). Это требует некоторого скромного хитрого SQL (предложение NOT EXISTS, например). Итак, у вас будет самообъединение в таблице плюс подзапрос NOT EXISTS для той же таблицы. Просто убедитесь, что вы разумно используете псевдонимы для всех ссылок на таблицу.

1 голос
/ 22 сентября 2009

При проектировании баз данных для временных событий, имеющих длительность, лучше размещать время «ВХОД» и «ВЫХОД» в одной строке.

Все запросы, которые вам нужно сделать, намного проще.

См. « Стиль программирования SQL Джо Селко », где он говорит о временной сплоченности на страницах 48 и 154.

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