Ускорение запроса на разделение в древней версии SQL Server
Настройка
У меня проблемы с производительностью и концептуальные проблемы с получением правильного запроса на SQL Server 7, работающем на двухъядерной машине с оперативной памятью 2 ГГц + 2 ГБ - нет шансов получить это, как вы могли ожидать:-/.
Ситуация
Я работаю с устаревшей базой данных, и мне нужно добывать данные, чтобы получить различные идеи. У меня есть all_stats
таблица, которая содержит все статистические данные для вещи в определенном контексте. Эти контексты сгруппированы с помощью group_contexts
Таблица. Упрощенная схема:
+--------------------------------------------------------------------+
| thingies |
+--------------------------------------------------------------------|
| id | INT PRIMARY KEY IDENTITY(1,1) |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
| all_stats |
+--------------------------------------------------------------------+
| id | INT PRIMARY KEY IDENTITY(1,1) |
| context_id | INT FOREIGN KEY REFERENCES contexts(id) |
| value | FLOAT NULL |
| some_date | DATETIME NOT NULL |
| thingy_id | INT NOT NULL FOREIGN KEY REFERENCES thingies(id) |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
| group_contexts |
+--------------------------------------------------------------------|
| id | INT PRIMARY KEY IDENTITY(1,1) |
| group_id | INT NOT NULL FOREIGN KEY REFERENCES groups(group_id) |
| context_id | INT NOT NULL FOREIGN KEY REFERENCES contexts(id) |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
| contexts |
+--------------------------------------------------------------------+
| id | INT PRIMARY KEY IDENTITY(1,1) |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
| groups |
+--------------------------------------------------------------------+
| group_id | INT PRIMARY KEY IDENTITY(1,1) |
+--------------------------------------------------------------------+
Эта проблема
Задача для заданного набора вещей найти и объединить 3 самых последнихall_stats.some_date
) характеристики вещи для всех групп, для которых у нее есть статистика. Я знаю, это звучит легко, но я не могу понять, как сделать это правильно в SQL - я не совсем вундеркинд.
Мое плохое решение (нет, это действительно плохо...)
Мое решение сейчас состоит в том, чтобы заполнить временную таблицу со всеми необходимыми данными и UNION ALL
В данных мне нужно:
-- Before I'm building this SQL I retrieve the relevant groups
-- for being able to build the `UNION ALL`s at the bottom.
-- I also retrieve the thingies that are relevant in this context
-- beforehand and include their ids as a comma separated list -
-- I said it would be awfull ...
-- Creating the temp table holding all stats data rows
-- for a thingy in a specific group
CREATE TABLE #stats
(id INT PRIMARY KEY IDENTITY(1,1),
group_id INT NOT NULL,
thingy_id INT NOT NULL,
value FLOAT NOT NULL,
some_date DATETIME NOT NULL)
-- Filling the temp table
INSERT INTO #stats(group_id,thingy_id,value,some_date)
SELECT filtered.group_id, filtered.thingy_id, filtered.some_date, filtered.value
FROM
(SELECT joined.group_id,joined.thingy_id,joined.value,joined.some_date
FROM
(SELECT groups.group_id,data.value,data.thingy_id,data.some_date
FROM
-- Getting the groups associated with the contexts
-- of all the stats available
(SELECT DISTINCT context.group_id
FROM all_stats AS stat
INNER JOIN group_contexts AS groupcontext
ON groupcontext.context_id = stat.context_id
) AS groups
INNER JOIN
-- Joining the available groups with the actual
-- stat data of the group for a thingy
(SELECT context.group_id,stat.value,stat.some_date,stat.thingy_id
FROM all_stats AS stat
INNER JOIN group_contexts AS groupcontext
ON groupcontext.context_id = stat.context_id
WHERE stat.value IS NOT NULL
AND stat.value >= 0) AS data
ON data.group_id = groups.group_id) AS joined
) AS filtered
-- I already have the thingies beforehand but if it would be possible
-- to include/query for them in another way that'd be OK by me
WHERE filtered.thingy_id in (/* somewhere around 10000 thingies are available */)
-- Now I'm building the `UNION ALL`s for each thingy as well as
-- the group the stat of the thingy belongs to
-- thingy 42 {
-- Getting the average of the most recent 3 stat items
-- for a thingy with id 42 in group 982
SELECT x.group_id,x.thingy_id,AVG(x.value)
FROM
(SELECT TOP 3 s.group_id,s.thingy_id,s.value,s.some_date
FROM #stats AS s
WHERE s.group_id = 982
AND s.thingy_id = 42
ORDER BY s.some_date DESC) AS x
GROUP BY x.group_id,x.thingy_id
HAVING COUNT(*) >= 3
UNION ALL
-- Getting the average of the most recent 3 stat items
-- for a thingy with id 42 in group 314159
SELECT x.group_id,x.thingy_id,AVG(x.value)
FROM
(SELECT TOP 3 s.group_id,s.thingy_id,s.value,s.some_date
FROM #stats AS s
WHERE s.group_id = 314159
AND s.thingy_id = 42
ORDER BY s.some_date DESC) AS x
GROUP BY x.group_id,x.thingy_id
HAVING COUNT(*) >= 3
-- }
UNION ALL
-- thingy 21 {
-- Getting the average of the most recent 3 stat items
-- for a thingy with id 21 in group 982
/* you get the idea */
Это работает - медленно, но работает - для небольших наборов данных (например, 100 штук с 10 прикрепленными статистиками каждый), но проблемная область, с которой он должен работать, состоит из 10000+ штук с потенциально сотнями статистических данных на штуку. В качестве примечания: сгенерированный SQL-запрос смехотворно велик: довольно маленький запрос включает, например, 350 штук, которые имеют данные в 3 контекстных группах, и он насчитывает более 250 000 отформатированных строк SQL - выполнение за 5 минут.
Так что, если у кого-то есть идея, как решить эту проблему, я очень, очень признателен за вашу помощь:-).
1 ответ
В вашей древней версии SQL Server вам нужно использовать скалярный подзапрос в старом стиле, чтобы получить последние три строки для всех штук в одном запросе:-)
SELECT x.group_id,x.thingy_id,AVG(x.value)
FROM
(
SELECT s.group_id,s.thingy_id,s.value
FROM #stats AS s
where (select count(*) from #stats as s2
where s.group_id = s2.group_id
and s.thingy_id = s2.thingy_id
and s.some_date <= s2.some_date
) <= 3
) AS x
GROUP BY x.group_id,x.thingy_id
HAVING COUNT(*) >= 3
Чтобы повысить производительность, вам нужно добавить кластерный индекс, вероятно, (group_id,thingy_id,some_date desc,value)
к #stats
Таблица.
Если group_id,thingy_id,some_date
уникален, вы должны удалить ненужное ID
столбец, в противном случае order by group_id,thingy_id,some_date desc
в течение Insert/Select
в #stats
и использовать ID
вместо some_date
для нахождения последних трех строк.