Перейти к основному содержанию
ClickHouse теперь поддерживает нативный тип столбца JSON, предназначенный для полуструктурированных и динамических данных. Важно уточнить, что это тип столбца, а не формат данных — вы можете вставлять JSON в ClickHouse как строку или через поддерживаемые форматы, например JSONEachRow, но это не означает, что вы используете тип столбца JSON. Тип JSON следует использовать только тогда, когда структура ваших данных динамична, а не просто в тех случаях, когда вы храните JSON.

Когда использовать тип JSON

Тип JSON предназначен для запросов, фильтрации и агрегации по отдельным полям в объектах JSON с динамической или непредсказуемой структурой. Для этого объекты JSON разбиваются на отдельные подстолбцы, что значительно уменьшает объём читаемых данных и ускоряет запросы по выбранным полям по сравнению с такими альтернативами, как Map или разбор строк. Однако у этого подхода есть важные недостатки:
  • Более медленные INSERT - Разбиение JSON на подстолбцы, определение типов и управление гибкими структурами хранения делают вставку медленнее по сравнению с хранением JSON в виде простого столбца String.
  • Медленнее при чтении объектов целиком - Если вам нужно получать JSON-документы целиком, а не отдельные поля, тип JSON работает медленнее, чем чтение из столбца String. Дополнительные затраты на восстановление объектов из отдельных подстолбцов не дают преимуществ, если вы не выполняете запрос по отдельным полям.
  • Дополнительные накладные расходы на хранение - Поддержка отдельных подстолбцов создаёт дополнительные структурные накладные расходы по сравнению с хранением JSON как одного строкового значения.

Используйте тип JSON, когда:

  • У ваших данных динамическая или непредсказуемая структура, а ключи различаются от документа к документу
  • Типы полей или схемы меняются со временем либо различаются между записями
  • Вам нужно выполнять запросы, фильтровать или агрегировать данные по определённым путям внутри объектов JSON, структуру которых невозможно заранее предсказать
  • Ваш сценарий предполагает работу с полуструктурированными данными, такими как журнал, события или пользовательский контент с непоследовательными схемами

Используйте столбец String (или структурированные типы), когда:

  • Структура ваших данных известна и стабильна — в этом случае лучше использовать обычные столбцы, типы Tuple, Array, Dynamic или Variant
  • Документы JSON рассматриваются как непрозрачные blob-объекты, которые только хранятся и извлекаются целиком, без анализа на уровне полей
  • Вам не нужно выполнять запросы или фильтровать данные по отдельным полям JSON в базе данных
  • JSON — это просто формат передачи/хранения, а не формат, который анализируется в ClickHouse
Если JSON — это непрозрачный документ, который не анализируется в базе данных, а только сохраняется и затем извлекается целиком, его следует хранить в поле String. Преимущества типа JSON проявляются только тогда, когда вам нужно эффективно выполнять запросы, фильтрацию или агрегацию по конкретным полям в динамических структурах JSON.Вы также можете комбинировать подходы — использовать стандартные столбцы для предсказуемых полей верхнего уровня и столбец JSON для динамических частей полезной нагрузки.

Рекомендации и советы по использованию JSON

Тип JSON обеспечивает эффективное столбцовое хранение, разворачивая пути в подстолбцы. Но гибкость требует ответственного подхода. Чтобы использовать его эффективно:
  • Указывайте типы путей с помощью подсказок в определении столбца, чтобы задавать типы для известных подстолбцов и избегать лишнего вывода типов.
  • Пропускайте пути, если эти значения вам не нужны, с помощью SKIP и SKIP REGEXP, чтобы сократить объём хранения и повысить производительность.
  • Не задавайте max_dynamic_paths слишком большим — большие значения увеличивают потребление ресурсов и снижают эффективность. Как правило, держите его ниже 10 000.
Подсказки типовПодсказки типов — это не просто способ избежать лишнего вывода типов: они полностью устраняют дополнительный уровень косвенности при хранении и обработке. Пути JSON с подсказками типов всегда хранятся так же, как обычные столбцы, без необходимости использовать столбцы-дискриминаторы или выполнять динамическое разрешение во время выполнения запроса. Это означает, что при хорошо заданных подсказках типов вложенные поля JSON обеспечивают ту же производительность и эффективность, как если бы они изначально были смоделированы как поля верхнего уровня. В результате для датасетов, которые в целом однородны, но при этом выигрывают от гибкости JSON, подсказки типов позволяют сохранить производительность без необходимости перестраивать схему или конвейер приёма.

Расширенные возможности

  • JSON-столбцы можно использовать в первичных ключах так же, как и любые другие столбцы. Для подстолбцов нельзя указывать кодеки.
  • Они поддерживают интроспекцию с помощью таких функций, как JSONAllPathsWithTypes() и JSONDynamicPaths().
  • Вы можете читать вложенные объекты с помощью синтаксиса .^.
  • Синтаксис запросов может отличаться от стандартного SQL и требовать специального приведения типов или операторов для вложенных полей.
Дополнительные рекомендации см. в документации ClickHouse по JSON или в нашей статье блога A New Powerful JSON Data Type for ClickHouse.

Примеры

Рассмотрим следующий образец JSON, представляющий строку из набора данных Python PyPI:
{
  "date": "2022-11-15",
  "country_code": "ES",
  "project": "clickhouse-connect",
  "type": "bdist_wheel",
  "installer": "pip",
  "python_minor": "3.9",
  "system": "Linux",
  "version": "0.3.0"
}
Предположим, что эта схема статична и типы можно чётко определить. Даже если данные находятся в формате NDJSON (одна JSON-строка на строку), для такой схемы нет необходимости использовать тип JSON. Просто определите схему с помощью классических типов.
CREATE TABLE pypi (
  `date` Date,
  `country_code` String,
  `project` String,
  `type` String,
  `installer` String,
  `python_minor` String,
  `system` String,
  `version` String
)
ENGINE = MergeTree
ORDER BY (project, date)
и вставьте JSON-строки:
INSERT INTO pypi FORMAT JSONEachRow
{"date":"2022-11-15","country_code":"ES","project":"clickhouse-connect","type":"bdist_wheel","installer":"pip","python_minor":"3.9","system":"Linux","version":"0.3.0"}
Рассмотрим датасет arXiv, содержащий 2,5 млн научных публикаций. Каждая строка в этом датасете, распространяемом в формате NDJSON, соответствует одной опубликованной научной работе. Пример строки показан ниже:
{
  "id": "2101.11408",
  "submitter": "Daniel Lemire",
  "authors": "Daniel Lemire",
  "title": "Number Parsing at a Gigabyte per Second",
  "comments": "Software at https://github.com/fastfloat/fast_float and\n  https://github.com/lemire/simple_fastfloat_benchmark/",
  "journal-ref": "Software: Practice and Experience 51 (8), 2021",
  "doi": "10.1002/spe.2984",
  "report-no": null,
  "categories": "cs.DS cs.MS",
  "license": "http://creativecommons.org/licenses/by/4.0/",
  "abstract": "With disks and networks providing gigabytes per second ....\n",
  "versions": [
    {
      "created": "Mon, 11 Jan 2021 20:31:27 GMT",
      "version": "v1"
    },
    {
      "created": "Sat, 30 Jan 2021 23:57:29 GMT",
      "version": "v2"
    }
  ],
  "update_date": "2022-11-07",
  "authors_parsed": [
    [
      "Lemire",
      "Daniel",
      ""
    ]
  ]
}
Хотя JSON здесь сложный и содержит вложенные структуры, его формат предсказуем. Количество и типы полей не меняются. Хотя в этом примере можно было бы использовать тип JSON, структуру также можно явно определить с помощью типов Tuple и Nested:
CREATE TABLE arxiv
(
  `id` String,
  `submitter` String,
  `authors` String,
  `title` String,
  `comments` String,
  `journal-ref` String,
  `doi` String,
  `report-no` String,
  `categories` String,
  `license` String,
  `abstract` String,
  `versions` Array(Tuple(created String, version String)),
  `update_date` Date,
  `authors_parsed` Array(Array(String))
)
ENGINE = MergeTree
ORDER BY update_date
Снова можно вставить данные в формате JSON:
INSERT INTO arxiv FORMAT JSONEachRow 
{"id":"2101.11408","submitter":"Daniel Lemire","authors":"Daniel Lemire","title":"Number Parsing at a Gigabyte per Second","comments":"Software at https://github.com/fastfloat/fast_float and\n  https://github.com/lemire/simple_fastfloat_benchmark/","journal-ref":"Software: Practice and Experience 51 (8), 2021","doi":"10.1002/spe.2984","report-no":null,"categories":"cs.DS cs.MS","license":"http://creativecommons.org/licenses/by/4.0/","abstract":"With disks and networks providing gigabytes per second ....\n","versions":[{"created":"Mon, 11 Jan 2021 20:31:27 GMT","version":"v1"},{"created":"Sat, 30 Jan 2021 23:57:29 GMT","version":"v2"}],"update_date":"2022-11-07","authors_parsed":[["Lemire","Daniel",""]]}
Предположим, добавлен ещё один столбец с именем tags. Если бы это был просто список строк, мы могли бы представить его как Array(String), но давайте предположим, что можно добавлять произвольные структуры тегов со смешанными типами (обратите внимание, score может быть строкой или целым числом). Наш изменённый JSON-документ:
{
 "id": "2101.11408",
 "submitter": "Daniel Lemire",
 "authors": "Daniel Lemire",
 "title": "Number Parsing at a Gigabyte per Second",
 "comments": "Software at https://github.com/fastfloat/fast_float and\n  https://github.com/lemire/simple_fastfloat_benchmark/",
 "journal-ref": "Software: Practice and Experience 51 (8), 2021",
 "doi": "10.1002/spe.2984",
 "report-no": null,
 "categories": "cs.DS cs.MS",
 "license": "http://creativecommons.org/licenses/by/4.0/",
 "abstract": "With disks and networks providing gigabytes per second ....\n",
 "versions": [
 {
   "created": "Mon, 11 Jan 2021 20:31:27 GMT",
   "version": "v1"
 },
 {
   "created": "Sat, 30 Jan 2021 23:57:29 GMT",
   "version": "v2"
 }
 ],
 "update_date": "2022-11-07",
 "authors_parsed": [
 [
   "Lemire",
   "Daniel",
   ""
 ]
 ],
 "tags": {
   "tag_1": {
     "name": "ClickHouse user",
     "score": "A+",
     "comment": "A good read, applicable to ClickHouse"
   },
   "28_03_2025": {
     "name": "professor X",
     "score": 10,
     "comment": "Didn't learn much",
     "updates": [
       {
         "name": "professor X",
         "comment": "Wolverine found more interesting"
       }
     ]
   }
 }
}
В этом случае документы arXiv можно было бы смоделировать либо целиком в формате JSON, либо просто добавить столбец tags типа JSON. Ниже приведены оба примера:
CREATE TABLE arxiv
(
  `doc` JSON(update_date Date)
)
ENGINE = MergeTree
ORDER BY doc.update_date
Мы указываем подсказку типа для столбца update_date в определении JSON, так как используем его в ключе сортировки/первичном ключе. Это помогает ClickHouse понять, что этот столбец не может быть null, и определить, какой подстолбец update_date использовать (для каждого типа их может быть несколько, поэтому иначе возникает неоднозначность).
Мы можем выполнить вставку в эту таблицу и просмотреть затем автоматически выведенную схему с помощью функции JSONAllPathsWithTypes и output format PrettyJSONEachRow:
INSERT INTO arxiv FORMAT JSONAsObject 
{"id":"2101.11408","submitter":"Daniel Lemire","authors":"Daniel Lemire","title":"Number Parsing at a Gigabyte per Second","comments":"Software at https://github.com/fastfloat/fast_float and\n  https://github.com/lemire/simple_fastfloat_benchmark/","journal-ref":"Software: Practice and Experience 51 (8), 2021","doi":"10.1002/spe.2984","report-no":null,"categories":"cs.DS cs.MS","license":"http://creativecommons.org/licenses/by/4.0/","abstract":"With disks and networks providing gigabytes per second ....\n","versions":[{"created":"Mon, 11 Jan 2021 20:31:27 GMT","version":"v1"},{"created":"Sat, 30 Jan 2021 23:57:29 GMT","version":"v2"}],"update_date":"2022-11-07","authors_parsed":[["Lemire","Daniel",""]],"tags":{"tag_1":{"name":"ClickHouse user","score":"A+","comment":"A good read, applicable to ClickHouse"},"28_03_2025":{"name":"professor X","score":10,"comment":"Didn't learn much","updates":[{"name":"professor X","comment":"Wolverine found more interesting"}]}}}
SELECT JSONAllPathsWithTypes(doc)
FROM arxiv
FORMAT PrettyJSONEachRow

{
  "JSONAllPathsWithTypes(doc)": {
    "abstract": "String",
    "authors": "String",
    "authors_parsed": "Array(Array(Nullable(String)))",
    "categories": "String",
    "comments": "String",
    "doi": "String",
    "id": "String",
    "journal-ref": "String",
    "license": "String",
    "submitter": "String",
    "tags.28_03_2025.comment": "String",
    "tags.28_03_2025.name": "String",
    "tags.28_03_2025.score": "Int64",
    "tags.28_03_2025.updates": "Array(JSON(max_dynamic_types=16, max_dynamic_paths=256))",
    "tags.tag_1.comment": "String",
    "tags.tag_1.name": "String",
    "tags.tag_1.score": "String",
    "title": "String",
    "update_date": "Date",
    "versions": "Array(JSON(max_dynamic_types=16, max_dynamic_paths=256))"
  }
}

1 row in set. Elapsed: 0.003 sec.
В качестве альтернативы это можно смоделировать, используя описанную ранее схему и JSON-столбец tags. Обычно этот вариант предпочтителен, поскольку сводит к минимуму объём автоматически определяемых ClickHouse данных:
CREATE TABLE arxiv
(
    `id` String,
    `submitter` String,
    `authors` String,
    `title` String,
    `comments` String,
    `journal-ref` String,
    `doi` String,
    `report-no` String,
    `categories` String,
    `license` String,
    `abstract` String,
    `versions` Array(Tuple(created String, version String)),
    `update_date` Date,
    `authors_parsed` Array(Array(String)),
    `tags` JSON()
)
ENGINE = MergeTree
ORDER BY update_date
INSERT INTO arxiv FORMAT JSONEachRow 
{"id":"2101.11408","submitter":"Daniel Lemire","authors":"Daniel Lemire","title":"Number Parsing at a Gigabyte per Second","comments":"Software at https://github.com/fastfloat/fast_float and\n  https://github.com/lemire/simple_fastfloat_benchmark/","journal-ref":"Software: Practice and Experience 51 (8), 2021","doi":"10.1002/spe.2984","report-no":null,"categories":"cs.DS cs.MS","license":"http://creativecommons.org/licenses/by/4.0/","abstract":"With disks and networks providing gigabytes per second ....\n","versions":[{"created":"Mon, 11 Jan 2021 20:31:27 GMT","version":"v1"},{"created":"Sat, 30 Jan 2021 23:57:29 GMT","version":"v2"}],"update_date":"2022-11-07","authors_parsed":[["Lemire","Daniel",""]],"tags":{"tag_1":{"name":"ClickHouse user","score":"A+","comment":"A good read, applicable to ClickHouse"},"28_03_2025":{"name":"professor X","score":10,"comment":"Didn't learn much","updates":[{"name":"professor X","comment":"Wolverine found more interesting"}]}}}
Теперь мы можем вывести типы подстолбца tags.
SELECT JSONAllPathsWithTypes(tags)
FROM arxiv
FORMAT PrettyJSONEachRow

{
  "JSONAllPathsWithTypes(tags)": {
    "28_03_2025.comment": "String",
    "28_03_2025.name": "String",
    "28_03_2025.score": "Int64",
    "28_03_2025.updates": "Array(JSON(max_dynamic_types=16, max_dynamic_paths=256))",
    "tag_1.comment": "String",
    "tag_1.name": "String",
    "tag_1.score": "String"
  }
}
1 row in set. Elapsed: 0.002 sec.
Последнее изменение 10 июня 2026 г.