2013-06-26 2 views
2

У меня есть таблица (назовем ее audit), который выглядит следующим образом:Выберите самую низкую дату из диапазона и исключить другой диапазон

+--------------------------------------------------------------------------+ 
| id | recordId | status | mdate     | type | relatedId  | 
+--------------------------------------------------------------------------+ 
| 1 | 3006  | A  | 2013-04-03 23:59:01.275 | type1 | 1    | 
| 2 | 3025  | B  | 2013-04-04 00:00:02.134 | type1 | 1    | 
| 3 | 4578  | A  | 2013-04-04 00:04:30.033 | type2 | 1    | 
| 4 | 7940  | C  | 2013-04-04 00:04:32.683 | type1 | <NULL>  | 
| 5 | 3006  | D  | 2013-04-04 00:04:32.683 | type1 | <NULL>  | 
| 6 | 4822  | E  | 2013-04-04 00:04:32.683 | type2 | <NULL>  | 
| 7 | 3006  | A  | 2013-04-04 00:06:54.033 | type1 | 2    | 
| 8 | 3025  | C  | 2013-04-04 00:06:54.033 | type1 | 2    | 

... и для миллионов строк. И еще один стол мы будем называть related:

+-------------+ 
| id | source | 
+-------------+ 
| 1 | src_X | 
| 2 | src_Y | 
| 3 | src_Z | 
| 4 | src_X | 
| 5 | src_X | 

... и на протяжении сотен тысяч строк.

На обеих таблицах больше столбцов, но это все, что нам нужно для описания проблемы. Столбец relatedId присоединяется к таблице related. recordId также присоединяется к другой таблице и будет содержать несколько записей в audit с тем же recordId.

Я пытаюсь создать запрос, который будет производить следующий вывод:

+-----------------+ 
| source | count | 
+-----------------+ 
| src_X | 1643 | 
| src_Y | 255 | 
| NULL | 729 | 
+-----------------+ 

Количество является количество записей в audit, которые данный type (например "type1".) И в наборе статусов (например, "A", "B", "C"), которые затем оставлены внешними, соединенными с related и сгруппированы по source.

Загвоздка в том, что я только хочу, чтобы включать записи изнутри audit, которые находятся в пределах определенного диапазона дат, и я только хочу, чтобы присоединиться от audit к related на самой старой записи в пределах этого диапазона для каждого recordId. Кроме того, я хочу игнорировать любые записи, которые соответствуют критериям type и status, но имеют запись для того же recordId, что старше, чем диапазон дат.

Таким образом, чтобы выяснить, из приведенного выше примера данных: Допустим, я хочу, тип type1 и значения из "A", "B", "C" с диапазона дат 2013-04-04 к 2013-04-05. Строки 2 и 4 будут включены в счет. Строка 3 исключается, так как она имеет неправильный type. Строка 5 исключается, так как статус неверен. Строка 6 исключается из-за неправильного состояния и типа. Строка 1 исключается, так как она находится за пределами диапазона дат. Строка 7 также исключается, так как существует еще одна строка (строка 1), которая соответствует критериям статуса и типа с тем же recordId, который старше начала диапазона дат. Строка 8 исключается, так как и строка 8, и строка 2 имеют одинаковые recordId и соответствуют критериям, но мы учитываем только самую старую запись двух в пределах диапазона.

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

Мы придумали следующее:

WITH data (recordId, id) AS (
    SELECT a.recordId, MIN(a.id) 
    FROM audit a 
    WHERE a.status in ('A','B','C') 
     AND type = 'type1' 
    GROUP BY a.recordId 
) 
SELECT r.source, COUNT(*) 
FROM data d 
    JOIN audit a ON d.id = a.id 
    LEFT JOIN related r ON a.relatedId = r.id 
WHERE a.mdate >= '2013-04-04 00:00:00.000' 
    and a.mdate < '2013-04-05 00:00:00.000' 
GROUP BY r.source 

Это будет работать на MSSQL Server 2008, и в настоящее время основывается на том факте, что таблица аудита идентификаторы являются автогенерируемые. Поскольку идентификаторы генерируются в точке, в которую вставлена ​​запись, а mdate также является временной меткой вставки, и записи никогда не обновляются после вставки, я думаю, что это нормально. Похоже, что запрос дает правильный результат по ограниченному набору тестовых данных, но я надеялся на второе мнение.

  • Этот вопрос выглядит нормально?
  • Можно ли улучшить его производительность?
+1

Диапазон дат в выраженном выражении таблицы, вероятно, улучшит производительность. –

+0

Хорошая точка. Добавление 'AND a.mdate <'2013-04-05 00: 00: 00.000'' в вычисленную таблицу поможет ограничить количество возвращаемых записей. –

+0

Чтобы улучшить производительность запросов, позаботьтесь об индексировании. Используйте индексирование в полях WHERE Clause, Join Fields и снова проверьте производительность. –

ответ

4

Вы можете использовать функцию ROW_NUMBER() для ранжирования записей на основе RecordId и mDate, а затем ограничить результаты тем, где первое событие находится между указанными вами датами.

WITH data AS 
( SELECT a.relatedId, a.mdate, rn = ROW_NUMBER() OVER(PARTITION BY a.RecordId ORDER BY a.mdate) 
    FROM audit a 
    WHERE a.status in ('A','B','C') 
    AND  type = 'type1' 
) 
SELECT r.source, [Count] = COUNT(*) 
FROM data d 
     LEFT JOIN related r 
      ON d.relatedId = r.id 
WHERE d.rn = 1 
AND  d.mdate >= '2013-04-04 00:00:00.000' 
AND  d.mdate < '2013-04-05 00:00:00.000' 
GROUP BY r.source; 

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

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

This SQL Fiddle показаны два запроса (моя и твоя) в конечном итоге с тем же результатом, однако, когда вы посмотрите на статистику ввода-вывода вы можете увидеть для вашего запроса вы получите:

(2 row(s) affected) 
Table 'Related'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'Audit'. Scan count 2, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Использование ROW_NUMBER() вы получить:

(2 row(s) affected) 
Table 'Related'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'Audit'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Ключевым фактором является одно менее логическое чтение. Быстрый взгляд на план выполнения показывает, что решение ROW_NUMBER() имеет одну меньшую ветвь и оценивается в 37% от стоимости партии, тогда как ваше решение составляет 63%, поэтому на этом небольшом наборе данных это будет производительность улучшение.

enter image description here

Однако есть только так много я могу сказать от такой небольшой выборки данных, некоторые решения не масштабируется, и как я уже сказал, что это будет зависеть от размера данных и Дистрибутив. Мое предложение было бы попробовать разные решения, найти шею бутылки, изучив статистику IO и план выполнения.

Например, глядя на план выполнения для КТР это составляет 50% от стоимости запроса на мой запрос:

enter image description here

Добавив этот индекс:

CREATE INDEX IX_Audit_ALL ON Audit (recordId, MDate, RelatedID, status, type) 

I удалось сократить это до 18% от стоимости запроса.

enter image description here

Однако в практичности, не зная больше я не могу сказать определенно, этот показатель будет (а) поможет этот запрос с вашими данными и (б) что оно не будет вызывать другие проблемы с базой данных замедляя вставку/обновление вниз.

+0

Спасибо за всесторонний ответ! –

 Смежные вопросы

  • Нет связанных вопросов^_^