Запрос данных с помощью ClickHouse Connect: расширенные возможности
QueryContexts
ClickHouse Connect выполняет стандартные запросы в контексте QueryContext. QueryContext содержит ключевые структуры, которые используются для построения запросов к базе данных ClickHouse, а также конфигурацию, применяемую для обработки результата в QueryResult или другую структуру данных ответа. Сюда входят сам запрос, параметры, настройки, форматы чтения и прочие свойства.
QueryContext можно получить с помощью клиентского метода create_query_context. Этот метод принимает те же параметры, что и основной метод выполнения запроса. Затем этот контекст запроса может быть передан методам query, query_df или query_np в качестве именованного аргумента context вместо любого или всех остальных аргументов этих методов. Обратите внимание, что дополнительные аргументы, указанные при вызове метода, переопределят любые свойства QueryContext.
Наиболее наглядный сценарий использования QueryContext — отправка одного и того же запроса с разными значениями параметров привязки. Все значения параметров можно обновить, вызвав метод QueryContext.set_parameters со словарём, или обновить любое отдельное значение, вызвав QueryContext.set_parameter с нужной парой key, value.
Обратите внимание, что экземпляры QueryContext не являются потокобезопасными, однако в многопоточной среде можно получить их копию, вызвав метод QueryContext.updated_copy.
Потоковые запросы
Клиент ClickHouse Connect предоставляет несколько методов для получения данных в виде потока (реализовано как генератор Python):
query_column_block_stream-- Возвращает данные запроса по блокам в виде последовательности столбцов с использованием нативных объектов Pythonquery_row_block_stream-- Возвращает данные запроса в виде блока строк с использованием нативных объектов Pythonquery_rows_stream-- Возвращает данные запроса в виде последовательности строк с использованием нативных объектов Pythonquery_np_stream-- Возвращает каждый блок данных запроса ClickHouse как массив NumPyquery_df_stream-- Возвращает каждый блок данных запроса ClickHouse как объект Pandas DataFramequery_arrow_stream-- Возвращает данные запроса в виде PyArrow RecordBlocksquery_df_arrow_stream-- Возвращает каждый блок данных запроса ClickHouse как основанный на Arrow объект Pandas DataFrame или Polars DataFrame в зависимости от значения аргументаdataframe_library(по умолчанию "pandas").
Каждый из этих методов возвращает объект ContextStream, который необходимо открыть с помощью конструкции with, чтобы начать чтение потока.
Блоки данных
ClickHouse Connect обрабатывает все данные из основного метода query как поток блоков, получаемых от сервера ClickHouse. Эти блоки передаются в собственном формате "Native" в ClickHouse и из него. «Блок» — это просто последовательность столбцов бинарных данных, где каждый столбец содержит одинаковое количество значений указанного типа данных. (Как колонночная СУБД, ClickHouse хранит эти данные в аналогичной форме.) Размер блока, возвращаемого из запроса, определяется двумя пользовательскими параметрами, которые могут быть заданы на нескольких уровнях (профиль пользователя, пользователь, сессия или запрос). Это:
- max_block_size — ограничение на размер блока в строках. Значение по умолчанию — 65536.
- preferred_block_size_bytes — мягкое ограничение на размер блока в байтах. Значение по умолчанию — 1,000,0000.
Независимо от preferred_block_size_setting, каждый блок никогда не будет содержать более чем max_block_size строк. В зависимости от типа запроса фактические возвращаемые блоки могут иметь произвольный размер. Например, запросы к распределённой таблице, охватывающей множество шардов, могут содержать более мелкие блоки, полученные непосредственно с каждого шарда.
При использовании одного из клиентских методов query_*_stream результаты возвращаются по одному блоку за раз. ClickHouse Connect загружает только один блок одновременно. Это позволяет обрабатывать большие объёмы данных без необходимости загружать весь большой набор результатов в память. Обратите внимание, что приложение должно быть готово обрабатывать произвольное количество блоков, а точный размер каждого блока не может быть задан или жёстко контролируем.
Буфер HTTP-данных при медленной обработке
Из‑за ограничений протокола HTTP, если блоки обрабатываются значительно медленнее, чем сервер ClickHouse передаёт данные, сервер ClickHouse закроет соединение, что приведёт к выбрасыванию исключения в потоке обработки. Частично это можно компенсировать, увеличив размер буфера HTTP‑потоковой передачи (по умолчанию 10 мегабайт) с помощью общего параметра http_buffer_size. Большие значения http_buffer_size допустимы в этой ситуации, если приложению доступно достаточно памяти. Данные в буфере хранятся в сжатом виде при использовании сжатия lz4 или zstd, поэтому применение этих типов сжатия увеличивает общий доступный размер буфера.
StreamContexts
Каждый из методов query_*_stream (например, query_row_block_stream) возвращает объект StreamContext ClickHouse, который представляет собой комбинированный контекстный менеджер/генератор в Python. Типичное использование:
Обратите внимание, что попытка использовать StreamContext без оператора with приведёт к ошибке. Использование контекстного менеджера Python гарантирует, что поток (в данном случае потоковый HTTP‑ответ) будет корректно закрыт, даже если не все данные были прочитаны и/или во время обработки было выброшено исключение. Также StreamContext может быть использован только один раз для чтения потока. Попытка использовать StreamContext после выхода из него приведёт к StreamClosedError.
Вы можете использовать свойство source у StreamContext для доступа к родительскому объекту QueryResult, содержащему имена и типы столбцов.
Типы потоков
Метод query_column_block_stream возвращает блок в виде последовательности данных столбцов, сохранённых в нативных типах данных Python. Используя приведённые выше запросы к taxi_trips, возвращаемые данные будут списком, каждый элемент которого — это другой список (или кортеж), содержащий все данные для соответствующего столбца. Таким образом, block[0] будет кортежем, содержащим только строки. Форматы, ориентированные на столбцы, чаще всего используются для выполнения агрегатных операций по всем значениям в столбце, например для суммирования общей стоимости поездок.
Метод query_row_block_stream возвращает блок в виде последовательности строк, как в традиционной реляционной базе данных. Для поездок на такси возвращаемые данные будут списком, каждый элемент которого — это другой список, представляющий строку данных. Таким образом, block[0] будет содержать все поля (в нужном порядке) для первой поездки на такси, block[1] будет содержать строку со всеми полями для второй поездки на такси и так далее. Результаты, ориентированные на строки, обычно используются для отображения или преобразования данных.
query_row_stream — это вспомогательный метод, который при итерации по потоку автоматически переходит к следующему блоку. В остальном он идентичен query_row_block_stream.
Метод query_np_stream возвращает каждый блок в виде двумерного массива NumPy. Внутренне массивы NumPy (обычно) хранятся по столбцам, поэтому отдельные методы для строк и столбцов не требуются. «Форма» (shape) массива NumPy будет выражаться как (столбцы, строки). Библиотека NumPy предоставляет множество методов для работы с массивами NumPy. Обратите внимание, что если все столбцы в запросе имеют один и тот же dtype NumPy, возвращаемый массив NumPy также будет иметь только один dtype и может быть изменён по форме/повёрнут без фактического изменения его внутренней структуры.
Метод query_df_stream возвращает каждый блок ClickHouse в виде двумерного DataFrame библиотеки Pandas. Вот пример, показывающий, что объект StreamContext может использоваться как контекст в отложенном режиме (но только один раз).
Метод query_df_arrow_stream возвращает каждый блок ClickHouse в виде DataFrame с бэкендом типов данных PyArrow. Этот метод поддерживает как Pandas (версии 2.x и выше), так и Polars DataFrame через параметр dataframe_library (по умолчанию "pandas"). Каждая итерация возвращает DataFrame, полученный из пакетов записей (record batches) PyArrow, что обеспечивает более высокую производительность и эффективное использование памяти для отдельных типов данных.
Наконец, метод query_arrow_stream возвращает результат в формате ClickHouse ArrowStream как объект pyarrow.ipc.RecordBatchStreamReader, обёрнутый в StreamContext. Каждая итерация потока возвращает PyArrow RecordBlock.
Примеры потоковой обработки
Потоковая передача строк
Потоковая передача блоков строк
Потоковая передача DataFrame из Pandas
Потоковая обработка пакетов Arrow
Запросы с использованием NumPy, Pandas и Arrow
ClickHouse Connect предоставляет специализированные методы выполнения запросов для работы со структурами данных NumPy, Pandas и Arrow. Эти методы позволяют получать результаты запросов непосредственно в этих популярных форматах данных без ручного преобразования.
Запросы NumPy
Метод query_np возвращает результаты запроса как массив NumPy вместо объекта QueryResult ClickHouse Connect.
Запросы Pandas
Метод query_df возвращает результаты запроса в виде объекта DataFrame библиотеки Pandas вместо QueryResult из ClickHouse Connect.
Запросы PyArrow
Метод query_arrow возвращает результаты запроса в виде таблицы PyArrow. Он напрямую использует формат ClickHouse Arrow, поэтому принимает только три аргумента, общих с основным методом query: query, parameters и settings. Кроме того, предусмотрен дополнительный аргумент use_strings, который определяет, будут ли типы ClickHouse String в таблице Arrow представляться как строки (если True) или как байты (если False).
DataFrame на базе Arrow
ClickHouse Connect поддерживает быстрое и экономное по памяти создание DataFrame из результатов Arrow с помощью методов query_df_arrow и query_df_arrow_stream. Эти методы являются тонкими обёртками вокруг методов выполнения запросов с использованием Arrow и выполняют преобразование в DataFrame без копирования данных, где это возможно:
query_df_arrow: выполняет запрос, используя формат вывода ClickHouseArrow, и возвращает DataFrame.- Для
dataframe_library='pandas'возвращает DataFrame pandas 2.x с типами данных на базе Arrow (pd.ArrowDtype). Требуется pandas 2.x; при этом по возможности используются буферы без копирования данных, что обеспечивает высокую производительность и низкие накладные расходы по памяти. - Для
dataframe_library='polars'возвращает Polars DataFrame, созданный из таблицы Arrow (pl.from_arrow), который столь же эффективен и может работать без копирования данных в зависимости от набора данных.
- Для
query_df_arrow_stream: отдаёт результаты потоком в виде последовательности DataFrame (pandas 2.x или Polars), преобразованных из пакетов потокового формата Arrow.
Запрос к DataFrame на основе Arrow
Примечания и особенности
- Отображение типов Arrow: при возврате данных в формате Arrow ClickHouse сопоставляет свои типы с ближайшими поддерживаемыми типами Arrow. Некоторые типы ClickHouse не имеют нативного аналога в Arrow и возвращаются как «сырые» байты в полях Arrow (обычно
BINARYилиFIXED_SIZE_BINARY).- Примеры:
IPv4представляется как ArrowUINT32;IPv6и большие целые (Int128/UInt128/Int256/UInt256) часто представляются какFIXED_SIZE_BINARY/BINARYс «сырыми» байтами. - В таких случаях столбец DataFrame будет содержать значения байтов, хранящиеся в соответствующем поле Arrow; интерпретация и преобразование этих байтов в соответствии с семантикой ClickHouse возлагается на клиентский код.
- Примеры:
- Неподдерживаемые типы данных Arrow (например, UUID/ENUM как полноценные типы Arrow) не формируются в выводе; значения представляются с использованием ближайшего поддерживаемого типа Arrow (часто в виде бинарных данных).
- Требования к pandas: типы данных на основе Arrow требуют pandas 2.x. Для более старых версий pandas используйте
query_df(без Arrow). - Строки vs бинарные данные: опция
use_strings(когда она поддерживается серверной настройкойoutput_format_arrow_string_as_string) управляет тем, возвращаются ли столбцы ClickHouseStringкак строковые типы Arrow или как бинарные.
Примеры преобразования несовпадающих типов ClickHouse/Arrow
Когда ClickHouse возвращает столбцы в виде сырых бинарных данных (например, FIXED_SIZE_BINARY или BINARY), ответственность за преобразование этих байтов в соответствующие типы Python лежит на коде приложения. Примеры ниже показывают, что некоторые преобразования можно выполнить с помощью API библиотеки DataFrame, тогда как другие могут потребовать использования «чистого» Python, например struct.unpack (что снижает производительность, но сохраняет гибкость).
Столбцы Date могут приходить как UINT16 (количество дней с начала эпохи Unix, 1970‑01‑01). Преобразование внутри DataFrame эффективно и несложно:
Столбцы типа Int128 могут поступать в виде FIXED_SIZE_BINARY с сырыми байтами. Polars предоставляет встроенную поддержку 128-битных целых чисел:
Начиная с NumPy 2.3 нет общедоступного 128-битного целочисленного типа dtype, поэтому приходится использовать чистый Python и, например, сделать что-то вроде этого:
Основной вывод: прикладной код должен выполнять эти преобразования в зависимости от возможностей выбранной библиотеки DataFrame и приемлемых компромиссов по производительности. Когда нативные для DataFrame преобразования недоступны, вариант с использованием чистого Python по‑прежнему остается рабочим.
Форматы чтения
Форматы чтения управляют типами данных значений, возвращаемых методами клиента query, query_np и query_df. (raw_query и query_arrow не изменяют данные, получаемые из ClickHouse, поэтому управление форматом к ним не применяется.) Например, если формат чтения для UUID изменён с формата по умолчанию native на альтернативный формат string, запрос ClickHouse к столбцу UUID будет возвращать строковые значения (в стандартном формате RFC 1422 8-4-4-4-12), а не объекты Python UUID.
Аргумент data type для любой функции форматирования может включать подстановочные символы (wildcards). Формат задаётся одной строкой в нижнем регистре.
Форматы чтения могут быть заданы на нескольких уровнях:
- Глобально — с использованием методов, определённых в пакете
clickhouse_connect.datatypes.format. Это будет определять формат настроенного типа данных для всех запросов.
- Для всего запроса целиком, используя необязательный аргумент-словарь
query_formats. В этом случае любой столбец (или подстолбец) указанных типов данных будет использовать настроенный формат.
- Для значений в определённом столбце, используя необязательный аргумент-словарь
column_formats. Ключом является имя столбца, возвращаемое ClickHouse, а значением — либо формат для данных этого столбца, либо вложенный словарь второго уровняformat, где ключом является имя типа ClickHouse, а значением — формат(ы) запроса. Этот дополнительный словарь можно использовать для вложенных типов столбцов, таких как Tuples или Maps.
Параметры формата чтения (типы Python)
| Тип ClickHouse | Базовый тип Python | Форматы чтения | Комментарии |
|---|---|---|---|
| Int[8-64], UInt[8-32] | int | - | |
| UInt64 | int | signed | Superset в настоящее время не обрабатывает большие беззнаковые значения UInt64 |
| [U]Int[128,256] | int | string | Значения типов int в Pandas и NumPy имеют максимальный размер 64 бита, поэтому такие значения могут возвращаться как строки |
| BFloat16 | float | - | Все значения float в Python внутренне являются 64-битными |
| Float32 | float | - | Все значения float в Python внутренне являются 64-битными |
| Float64 | float | - | |
| Decimal | decimal.Decimal | - | |
| String | string | bytes | Столбцы типа String в ClickHouse не имеют встроенной кодировки, поэтому также используются для двоичных данных переменной длины |
| FixedString | bytes | string | FixedString — это массивы байт фиксированного размера, но иногда обрабатываются как строки Python |
| Enum[8,16] | string | string, int | Перечисления (enum) в Python не принимают пустые строки, поэтому все enum возвращаются либо в виде строк, либо как базовое значение int |
| Date | datetime.date | int | ClickHouse хранит Date как количество дней с 01/01/1970. Это значение доступно как int |
| Date32 | datetime.date | int | То же, что и Date, но для более широкого диапазона дат |
| DateTime | datetime.datetime | int | ClickHouse хранит DateTime в секундах Unix-эпохи. Это значение доступно как int |
| DateTime64 | datetime.datetime | int | datetime.datetime в Python ограничен микросекундной точностью. Доступно исходное 64-битное целочисленное значение |
| Time | datetime.timedelta | int, string, time | Момент времени сохраняется как Unix timestamp. Это значение доступно как int |
| Time64 | datetime.timedelta | int, string, time | datetime.timedelta в Python ограничен микросекундной точностью. Доступно исходное 64-битное целочисленное значение |
| IPv4 | ipaddress.IPv4Address | string | IP-адреса могут читаться как строки, а корректно отформатированные строки могут вставляться как IP-адреса |
| IPv6 | ipaddress.IPv6Address | string | IP-адреса могут читаться как строки, а корректно отформатированные строки могут вставляться как IP-адреса |
| Tuple | dict or tuple | tuple, json | Именованные кортежи по умолчанию возвращаются как словари. Именованные кортежи также могут возвращаться как JSON-строки |
| Map | dict | - | |
| Nested | Sequence[dict] | - | |
| UUID | uuid.UUID | string | UUID можно читать как строки, форматированные в соответствии с RFC 4122 |
| JSON | dict | string | По умолчанию возвращается словарь Python. Формат string вернёт JSON-строку |
| Variant | object | - | Возвращает соответствующий тип Python для типа данных ClickHouse, хранящегося в значении |
| Dynamic | object | - | Возвращает соответствующий тип Python для типа данных ClickHouse, хранящегося в значении |
Внешние данные
Запросы ClickHouse могут принимать внешние данные в любом формате ClickHouse. Эти двоичные данные отправляются вместе со строкой запроса и используются для обработки данных. Подробности о функции External Data приведены здесь. Методы клиента query* принимают необязательный параметр external_data, чтобы использовать эту возможность. Значением параметра external_data должен быть объект clickhouse_connect.driver.external.ExternalData. Конструктор этого объекта принимает следующие аргументы:
| Name | Type | Description |
|---|---|---|
| file_path | str | Путь к файлу в локальной файловой системе, из которого будут прочитаны внешние данные. Требуется либо file_path, либо data |
| file_name | str | Имя внешнего файла данных. Если не указано, будет определено из file_path (без расширения) |
| data | bytes | Внешние данные в двоичном виде (вместо чтения из файла). Требуется либо data, либо file_path |
| fmt | str | Входной формат данных ClickHouse. По умолчанию TSV |
| types | str or seq of str | Список типов данных столбцов во внешних данных. Если указана строка, типы должны быть разделены запятыми. Требуется либо types, либо structure |
| structure | str or seq of str | Список «имя столбца + тип данных» в данных (см. примеры). Требуется либо structure, либо types |
| mime_type | str | Необязательный MIME-тип данных файла. В настоящее время ClickHouse игнорирует этот подзаголовок HTTP |
Чтобы отправить запрос с внешним CSV-файлом, содержащим данные о фильмах, и объединить эти данные с таблицей directors, уже присутствующей на сервере ClickHouse:
К исходному объекту ExternalData можно добавить дополнительные внешние файлы данных с помощью метода add_file, который принимает те же параметры, что и конструктор. При использовании HTTP все внешние данные передаются как часть загрузки файла в формате multipart/form-data.
Часовые пояса
Существует несколько механизмов применения часового пояса к значениям ClickHouse DateTime и DateTime64. Внутри сервера ClickHouse любой объект DateTime или DateTime64 всегда хранится как «наивное» числовое значение (без учёта часового пояса), представляющее количество секунд, прошедших с начала эпохи — 1970-01-01 00:00:00 по времени UTC. Для значений DateTime64 представление может быть в миллисекундах, микросекундах или наносекундах, прошедших с начала эпохи, в зависимости от точности. В результате любое применение информации о часовом поясе всегда выполняется на стороне клиента. Обратите внимание, что это требует дополнительных вычислений, поэтому в приложениях, критичных к производительности, рекомендуется обрабатывать типы DateTime как метки времени эпохи (epoch timestamps), за исключением пользовательского отображения и конвертации (например, объекты Pandas Timestamps всегда являются 64-битным целым числом, представляющим количество наносекунд с начала эпохи, для повышения производительности).
При использовании зависящих от часового пояса типов данных в запросах — в частности, объекта Python datetime.datetime — clickhouse-connect применяет часовой пояс на стороне клиента, используя следующие правила приоритета:
- Если для метода запроса указан параметр
client_tzs, применяется указанный для столбца часовой пояс. - Если столбец ClickHouse имеет метаданные часового пояса (т. е. его тип — такой как
DateTime64(3, 'America/Denver')), применяется часовой пояс столбца ClickHouse. (Обратите внимание, что эти метаданные часового пояса недоступныclickhouse-connectдля столбцов типа DateTime в версиях ClickHouse до 23.2.) - Если для метода запроса указан параметр
query_tz, применяется «часовой пояс запроса». - Если к запросу или сессии применена настройка часового пояса, применяется этот часовой пояс. (Эта функциональность ещё не реализована в сервере ClickHouse.)
- Наконец, если для клиентского параметра
apply_server_timezoneустановлено значение True (по умолчанию), применяется часовой пояс сервера ClickHouse.
Обратите внимание, что если применённый в соответствии с этими правилами часовой пояс — UTC, clickhouse-connect всегда вернёт наивный (без часового пояса) объект Python datetime.datetime. При необходимости дополнительную информацию о часовом поясе затем можно добавить к этому объекту в коде приложения.