Часто приходится работать с данными в произвольных текстовых форматах. Это может быть нестандартный формат, некорректный JSON или повреждённый CSV. Стандартные парсеры, такие как CSV или JSON, подходят не для всех таких случаев. Но в ClickHouse для этого есть мощные форматы Template и Regex.
Импорт по шаблону
Предположим, что мы хотим импортировать данные из следующего файла журнала:
head error.log2023/01/15 14:51:17 [error] client: 7.2.8.1, server: example.com "GET /apple-touch-icon-120x120.png HTTP/1.1"
2023/01/16 06:02:09 [error] client: 8.4.2.7, server: example.com "GET /apple-touch-icon-120x120.png HTTP/1.1"
2023/01/15 13:46:13 [error] client: 6.9.3.7, server: example.com "GET /apple-touch-icon.png HTTP/1.1"
2023/01/16 05:34:55 [error] client: 9.9.7.6, server: example.com "GET /h5/static/cert/icon_yanzhengma.png HTTP/1.1"Мы можем использовать формат Template для импорта этих данных. Нужно определить строку шаблона с плейсхолдерами значений для каждой строки входных данных:
<time> [error] client: <ip>, server: <host> "<request>"Создадим таблицу, в которую импортируем данные:
CREATE TABLE error_log
(
`time` DateTime,
`ip` String,
`host` String,
`request` String
)
ENGINE = MergeTree
ORDER BY (host, request, time)Чтобы импортировать данные с помощью указанного шаблона, нужно сохранить строку шаблона в файл (row.template в нашем случае):
${time:Escaped} [error] client: ${ip:CSV}, server: ${host:CSV} ${request:JSON}Мы задаём имя столбца и правило экранирования в формате ${name:escaping}. Здесь доступно несколько вариантов, например CSV, JSON, Escaped или Quoted, для которых используются соответствующие правила экранирования.
Теперь мы можем использовать этот файл как аргумент параметра format_template_row при импорте данных (обратите внимание: в конце файлов шаблона и данных не должно быть лишнего символа \n):
INSERT INTO error_log FROM INFILE 'error.log'
SETTINGS format_template_row = 'row.template'
FORMAT TemplateИ можем убедиться, что данные были загружены в таблицу:
SELECT
request,
count(*)
FROM error_log
GROUP BY request┌─request──────────────────────────────────────────┬─count()─┐
│ GET /img/close.png HTTP/1.1 │ 176 │
│ GET /h5/static/cert/icon_yanzhengma.png HTTP/1.1 │ 172 │
│ GET /phone/images/icon_01.png HTTP/1.1 │ 139 │
│ GET /apple-touch-icon-precomposed.png HTTP/1.1 │ 161 │
│ GET /apple-touch-icon.png HTTP/1.1 │ 162 │
│ GET /apple-touch-icon-120x120.png HTTP/1.1 │ 190 │
└──────────────────────────────────────────────────┴─────────┘Пропуск пробелов
Можно использовать TemplateIgnoreSpaces, чтобы пропускать пробелы между разделителями в шаблоне:
Template: --> "p1: ${p1:CSV}, p2: ${p2:CSV}"
TemplateIgnoreSpaces --> "p1:${p1:CSV}, p2:${p2:CSV}"Экспорт данных с использованием Template
Мы также можем экспортировать данные в любой текстовый формат с помощью Template. В этом случае нужно создать два файла:
Шаблон результирующего набора, который задаёт структуру всего результирующего набора:
== Top 10 IPs ==
${data}
--- ${rows_read:XML} rows read in ${time:XML} ---Здесь rows_read и time — системные метрики, доступные для каждого запроса. При этом data обозначает сгенерированные строки (${data} всегда должен идти первым плейсхолдером в этом файле), сформированные по шаблону, заданному в файле шаблона строки:
${ip:Escaped} generated ${total:Escaped} requestsТеперь воспользуемся этими Template, чтобы экспортировать следующий запрос:
SELECT
ip,
count() AS total
FROM error_log GROUP BY ip ORDER BY total DESC LIMIT 10
FORMAT Template SETTINGS format_template_resultset = 'output.results',
format_template_row = 'output.rows';
== Top 10 IPs ==
9.8.4.6 generated 3 requests
9.5.1.1 generated 3 requests
2.4.8.9 generated 3 requests
4.8.8.2 generated 3 requests
4.5.4.4 generated 3 requests
3.3.6.4 generated 2 requests
8.9.5.9 generated 2 requests
2.5.1.8 generated 2 requests
6.8.3.6 generated 2 requests
6.6.3.5 generated 2 requests
--- 1000 rows read in 0.001380604 ---Экспорт в HTML-файлы
Результаты, сформированные по шаблону, также можно экспортировать в файлы с помощью конструкции INTO OUTFILE. Давайте сгенерируем HTML-файлы на основе заданных форматов resultset и row:
SELECT
ip,
count() AS total
FROM error_log GROUP BY ip ORDER BY total DESC LIMIT 10
INTO OUTFILE 'out.html'
FORMAT Template
SETTINGS format_template_resultset = 'html.results',
format_template_row = 'html.row'Экспорт в XML
Формат Template можно использовать для создания любых текстовых файлов, включая XML. Просто задайте подходящий шаблон и выполните экспорт.
Также можно использовать формат XML, чтобы получить стандартный XML-вывод, включая метаданные:
SELECT *
FROM error_log
LIMIT 3
FORMAT XML<?xml version='1.0' encoding='UTF-8' ?>
<result>
<meta>
<columns>
<column>
<name>time</name>
<type>DateTime</type>
</column>
...
</columns>
</meta>
<data>
<row>
<time>2023-01-15 13:00:01</time>
<ip>3.5.9.2</ip>
<host>example.com</host>
<request>GET /apple-touch-icon-120x120.png HTTP/1.1</request>
</row>
...
</data>
<rows>3</rows>
<rows_before_limit_at_least>1000</rows_before_limit_at_least>
<statistics>
<elapsed>0.000745001</elapsed>
<rows_read>1000</rows_read>
<bytes_read>88184</bytes_read>
</statistics>
</result>Импорт данных с помощью регулярных выражений
Формат Regexp подходит для более сложных случаев, когда входные данные нужно разбирать более нетривиальным способом. Разберём наш файл error.log, но на этот раз извлечём имя файла и протокол, чтобы сохранить их в отдельные столбцы. Сначала подготовим для этого новую таблицу:
CREATE TABLE error_log
(
`time` DateTime,
`ip` String,
`host` String,
`file` String,
`protocol` String
)
ENGINE = MergeTree
ORDER BY (host, file, time)Теперь мы можем импортировать данные с помощью регулярного выражения:
INSERT INTO error_log FROM INFILE 'error.log'
SETTINGS
format_regexp = '(.+?) \\[error\\] client: (.+), server: (.+?) "GET .+?([^/]+\\.[^ ]+) (.+?)"'
FORMAT RegexpClickHouse вставит данные из каждой группы захвата в соответствующий столбец в порядке следования. Проверим данные:
SELECT * FROM error_log LIMIT 5┌────────────────time─┬─ip──────┬─host────────┬─file─────────────────────────┬─protocol─┐
│ 2023-01-15 13:00:01 │ 3.5.9.2 │ example.com │ apple-touch-icon-120x120.png │ HTTP/1.1 │
│ 2023-01-15 13:01:40 │ 3.7.2.5 │ example.com │ apple-touch-icon-120x120.png │ HTTP/1.1 │
│ 2023-01-15 13:16:49 │ 9.2.9.2 │ example.com │ apple-touch-icon-120x120.png │ HTTP/1.1 │
│ 2023-01-15 13:21:38 │ 8.8.5.3 │ example.com │ apple-touch-icon-120x120.png │ HTTP/1.1 │
│ 2023-01-15 13:31:27 │ 9.5.8.4 │ example.com │ apple-touch-icon-120x120.png │ HTTP/1.1 │
└─────────────────────┴─────────┴─────────────┴──────────────────────────────┴──────────┘По умолчанию ClickHouse выдаёт ошибку при наличии несовпавших строк. Если вы хотите вместо этого пропускать такие строки, включите параметр format_regexp_skip_unmatched:
SET format_regexp_skip_unmatched = 1;Другие форматы
ClickHouse поддерживает множество форматов — как текстовых, так и двоичных — для самых разных сценариев и платформ. Подробнее о форматах и способах работы с ними читайте в следующих статьях:
- Форматы CSV и TSV
- Parquet
- Форматы JSON
- Regex и Template
- Native и двоичные форматы
- Форматы SQL
Также обратите внимание на clickhouse-local — это портативный полнофункциональный инструмент для работы с локальными и удалёнными файлами без необходимости запускать сервер ClickHouse.