Статический и динамический JSON
- Примитивные типы - Если значение ключа имеет примитивный тип, независимо от того, находится ли оно во вложенном объекте или в корне, убедитесь, что выбираете его тип в соответствии с общими рекомендациями по проектированию схемы и правилами оптимизации типов. Массивы примитивов, такие как
phone_numbersниже, можно моделировать какArray(<type>), напримерArray(String). - Статический или динамический - Если значение ключа представляет собой сложный объект, то есть объект или массив объектов, определите, может ли оно меняться. Объекты, в которых новые ключи появляются редко, а добавление нового ключа можно предвидеть и обработать изменением схемы через
ALTER TABLE ADD COLUMN, можно считать статическими. Сюда относятся и объекты, в которых в некоторых JSON-документах может присутствовать только часть ключей. Объекты, в которые новые ключи добавляются часто и/или непредсказуемо, следует считать динамическими. Исключение составляют структуры с сотнями или тысячами вложенных ключей — для удобства их тоже можно считать динамическими.
- Корневые ключи
name,username,email,websiteможно представить типомString. Столбецphone_numbers— это примитив Array типаArray(String), аdobиidимеют типыDateиUInt32соответственно. - В объект
addressновые ключи добавляться не будут (только новые объекты адресов), поэтому его можно считать статическим. Если рекурсивно развернуть его, все подстолбцы можно считать примитивами (и типаString), кромеgeo. Это также статическая структура с двумя столбцами типаFloat32:latиlon. - Столбец
tagsявляется динамическим. Мы предполагаем, что в этот объект можно добавлять новые произвольные теги любого типа и структуры. - Объект
companyявляется статическим и всегда будет содержать не более 3 указанных ключей. Вложенные ключиnameиcatchPhraseимеют типString. Ключlabelsявляется динамическим. Мы предполагаем, что в этот объект можно добавлять новые произвольные теги. Значения всегда будут представлять собой пары ключ-значение строкового типа.
Структуры с сотнями или тысячами статических ключей можно считать динамическими, поскольку на практике для них редко удается статически объявить столбцы. Однако там, где это возможно, пропускайте ненужные пути, чтобы сократить затраты как на хранение, так и на определение типов.
Обработка статических структур
Tuple. Массивы объектов можно хранить с помощью массивов кортежей, то есть Array(Tuple). В самих кортежах столбцы и соответствующие им типы следует определять по тем же правилам. В результате могут получаться вложенные Tuple для представления вложенных объектов, как показано ниже.
Чтобы проиллюстрировать это, мы используем приведённый ранее JSON-пример с person, опуская динамические объекты:
company имеет тип Tuple(catchPhrase String, name String). Для ключа address используется Array(Tuple) с вложенным Tuple для представления столбца geo.
JSON можно вставлять в эту таблицу в её текущей структуре:
address.street возвращается как Array. Чтобы обратиться к конкретному объекту внутри массива по его позиции, после имени столбца нужно указать смещение в массиве. Например, чтобы получить улицу из первого адреса:
24.12:
Обработка значений по умолчанию
Tuple не требует наличия всех столбцов в полезной нагрузке JSON. Если какие-то из них не указаны, будут использованы значения по умолчанию.
Рассмотрим таблицу people из предыдущего примера и следующий разреженный JSON, в котором отсутствуют ключи suite, geo, phone_numbers и catchPhrase.
Различие между пустым значением и nullЕсли вам нужно различать пустое значение и отсутствие значения, можно использовать тип Nullable. Однако этого следует избегать, если только это не является абсолютно необходимым, поскольку это отрицательно скажется на хранении и производительности запросов для этих столбцов.
Обработка новых столбцов
nickname:
nickname:
ALTER TABLE ADD COLUMN. Значение по умолчанию можно задать с помощью конструкции DEFAULT; оно будет использоваться, если его не указать при последующих вставках. Для строк, в которых это значение отсутствует (поскольку они были вставлены до его создания), также будет возвращаться это значение по умолчанию. Если значение DEFAULT не указано, будет использоваться значение по умолчанию для этого типа.
Например:
Обработка полуструктурированных/динамических структур
JSON.
Точнее, используйте тип JSON, если ваши данные:
- Содержат непредсказуемые ключи, которые со временем могут меняться.
- Содержат значения разных типов (например, путь иногда может содержать строку, а иногда число).
- Требуют гибкости схемы, когда строгая типизация непрактична.
- У вас есть сотни или даже тысячи путей, которые статичны, но явно объявлять их просто нереалистично. Это, как правило, встречается редко.
company.labels был определён как динамический.
Предположим, что company.labels содержит произвольные ключи. Кроме того, тип значения любого ключа в этой структуре может различаться от строки к строке. Например:
company.labels в разных объектах с точки зрения ключей и типов, у нас есть несколько вариантов моделирования этих данных:
- Один JSON-столбец - представляет всю схему в виде одного столбца
JSON, позволяя всем вложенным структурам оставаться динамическими. - Целевой JSON-столбец - использовать тип
JSONтолько для столбцаcompany.labels, сохраняя для всех остальных столбцов структурированную схему, описанную выше.
- Валидация данных – использование строгой схемы позволяет избежать риска разрастания столбцов за пределами явно заданных структур.
- Позволяет избежать риска разрастания столбцов - Хотя тип JSON может масштабироваться до тысяч столбцов, где подстолбцы хранятся как отдельные столбцы, это может привести к лавинообразному росту числа файлов столбцов, что негативно сказывается на производительности. Чтобы уменьшить этот риск, базовый тип Dynamic, используемый в JSON, поддерживает параметр
max_dynamic_paths, который ограничивает число уникальных путей, сохраняемых как отдельные файлы столбцов. После достижения этого порога дополнительные пути сохраняются в общем файле столбца в компактном закодированном формате, что позволяет сохранить производительность и эффективность хранения, одновременно поддерживая гибкую ингестию данных. Однако доступ к этому общему файлу столбца менее производителен. При этом JSON-столбец можно использовать с подсказками типов. Столбцы с подсказками обеспечивают ту же производительность, что и отдельные столбцы. - Более простая интроспекция путей и типов - Хотя тип JSON поддерживает функции интроспекции для определения выведенных типов и путей, статические структуры бывает проще исследовать, например с помощью
DESCRIBE.
Один JSON-столбец
JSON только для динамических вложенных структур там, где это действительно необходимо.
Соображения по производительностиОдин JSON-столбец можно оптимизировать, пропуская (не сохраняя) JSON-пути, которые не нужны, и используя подсказки типов. Подсказки типов позволяют пользователю явно задавать тип для подстолбца, тем самым избегая вывода типов и дополнительной косвенной обработки во время выполнения запроса. Это позволяет добиться той же производительности, как если бы использовалась явная схема. Подробнее см. в разделе “Использование подсказок типов и пропуск путей”.
Мы указываем подсказку типа для столбца
username в определении JSON, поскольку используем его в ключе сортировки/первичном ключе. Это помогает ClickHouse понять, что этот столбец не будет содержать NULL, и определить, какой подстолбец username следует использовать (для каждого типа их может быть несколько, поэтому без этого возникает неоднозначность).JSONAsObject:
.; например.
NULL.
Кроме того, для путей с одним и тем же типом создаётся отдельный подстолбец. Например, для company.labels.type существует подстолбец как типа String, так и типа Array(Nullable(String)). По возможности будут возвращаться оба, но с помощью синтаксиса .: можно обращаться к конкретным подстолбцам:
^. Это сделано намеренно, чтобы избежать чтения большого количества столбцов без явного запроса. При обращении к объектам без ^ будет возвращаться NULL, как показано ниже:
Целевой JSON-столбец
JSON для столбца company.labels.
JSONEachRow:
company.labels.
Использование подсказок типов и пропускаемых путей
dissolved, employees и founded в JSON-столбце company.labels
SKIP and SKIP REGEXP, чтобы сократить объём хранилища и избежать лишнего вывода типов для ненужных путей. Например, предположим, что для приведённых выше данных используется один JSON-столбец. Мы можем пропустить пути address и company:”
Оптимизация производительности с помощью подсказок типов
Настройка динамических путей
max_dynamic_paths в объявлении типа JSON.
SKIP, чтобы ограничить объём сохраняемых данных.
Пользователям, которым интересна реализация этого нового типа столбца, рекомендуем прочитать нашу подробную статью в блоге “A New Powerful JSON Data Type for ClickHouse”.