Материализованные представления
Материализованное представление (materialized view) вычисляет промежуточные агрегатные состояния для данных другой таблицы (source table) согласно запросу SELECT, который указывается в описании представления, и сохраняет результаты в свою внутреннюю или отдельную таблицу (target table). Обновление агрегатных состояний происходит автоматически при каждой вставке новых данных в исходную таблицу. Использование материализованных представлений позволяет оптимизировать время выполнения сложных запросов (с объединениями и агрегатами), обращающихся к большому количеству записей — за счет предварительного вычисления итоговых значений сокращается объем данных, которые необходимо обработать для выполнения запросов SELECT.
Создание
Обзор синтаксиса
Чтобы создать материализованное представление, используйте запрос CREATE MATERIALIZED VIEW. Общий синтаксис:
CREATE MATERIALIZED VIEW [IF NOT EXISTS] [<db_name>.]<mat_view_name> [ON CLUSTER <cluster_name>]
[TO [<db_name>.]<target_table>]
[ENGINE = <engine>]
[POPULATE]
AS SELECT
...
FROM [<db_name>.]<source_table>
...;
Для описания материализованного представления используются следующие выражения.
TO |
Целевая таблица (target table), в которую будут сохраняться данные из материализованного представления |
ENGINE |
Движок внутренней таблицы материализованного представления, где будут храниться агрегированные данные, если отдельная целевая таблица не была заранее создана и явно указана через выражение |
POPULATE |
Указывает, что при создании материализованного представления в него будут добавлены данные, уже содержащиеся в исходной таблице. Использовать |
AS SELECT … FROM … |
Запрос, который считывает новые данные, вставляемые в исходную таблицу, и определяет, как эти данные преобразовывать для загрузки в материализованное представление (или целевую таблицу, если она указана). Запрос Изменить запрос |
|
ПРИМЕЧАНИЕ
Изменения существующих данных в исходной таблице (например, обновление, удаление и т.д.) не влияют на материализованное представление. |
В ADQM материализованные представления выглядят так же, как обычные таблицы. Например, они включаются в результат запроса SHOW TABLES.
Чтобы удалить материализованное представление, выполните запрос DROP VIEW или DROP TABLE.
Пример
В примере ниже показано, как создать материализованное представление для предварительной агрегации данных, чтобы ускорить последующие выполнения запросов SELECT.
-
Создайте таблицу, которая будет хранить исходные данные о просмотрах страниц:
CREATE TABLE page_views ( time DateTime, page_name String, time_on_page UInt64 ) ENGINE = MergeTree ORDER BY time; -
Создайте целевую таблицу, в которую будут записываться количество просмотров и среднее время просмотра страниц по месяцам:
CREATE TABLE page_views_monthly ( page_name String, month Date, page_view_count UInt64, avg_time_on_page AggregateFunction(avg, UInt64) ) ENGINE = SummingMergeTree() ORDER BY (page_name, month);Для колонки со средним значением времени просмотра страниц используется тип данных AggregateFunction, который позволяет хранить частично агрегированные данные. Табличный движок SummingMergeTree, который используется в примере, требует этот тип данных для всех агрегатов, кроме
sumиcount. Для работы с материализованными представлениями можно использовать и другие движки таблиц. Например, движок AggregatingMergeTree — этот движок работает только с агрегатными функциями (то есть если бы в запросе выше использовался движок AggregatingMergeTree, для колонкиpage_view_countтакже нужно было бы использовать тип AggregateFunction). -
Создайте материализованное представление:
CREATE MATERIALIZED VIEW page_views_monthly_mv TO page_views_monthly AS SELECT toDate(toStartOfMonth(time)) AS month, page_name, count() as page_view_count, avgState(time_on_page) AS avg_time_on_page FROM page_views GROUP BY page_name, month ORDER BY page_name, month;При вставке данных в исходную таблицу
page_viewsматериализованное представление преобразует вставляемые данные, как указано в запросеSELECT, и поместит результат в целевую таблицуpage_views_monthly. Для сохранения промежуточных агрегатных состояний в столбец типа AggregateFunction (avg_time_on_page) в запросеSELECTдолжна использоваться соответствующая агрегатная функция с суффиксомState(avgState).Если на этом этапе выполнить запрос
SHOW TABLES, вывод на экран будет включать созданные таблицы и материализованное представление:┌─name──────────────────┐ │ page_views │ │ page_views_monthly │ │ page_views_monthly_mv │ └───────────────────────┘
-
Заполните исходную таблицу тестовыми данными. Например, сгенерируйте случайным образом 1 миллион просмотров для 4 страниц:
INSERT INTO page_views SELECT toDate('2023-01-01 00:00:00') + (rand() % 182) AS time, ['landing', 'products', 'docs', 'about'][toInt32(randBinomial(4, 0.01)) + 1], rand() % 15 FROM numbers(1000000); -
Сравните скорость выполнения запроса
SELECTпри выборке данных из исходной таблицы с необработанными входящими данными и таблицы с предварительно агрегированными данными по месяцам.
Сделайте выборку данных из таблицыpage_views:SELECT page_name, toDate(toStartOfMonth(time)) AS month, count() AS page_view_count, round(avg(time_on_page), 2) AS avg_time_on_page FROM page_views GROUP BY page_name, month ORDER BY page_name, month;┌─page_name─┬──────month─┬─page_view_count─┬─avg_time_on_page─┐ │ about │ 2023-02-01 │ 2 │ 7 │ │ about │ 2023-03-01 │ 1 │ 7 │ │ docs │ 2023-01-01 │ 95 │ 7.21 │ │ docs │ 2023-02-01 │ 93 │ 6.42 │ │ docs │ 2023-03-01 │ 102 │ 6.72 │ │ docs │ 2023-04-01 │ 84 │ 7.35 │ │ docs │ 2023-05-01 │ 101 │ 7.18 │ │ docs │ 2023-06-01 │ 92 │ 7.72 │ │ docs │ 2023-07-01 │ 5 │ 8.6 │ │ landing │ 2023-01-01 │ 163242 │ 7.01 │ │ landing │ 2023-02-01 │ 147695 │ 7 │ │ landing │ 2023-03-01 │ 163930 │ 6.99 │ │ landing │ 2023-04-01 │ 158155 │ 7.01 │ │ landing │ 2023-05-01 │ 163263 │ 6.99 │ │ landing │ 2023-06-01 │ 158840 │ 7 │ │ landing │ 2023-07-01 │ 5396 │ 7.02 │ │ products │ 2023-01-01 │ 6667 │ 7.05 │ │ products │ 2023-02-01 │ 6099 │ 6.99 │ │ products │ 2023-03-01 │ 6589 │ 7.02 │ │ products │ 2023-04-01 │ 6391 │ 7.01 │ │ products │ 2023-05-01 │ 6428 │ 6.98 │ │ products │ 2023-06-01 │ 6517 │ 7.04 │ │ products │ 2023-07-01 │ 213 │ 7.48 │ └───────────┴────────────┴─────────────────┴──────────────────┘ 23 rows in set. Elapsed: 0.023 sec. Processed 1.00 million rows, 28.04 MB (43.43 million rows/s., 1.22 GB/s.)
Сделайте выборку из таблицы
page_views_monthly— чтение данных из этой таблицы выполнится в 7 раз быстрее, чем из исходной таблицы (на большем объеме данных разница будет еще более заметна). Обратите внимание, в запросеSELECTиспользуется та же агрегатная функция, что и при вставке данных, но с суффиксомMerge— это необходимо, чтобы объединить множество промежуточных агрегатных состояний и вернуть полную агрегацию данных (см. AggregateFunction):SELECT page_name, month, sum(page_view_count) AS page_view_count, round(avgMerge(avg_time_on_page), 2) AS avg_time_on_page FROM page_views_monthly GROUP BY page_name, month ORDER BY page_name, month;┌─page_name─┬──────month─┬─page_view_count─┬─avg_time_on_page─┐ │ about │ 2023-02-01 │ 2 │ 7 │ │ about │ 2023-03-01 │ 1 │ 7 │ │ docs │ 2023-01-01 │ 95 │ 7.21 │ │ docs │ 2023-02-01 │ 93 │ 6.42 │ │ docs │ 2023-03-01 │ 102 │ 6.72 │ │ docs │ 2023-04-01 │ 84 │ 7.35 │ │ docs │ 2023-05-01 │ 101 │ 7.18 │ │ docs │ 2023-06-01 │ 92 │ 7.72 │ │ docs │ 2023-07-01 │ 5 │ 8.6 │ │ landing │ 2023-01-01 │ 163242 │ 7.01 │ │ landing │ 2023-02-01 │ 147695 │ 7 │ │ landing │ 2023-03-01 │ 163930 │ 6.99 │ │ landing │ 2023-04-01 │ 158155 │ 7.01 │ │ landing │ 2023-05-01 │ 163263 │ 6.99 │ │ landing │ 2023-06-01 │ 158840 │ 7 │ │ landing │ 2023-07-01 │ 5396 │ 7.02 │ │ products │ 2023-01-01 │ 6667 │ 7.05 │ │ products │ 2023-02-01 │ 6099 │ 6.99 │ │ products │ 2023-03-01 │ 6589 │ 7.02 │ │ products │ 2023-04-01 │ 6391 │ 7.01 │ │ products │ 2023-05-01 │ 6428 │ 6.98 │ │ products │ 2023-06-01 │ 6517 │ 7.04 │ │ products │ 2023-07-01 │ 213 │ 7.48 │ └───────────┴────────────┴─────────────────┴──────────────────┘ 23 rows in set. Elapsed: 0.003 sec.
Изменение
Запрос SELECT, который был указан при создании материализованного представления, можно изменить с помощью выражения ALTER TABLE…MODIFY QUERY. Эта команда не меняет структуру и описания столбцов внутренней таблицы материализованного представления, поэтому не рекомендуется ее использование для изменения материализованных представлений, созданных без указания целевой таблицы через выражение TO.
Перед выполнением команды ALTER TABLE…MODIFY QUERY, активируйте опцию: SET allow_experimental_alter_materialized_view_structure=1.
Пример
-
С помощью выражения ALTER TABLE…ADD COLUMN добавьте столбец
browserв исходную таблицуpage_viewsи целевую таблицуpage_views_monthly(столбцы материализованного представления и целевой таблицы не обязательно должны совпадать, поэтому изменение структуры целевой таблицы не нарушит вставку данных):ALTER TABLE page_views ADD COLUMN browser String;ALTER TABLE page_views_monthly ADD COLUMN browser String after month, MODIFY ORDER BY (page_name, month, browser); -
Вставьте данные в исходную таблицу:
INSERT INTO page_views SELECT toDate('2023-01-01 00:00:00') + (rand() % 182) AS time, ['landing', 'products', 'docs', 'about'][toInt32(randBinomial(4, 0.01)) + 1], rand() % 15, ['firefox', 'safary', 'chrome'][(number % 3) + 1] FROM numbers(1000000);Если на этом этапе сделать выборку данных из целевой таблицы, столбец
browserбудет пустым:SELECT page_name, month, browser, sum(page_view_count) AS page_view_count, round(avgMerge(avg_time_on_page), 2) AS avg_time_on_page FROM page_views_monthly GROUP BY page_name, month, browser ORDER BY page_name, month ASC, browser;┌─page_name─┬──────month─┬─browser─┬─page_view_count─┬─avg_time_on_page─┐ │ about │ 2023-01-01 │ │ 1 │ 14 │ │ about │ 2023-02-01 │ │ 3 │ 6.67 │ │ about │ 2023-03-01 │ │ 2 │ 8.5 │ │ about │ 2023-05-01 │ │ 1 │ 12 │ │ docs │ 2023-01-01 │ │ 203 │ 7.13 │ │ docs │ 2023-02-01 │ │ 175 │ 6.91 │ │ docs │ 2023-03-01 │ │ 185 │ 7.21 │ │ docs │ 2023-04-01 │ │ 175 │ 7.15 │ │ docs │ 2023-05-01 │ │ 182 │ 7.53 │ │ docs │ 2023-06-01 │ │ 190 │ 7.94 │ │ docs │ 2023-07-01 │ │ 8 │ 9.88 │ │ landing │ 2023-01-01 │ │ 326986 │ 7.01 │ │ landing │ 2023-02-01 │ │ 295510 │ 6.99 │ │ landing │ 2023-03-01 │ │ 327021 │ 7 │ │ landing │ 2023-04-01 │ │ 316761 │ 7 │ │ landing │ 2023-05-01 │ │ 327207 │ 7 │ │ landing │ 2023-06-01 │ │ 316732 │ 6.99 │ │ landing │ 2023-07-01 │ │ 10702 │ 7.05 │ │ products │ 2023-01-01 │ │ 13224 │ 7.07 │ │ products │ 2023-02-01 │ │ 12103 │ 7.02 │ │ products │ 2023-03-01 │ │ 13225 │ 6.99 │ │ products │ 2023-04-01 │ │ 12807 │ 6.98 │ │ products │ 2023-05-01 │ │ 13156 │ 6.95 │ │ products │ 2023-06-01 │ │ 13000 │ 7.01 │ │ products │ 2023-07-01 │ │ 441 │ 6.95 │ └───────────┴────────────┴─────────┴─────────────────┴──────────────────┘
-
Чтобы данные из нового столбца исходной таблицы попадали в соответствующий столбец целевой таблицы, необходимо изменить запрос
SELECTматериализованного представления. Для этого выполните командуALTER TABLE…MODIFY QUERY:SET allow_experimental_alter_materialized_view_structure=1;ALTER TABLE page_views_monthly_mv MODIFY QUERY SELECT toDate(toStartOfMonth(time)) AS month, page_name, browser, count() AS page_view_count, avgState(time_on_page) AS avg_time_on_page FROM page_views GROUP BY page_name, month, browser ORDER BY page_name, month, browser; -
Добавьте еще раз данные в исходную таблицу:
INSERT INTO page_views SELECT toDate('2023-01-01 00:00:00') + (rand() % 182) AS time, ['landing', 'products', 'docs', 'about'][toInt32(randBinomial(4, 0.01)) + 1], rand() % 15, ['firefox', 'safary', 'chrome'][(number % 3) + 1] FROM numbers(1000000);Убедитесь, что данные из столбца
browserтеперь учитываются в целевой таблице:SELECT page_name, month, browser, sum(page_view_count) AS page_view_count, round(avgMerge(avg_time_on_page), 2) AS avg_time_on_page FROM page_views_monthly GROUP BY page_name, month, browser ORDER BY page_name, month ASC, browser;┌─page_name─┬──────month─┬─browser─┬─page_view_count─┬─avg_time_on_page─┐ │ about │ 2023-01-01 │ │ 1 │ 14 │ │ about │ 2023-02-01 │ │ 3 │ 6.67 │ │ about │ 2023-02-01 │ safary │ 1 │ 7 │ │ about │ 2023-03-01 │ │ 2 │ 8.5 │ │ about │ 2023-04-01 │ firefox │ 1 │ 1 │ │ about │ 2023-05-01 │ │ 1 │ 12 │ │ about │ 2023-05-01 │ chrome │ 1 │ 14 │ │ about │ 2023-05-01 │ safary │ 1 │ 6 │ │ docs │ 2023-01-01 │ │ 203 │ 7.13 │ │ docs │ 2023-01-01 │ chrome │ 37 │ 6.92 │ │ docs │ 2023-01-01 │ firefox │ 38 │ 5.58 │ │ docs │ 2023-01-01 │ safary │ 29 │ 5.79 │ │ docs │ 2023-02-01 │ │ 175 │ 6.91 │ │ docs │ 2023-02-01 │ chrome │ 30 │ 6.77 │ │ docs │ 2023-02-01 │ firefox │ 26 │ 6.65 │ │ docs │ 2023-02-01 │ safary │ 18 │ 5.89 │ │ docs │ 2023-03-01 │ │ 185 │ 7.21 │ │ docs │ 2023-03-01 │ chrome │ 42 │ 7.83 │ │ docs │ 2023-03-01 │ firefox │ 31 │ 7.65 │ │ docs │ 2023-03-01 │ safary │ 33 │ 5.88 │ │ docs │ 2023-04-01 │ │ 175 │ 7.15 │ │ docs │ 2023-04-01 │ chrome │ 28 │ 8.25 │ │ docs │ 2023-04-01 │ firefox │ 25 │ 6.32 │ │ docs │ 2023-04-01 │ safary │ 26 │ 6.58 │ │ docs │ 2023-05-01 │ │ 182 │ 7.53 │ │ docs │ 2023-05-01 │ chrome │ 32 │ 7.03 │ │ docs │ 2023-05-01 │ firefox │ 20 │ 7.45 │ │ docs │ 2023-05-01 │ safary │ 34 │ 6.59 │ │ docs │ 2023-06-01 │ │ 190 │ 7.94 │ │ docs │ 2023-06-01 │ chrome │ 35 │ 8.37 │ │ docs │ 2023-06-01 │ firefox │ 29 │ 6.69 │ │ docs │ 2023-06-01 │ safary │ 27 │ 7.15 │ │ docs │ 2023-07-01 │ │ 8 │ 9.88 │ │ docs │ 2023-07-01 │ chrome │ 2 │ 2 │ │ docs │ 2023-07-01 │ firefox │ 1 │ 1 │ │ docs │ 2023-07-01 │ safary │ 1 │ 1 │ │ landing │ 2023-01-01 │ │ 326986 │ 7.01 │ │ landing │ 2023-01-01 │ chrome │ 54763 │ 6.99 │ │ landing │ 2023-01-01 │ firefox │ 54665 │ 7.02 │ │ landing │ 2023-01-01 │ safary │ 54294 │ 7 │ │ landing │ 2023-02-01 │ │ 295510 │ 6.99 │ │ landing │ 2023-02-01 │ chrome │ 48944 │ 7.03 │ │ landing │ 2023-02-01 │ firefox │ 49031 │ 7.03 │ │ landing │ 2023-02-01 │ safary │ 49298 │ 7 │ │ landing │ 2023-03-01 │ │ 327021 │ 7 │ │ landing │ 2023-03-01 │ chrome │ 54381 │ 6.98 │ │ landing │ 2023-03-01 │ firefox │ 54433 │ 7.03 │ │ landing │ 2023-03-01 │ safary │ 54556 │ 7.01 │ │ landing │ 2023-04-01 │ │ 316761 │ 7 │ │ landing │ 2023-04-01 │ chrome │ 52931 │ 6.95 │ │ landing │ 2023-04-01 │ firefox │ 52706 │ 6.99 │ │ landing │ 2023-04-01 │ safary │ 53075 │ 6.99 │ │ landing │ 2023-05-01 │ │ 327207 │ 7 │ │ landing │ 2023-05-01 │ chrome │ 54538 │ 6.99 │ │ landing │ 2023-05-01 │ firefox │ 54733 │ 7.02 │ │ landing │ 2023-05-01 │ safary │ 54618 │ 7.02 │ │ landing │ 2023-06-01 │ │ 316732 │ 6.99 │ │ landing │ 2023-06-01 │ chrome │ 52763 │ 6.97 │ │ landing │ 2023-06-01 │ firefox │ 52955 │ 7 │ │ landing │ 2023-06-01 │ safary │ 52795 │ 7.01 │ │ landing │ 2023-07-01 │ │ 10702 │ 7.05 │ │ landing │ 2023-07-01 │ chrome │ 1762 │ 7.08 │ │ landing │ 2023-07-01 │ firefox │ 1747 │ 6.98 │ │ landing │ 2023-07-01 │ safary │ 1767 │ 7.09 │ │ products │ 2023-01-01 │ │ 13224 │ 7.07 │ │ products │ 2023-01-01 │ chrome │ 2208 │ 7.04 │ │ products │ 2023-01-01 │ firefox │ 2210 │ 6.99 │ │ products │ 2023-01-01 │ safary │ 2273 │ 6.99 │ │ products │ 2023-02-01 │ │ 12103 │ 7.02 │ │ products │ 2023-02-01 │ chrome │ 1970 │ 7.1 │ │ products │ 2023-02-01 │ firefox │ 1978 │ 6.88 │ │ products │ 2023-02-01 │ safary │ 1870 │ 7.01 │ │ products │ 2023-03-01 │ │ 13225 │ 6.99 │ │ products │ 2023-03-01 │ chrome │ 2189 │ 6.97 │ │ products │ 2023-03-01 │ firefox │ 2186 │ 6.94 │ │ products │ 2023-03-01 │ safary │ 2251 │ 7.18 │ │ products │ 2023-04-01 │ │ 12807 │ 6.98 │ │ products │ 2023-04-01 │ chrome │ 2252 │ 6.91 │ │ products │ 2023-04-01 │ firefox │ 2210 │ 7.1 │ │ products │ 2023-04-01 │ safary │ 2089 │ 7.04 │ │ products │ 2023-05-01 │ │ 13156 │ 6.95 │ │ products │ 2023-05-01 │ chrome │ 2214 │ 7 │ │ products │ 2023-05-01 │ firefox │ 2188 │ 6.94 │ │ products │ 2023-05-01 │ safary │ 2151 │ 6.98 │ │ products │ 2023-06-01 │ │ 13000 │ 7.01 │ │ products │ 2023-06-01 │ chrome │ 2134 │ 6.89 │ │ products │ 2023-06-01 │ firefox │ 2054 │ 7.18 │ │ products │ 2023-06-01 │ safary │ 2062 │ 6.91 │ │ products │ 2023-07-01 │ │ 441 │ 6.95 │ │ products │ 2023-07-01 │ chrome │ 77 │ 6.65 │ │ products │ 2023-07-01 │ firefox │ 67 │ 7.27 │ │ products │ 2023-07-01 │ safary │ 64 │ 7.16 │ └───────────┴────────────┴─────────┴─────────────────┴──────────────────┘