С примером таблицы (> 20 миллионов строк) следующим образом:
CREATE TABLE T (
D DATE,
V INT
);
INSERT INTO T VALUES ('2024-07-01', 1), ('2024-07-02', 2), ('2024-07-02', 3);
У меня есть существующий запрос, который занимает около 5 секунд:
SELECT D, 'SUM', SUM(V)
FROM T
GROUP BY D
UNION
SELECT D, 'AVG', AVG(V)
FROM T
GROUP BY D
ORDER BY 1;
И необходимый вывод:
Чтобы избежать многократного сканирования, я переписываю так (около 1 с):
SELECT D, SUM(V), AVG(V)
FROM T
GROUP BY D;
Мне нужно сохранить вывод, я могу сделать это только в одном запросе, поэтому я попробовал общее табличное выражение:
WITH CTE AS (
SELECT D, SUM(V) AS S, AVG(V) AS A
FROM T
GROUP BY D
)
SELECT D, 'SUM', S
FROM CTE
UNION
SELECT D, 'AVG', A
FROM CTE
ORDER BY 1;
Но таблица по-прежнему сканируется дважды, а запрос все еще выполняется в течение 5 секунд:
Есть ли возможность сделать это одним запросом и только одним сканированием?
Обновлено:
Нашел решение с помощью json, но оно мне не нравится:
WITH CTE AS (
SELECT D, JSON_ARRAY(SUM(V), AVG(V)) AS data
FROM T
GROUP BY D
)
SELECT
c.D,
CASE WHEN JT.Id = 1 THEN 'SUM'
WHEN JT.Id = 2 THEN 'AVG'
END AS F,
JT.N
FROM CTE c,
JSON_TABLE(c.data, '$[*]'
COLUMNS(
Id for ordinality,
N FLOAT PATH '$[0]'
)
) AS JT;
@P.Salmon Этот запрос является примером. Реальный запрос более сложный, и индекс в порядке.
Вы используете MySQL или MariaDB? Вы отметили оба в своем вопросе, но это разные продукты, и они все более несовместимы друг с другом. Пожалуйста, поставьте SELECT VERSION();
и добавьте результат в свой пост.
@BillKarwin mariadb 10.6.18
вы пробовали подзапрос вместо cte? если это не сработает, возможно, подзапрос с дополнительным уровнем вложенности?
@ysth Нет, я не понимаю, как это сделать
Если вы видите, что ваш базовый запрос увеличился с 5 до 1 с удалением объединения, вы, вероятно, увидите некоторое улучшение, просто изменив union
на union all
. по умолчанию объединение имеет значение union distinct
, что может снизить производительность
@ysth На самом деле у меня 5 UNION, и каждый занимает 1 секунду. Я хочу избежать многократного сканирования большой таблицы. UNION ALL не помогает
Я бы использовал для этого подзапрос:
select D, rowtype, case rowtype when 'SUM' then sum_v else avg_v end value
from (
select D, sum(V) sum_v, avg(V) avg_v
from T
group by D
) sum_or_avg
join (select 'SUM' rowtype union all select 'AVG') rowtype
order by D, rowtype desc
У вас есть индекс на дату? Пожалуйста, добавьте план объяснения для обоих запросов.