Поиск событий головы и хвоста в SQL Server (оптимизация) - PullRequest
0 голосов
/ 13 ноября 2008

У меня есть таблица событий, мне нужно найти все хвостовые события типа 1 и все события головы типа 1.

Итак, для множества событий в этом порядке [1, 1], 3, 1, 4, 5, [1,1,1] скобки обозначают события головы и хвоста типа 1.

Это гораздо лучше проиллюстрировано в SQL:

drop table #event
go 
create table #event (group_id int, [date] datetime, [type] int)
create index idx1 on #event (group_id, date)


insert into #event values (1, '2000-01-01', 1) 
insert into #event values (1, '2000-01-02', 1) 
insert into #event values (1, '2000-01-03', 3) 
insert into #event values (1, '2000-01-04', 2) 
insert into #event values (1, '2000-01-05', 1) 
insert into #event values (2, '2000-01-01', 2) 
insert into #event values (2, '2000-01-02', 2) 
insert into #event values (2, '2000-01-03', 3) 
insert into #event values (2, '2000-01-04', 2) 
insert into #event values (2, '2000-01-05', 1) 
insert into #event values (3, '2000-01-01', 1) 
insert into #event values (3, '2000-01-02', 2) 
insert into #event values (3, '2000-01-03', 1) 
insert into #event values (3, '2000-01-04', 2) 
insert into #event values (3, '2000-01-05', 2) 
insert into #event values (4, '2000-01-01', 2) 
insert into #event values (4, '2000-01-02', 2) 
insert into #event values (4, '2000-01-03', 3) 
insert into #event values (4, '2000-01-04', 1) 
insert into #event values (4, '2000-01-05', 1) 

go 

select e1.* from #event e1 
where (
    not exists (
        select top 1 1 
        from #event e2 
        where e1.group_id = e2.group_id 
        and e2.date < e1.date 
        and e2.type <> 1
    ) or not exists (
        select top 1 1 
        from #event e2 
        where e1.group_id = e2.group_id 
        and e2.date > e1.date 
        and e2.type <> 1
    ) 
)
and e1.type = 1 

Ожидаемые результаты:

1   2000-01-01 00:00:00.000 1
1   2000-01-02 00:00:00.000 1
1   2000-01-05 00:00:00.000 1
2   2000-01-05 00:00:00.000 1
3   2000-01-01 00:00:00.000 1
4   2000-01-04 00:00:00.000 1
4   2000-01-05 00:00:00.000 1

Это все работает отлично и возвращает ожидаемые результаты, но просматривает таблицу 3 раза. Есть ли способ сделать это быстрее и уменьшить количество проверок таблицы?

Ответы [ 9 ]

1 голос
/ 13 ноября 2008

Для генерации большого подмножества данных вы можете использовать это:

declare @i int 
set @i = 10000
while @i > 5 
begin
    insert into #event values (@i, '2000-01-01', 1) 
    insert into #event values (@i, '2000-01-02', 1) 
    insert into #event values (@i, '2000-01-03', 3) 
    insert into #event values (@i, '2000-01-04', 2) 
    insert into #event values (@i, '2000-01-05', 1)  
    set @i = @i -1 
end 

Кроме того, для включения большого количества событий в группу попробуйте следующее:

declare @j int 
set @j = 0 
while @j < 10
begin 
    set nocount on 
    declare @i int 
    set @i = 0
    while @i < 10000 
    begin
        insert into #event values (@j, DateAdd(d, @i, '2000-01-01'), rand(10) * 10) 

        set @i = @i  +1 
    end
    set @j = @j + 1 
end
set nocount off

Во всех моих тестах кажется, что мой исходный запрос производит только 3 сканирования таблицы, и я не совсем уверен, можно ли здесь улучшить производительность.

1 голос
/ 12 декабря 2008

Я думаю, что это лучше:

выберите e1.group_id, e1.date, e1.type
из #event e1, #event e2
где e1.type = 1
и e2.type <> 1
и e1.group_id = e2.group_id
сгруппировать по e1.group_id, e1.date, e1.type, e2.group_id
с e1.date max (e2.date)

0 голосов
/ 13 ноября 2008

Это лучше, чем последний:

ВЫБРАТЬ e1. * ОТ события e1
ГДЕ e1.type = 1
И НЕ СУЩЕСТВУЕТ (
ВЫБЕРИТЕ 1 ИЗ события
ГДЕ тип! = 1
AND id = e1.id
И дата ) * +1010 * СОЮЗ ВСЕХ ВЫБРАТЬ e1. * ОТ события e1
ГДЕ e1.type = 1
И НЕ СУЩЕСТВУЕТ (
ВЫБРАТЬ 1 ИЗ СОБЫТИЯ
ГДЕ тип! = 1
AND id = e1.id
И дата> e1.date
)

Получает тот же результат. Но ваш запрос и мой предыдущий ужасны. Вот статистика IO:

(29985 затронутых строк)

Таблица 'событие'. Сканирование 9997, логическое чтение 20477, физическое чтение 0, чтение с опережением 0.
Стол «Рабочий стол». Число сканирований 39979, логическое чтение 99950, физическое чтение 0, чтение с опережением 0.

Вот то же самое для самого последнего запроса:

(затронуто 29985 строк)

Таблица 'событие'. Сканирование 4, логическое чтение 652, физическое чтение 0, чтение с опережением 0.

Обратите внимание на две вещи -
1. Даже в худшем случае вся таблица загружается и нет физических чтений.
2. У плохих есть 9997 + 39979 сканов таблицы. Не просто 4.

Пожалуйста, опишите цель запроса.

0 голосов
/ 13 ноября 2008

Вот запрос, в котором showplan, по крайней мере, не говорит "сканирование таблицы" и дает тот же ответ.

Я пока не понимаю запрос в логических терминах.

SELECT DISTINCT e1. * FROM #event e1
ГДЕ e1.type = 1
И
(
НЕ СУЩЕСТВУЕТ (
ВЫБЕРИТЕ 1 ИЗ # события
ГДЕ тип! = 1
AND id = e1.id
И дата )
ИЛИ НЕ СУЩЕСТВУЕТ (
ВЫБЕРИТЕ 1 ИЗ # события
ГДЕ тип! = 1
AND id = e1.id
И дата> e1.date
)
)

0 голосов
/ 13 ноября 2008

Вот версия с объединениями:

select distinct e1.* from #event e1 
left outer join #event e2 ON 
        e1.id = e2.id 
        and e2.date < e1.date 
        and e2.type <> 1
left outer join #event e3 ON
        e1.id = e3.id 
        and e3.date > e1.date 
        and e3.type <> 1
where e1.type = 1 AND (e2.id is null or e3.id is null)

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

0 голосов
/ 13 ноября 2008

Имея только 20 записей, оптимизатор запросов может легко прийти к заключению, что 3 сканирования таблицы - это самый быстрый способ сделать это независимо от того, как вы выражаете запрос или какие индексы вы создаете. При небольших записях данных вся таблица, вероятно, будет загружаться с одной или несколькими операциями чтения с диска; и в блоке чтения нет особой оптимизации; Оптимизатор в первую очередь заинтересован в минимизации дисковой активности, а все остальное сравнительно несущественно. То, что оптимизатор делает с таким небольшим количеством записей, не является хорошим показателем того, как он будет обрабатывать большие объемы.

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

0 голосов
/ 13 ноября 2008

Рассмотрим

создать индекс idx2 для #event (type)

У меня нет SQL Server для проверки, но в Oracle это исключит сканирование верхнего уровня (для условия 'type = 1').

Что касается самого запроса - в MS SQL 2000 предикаты [не существует] и [не] в 'почти всегда выполняли полное сканирование - мы заменяли их соответствующими JOIN.

0 голосов
/ 13 ноября 2008

В основном выглядит, что вы хотите получить событие типа 1, которое меньше, чем в метке времени, чем другие события, и больше, чем событие метки времени другой даты. Попробуйте, написано в Oracle Syntax, но не уверены в MSSQL.

выберите e1. * Из e1, где e1.id = 1 и (e1.date <= ( выберите min (e2.date) из e2, где e2.id <> 1 группа по e2.date ) или же (e1.date> = выберите максимум (e3.date) из e3, где e3.id <> 1 сгруппировать по e3.date ) )

0 голосов
/ 13 ноября 2008

Из того, что я понимаю, вы после того, как голова и хвост, упорядоченные по дням **, для каждого удостоверения личности **. Голова и хвост являются всеми записями до тех пор, пока не будет найдена запись, в которой не указан тип.

Это другой способ сделать, это может быть быстрее

;WITH Ranked AS (
    SELECT 
        *,
        Row_Number() OVER (ORDER BY date) as 'rnk'
    FROM #event
)


SELECT * 
FROM Ranked
WHERE rnk not between 
        (SELECT Min(rnk) FROM Ranked r WHERE r.type <> 1 AND ranked.id = r.id)
        AND (SELECT Max(rnk) FROM Ranked r WHERE r.type <> 1 AND ranked.id = r.id)
order by id

Вам не нужно использовать TOP с оператором exist, хотя это не повредит.

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