QueryContexts
QueryContext 中执行标准查询。QueryContext 包含用于针对 ClickHouse 数据库构建查询的关键结构,以及用于将结果处理为 QueryResult 或其他响应数据结构的配置。其中包括查询本身、参数、settings、读取格式以及其他属性。
可以使用客户端的 create_query_context 方法获取 QueryContext。此方法接受与核心查询方法相同的参数。随后,可将此查询上下文作为 context 关键字参数传递给 query、query_df 或 query_np 方法,以替代这些方法中的部分或全部其他参数。请注意,在方法调用中额外指定的参数会覆盖 QueryContext 中的相应属性。
QueryContext 最清晰的用例是使用不同的绑定参数值发送同一个查询。调用 QueryContext.set_parameters 方法并传入一个字典,即可更新所有参数值;也可以调用 QueryContext.set_parameter,传入所需的 key、value 对,来更新任意单个值。
QueryContext 不是线程安全的;但在多线程环境中,可以通过调用 QueryContext.updated_copy 方法获取其副本。
流式查询
query_column_block_stream— 使用原生 Python 对象,以列序列形式按块返回查询数据query_row_block_stream— 使用原生 Python 对象,以行块形式返回查询数据query_rows_stream— 使用原生 Python 对象,以行序列形式返回查询数据query_np_stream— 将每个 ClickHouse 查询数据块作为 NumPy array 返回query_df_stream— 将每个 ClickHouse 查询数据块作为 Pandas DataFrame 返回query_arrow_stream— 以 PyArrow RecordBlocks 形式返回查询数据query_df_arrow_stream— 根据关键字参数dataframe_library(默认为 “pandas”) ,将每个 ClickHouse 查询数据块作为基于 Arrow 的 Pandas DataFrame 或 Polars DataFrame 返回。
ContextStream 对象,必须通过 with 语句打开后才能开始消费该流。
数据块
query 方法返回的所有数据,作为从 ClickHouse 服务器 接收的块流进行处理。这些块以自定义的 “Native” 格式在客户端与 ClickHouse 之间传输。“块”本质上就是一系列二进制数据列,其中每一列都包含数量相同、且具有指定数据类型的数据值。 (作为列式数据库,ClickHouse 也以类似的形式存储这些数据。) 查询返回的块大小由两个用户设置控制,这两个设置可在多个级别上设定 (用户 profile、用户、会话或查询) 。它们是:
- max_block_size — 以行为单位限制块大小。默认值为 65536。
- preferred_block_size_bytes — 以字节为单位对块大小设置软限制。默认值为 1,000,0000。
preferred_block_size_setting 如何设置,每个块都不会超过 max_block_size 行。根据查询类型的不同,实际返回的块大小可能各不相同。例如,对覆盖多个分片的分布式表执行查询时,返回结果中可能包含直接从各个分片检索到的较小块。
使用 Client 的 query_*_stream 方法之一时,结果会按块返回。ClickHouse Connect 一次只加载一个块。这样就能在无需将整个大型结果集全部加载到内存中的情况下处理大量数据。请注意,应用程序应能够处理任意数量的块,并且无法精确控制每个块的大小。
慢速处理时的 HTTP 数据缓冲区
http_buffer_size 设置增大 HTTP 流缓冲区大小 (默认为 10 MB) ,可以在一定程度上缓解这一问题。如果应用程序有足够可用内存,那么在这种情况下将 http_buffer_size 设为较大值通常没有问题。如果使用 lz4 或 zstd 压缩,缓冲区中的数据会以压缩形式存储,因此使用这些压缩类型会提高总体可用缓冲区容量。
StreamContexts
query_*_stream 方法 (例如 query_row_block_stream) 都会返回一个 ClickHouse StreamContext 对象,它结合了 Python 的上下文管理器和生成器。基本用法如下:
with 语句就尝试使用 StreamContext,将会引发错误。使用 Python 上下文可以确保 stream (此处指流式 HTTP 响应) 被正确关闭,即使没有消费完所有数据和/或在处理过程中引发了异常也是如此。此外,StreamContext 只能用于消费一次 stream。在 StreamContext 退出后再次尝试使用它,会产生 StreamClosedError。
你可以使用 StreamContext 的 source 属性来访问父 QueryResult 对象,其中包含列名和类型。
Stream 类型
query_column_block_stream 方法将块作为序列返回,其中包含以原生 Python 数据类型存储的列数据。使用上面的 taxi_trips 查询时,返回的数据将是一个列表,其中每个元素都是另一个列表 (或元组) ,包含对应列的全部数据。因此,block[0] 会是一个只包含字符串的元组。面向列的格式最常用于对某一列中的所有值执行聚合操作,例如计算总车费。
query_row_block_stream 方法将块作为行序列返回,类似于传统的关系型数据库。对于出租车行程,返回的数据将是一个列表,其中每个元素都是另一个表示一行数据的列表。因此,block[0] 将包含第一条出租车行程的所有字段 (按顺序) ,block[1] 将包含第二条出租车行程的所有字段,依此类推。面向行的结果通常用于显示或转换处理。
query_row_stream 是一个便捷方法,在遍历 stream 时会自动切换到下一个块。除此之外,它与 query_row_block_stream 完全相同。
query_np_stream 方法将每个块作为二维 NumPy Array 返回。NumPy 数组在内部通常按列存储,因此不需要单独的按行或按列方法。NumPy 数组的“形态”表示为 (columns, rows)。NumPy 库提供了许多操作 NumPy 数组的方法。请注意,如果查询中的所有列具有相同的 NumPy dtype,则返回的 NumPy 数组也只会有一种 dtype,并且可以在不实际改变其内部结构的情况下进行 reshape/rotate。
query_df_stream 方法将每个 ClickHouse 块作为二维 Pandas DataFrame 返回。下面的示例展示了 StreamContext 对象可以以延迟方式用作上下文 (但只能使用一次) 。
query_df_arrow_stream 方法将每个 ClickHouse 块返回为使用 PyArrow dtype 后端的 DataFrame。该方法通过 dataframe_library 参数支持 Pandas (2.x 及以上版本) 和 Polars DataFrame (默认为 "pandas") 。每次迭代都会生成一个由 PyArrow record 批次转换而成的 DataFrame,对于某些数据类型可提供更好的性能和内存效率。
最后,query_arrow_stream 方法会将 ClickHouse ArrowStream 格式的结果返回为封装在 StreamContext 中的 pyarrow.ipc.RecordBatchStreamReader。该流的每次迭代都会返回一个 PyArrow RecordBlock。
流式示例
流式传输行数据
流式传输行数据块
流式传输 Pandas DataFrames
流式传输 Arrow 批次数据
NumPy、Pandas 和 Arrow 查询
NumPy 查询
query_np 方法返回的是 NumPy 数组形式的查询结果,而不是 ClickHouse Connect 的 QueryResult。
Pandas 查询
query_df 方法会将查询结果以 Pandas DataFrame 的形式返回,而不是返回 ClickHouse Connect 的 QueryResult。
PyArrow 查询
query_arrow 方法会以 PyArrow Table 的形式返回查询结果。它直接使用 ClickHouse 的 Arrow format,因此与主要的 query 方法相同且仅接受三个参数:query、parameters 和 settings。此外,还有一个额外参数 use_strings,用于决定 Arrow Table 是将 ClickHouse 的 String 类型呈现为字符串 (如果为 True) ,还是字节 (如果为 False) 。
基于 Arrow 的 DataFrame
query_df_arrow 和 query_df_arrow_stream 方法,基于 Arrow 查询结果快速且节省内存地创建 DataFrame。这些方法是对 Arrow 查询方法的轻量封装,并会在可能的情况下将结果零拷贝转换为 DataFrame:
query_df_arrow:使用 ClickHouse 的Arrow输出格式执行查询,并返回一个 DataFrame。- 对于
dataframe_library='pandas',返回一个使用 Arrow 支持的数据类型 (pd.ArrowDtype) 的 pandas 2.x DataFrame。这要求使用 pandas 2.x,并会在可能的情况下利用零拷贝缓冲区,从而获得出色的性能和较低的内存开销。 - 对于
dataframe_library='polars',返回一个基于 Arrow 表创建的 Polars DataFrame (pl.from_arrow) 。这种方式同样高效,并且是否可实现零拷贝取决于具体数据。
- 对于
query_df_arrow_stream:将结果以一系列 DataFrame (pandas 2.x 或 Polars) 的形式流式返回,这些 DataFrame 由 Arrow 流批次转换而来。
查询到基于 Arrow 的 DataFrame
注意事项与说明
- Arrow 类型映射:以 Arrow format 返回数据时,ClickHouse 会将类型映射到最接近的受支持 Arrow 类型。某些 ClickHouse 类型没有原生的 Arrow 对应类型,因此会在 Arrow field 中以原始字节形式返回 (通常是
BINARY或FIXED_SIZE_BINARY) 。- 示例:
IPv4会表示为 ArrowUINT32;IPv6和大整数 (Int128/UInt128/Int256/UInt256) 通常会表示为包含原始字节的FIXED_SIZE_BINARY/BINARY。 - 在这些情况下,DataFrame 列中包含的是由 Arrow field 提供支持的字节值;这些字节应由客户端代码按照 ClickHouse 语义进行解释或转换。
- 示例:
- 不受支持的 Arrow 数据类型 (例如不能作为真正 Arrow 类型输出的 UUID/ENUM) 不会直接输出;这些值会改用最接近的受支持 Arrow 类型表示 (通常为二进制字节) 以供输出。
- Pandas 要求:Arrow 支持的 dtype 需要 pandas 2.x。对于较旧版本的 pandas,请改用
query_df(非 Arrow) 。 - String 与二进制:
use_strings选项 (在 server settingoutput_format_arrow_string_as_string支持时) 控制 ClickHouseString列是以 Arrow 字符串还是以二进制形式返回。
ClickHouse/Arrow 类型转换不匹配示例
FIXED_SIZE_BINARY 或 BINARY) ,需要由应用程序代码负责将这些字节转换为适当的 Python 类型。下面的示例说明,有些转换可以通过 DataFrame 库的 API 完成,而另一些则可能需要使用 struct.unpack 这类纯 Python 方法 (虽然会牺牲性能,但能保留灵活性) 。
Date 列可能会以 UINT16 形式返回 (表示自 Unix 纪元 1970‑01‑01 起的天数) 。在 DataFrame 内进行转换既高效又简单:
Int128 这样的列可能会以包含原始字节的 FIXED_SIZE_BINARY 形式传入。Polars 原生支持 128 位整数:
读取格式
query、query_np 和 query_df 方法返回值的数据类型。 (raw_query 和 query_arrow 不会修改从 ClickHouse 返回的原始数据,因此格式控制不适用于它们。) 例如,如果将 UUID 的读取格式从默认的 native 格式改为可选的 string 格式,那么查询 UUID 列时,ClickHouse 将返回字符串值 (采用标准的 8-4-4-4-12 RFC 1422 格式) ,而不是 Python UUID 对象。
任何格式化函数的“数据类型”参数都可以包含通配符。格式值是一个全小写字符串。
读取格式可以在多个级别设置:
- 全局设置:使用
clickhouse_connect.datatypes.format包中定义的方法。这会控制所有查询中已配置数据类型的格式。
- 对整个查询,可使用可选的
query_formats字典参数。在这种情况下,所有属于指定数据类型的列 (或子列) 都会使用已配置的格式。
- 对于特定列中的值,可使用可选的
column_formats字典参数。键为 ClickHouse 返回的列名,值可以是该数据列的格式,也可以是第二层的 “format” 字典,其中包含 ClickHouse 类型名称以及对应的查询格式值。这个次级字典可用于 Tuple 或 Map 等嵌套列类型。
读取格式选项 (Python 类型)
| ClickHouse 类型 | 原生 Python 类型 | 读取格式 | 注释 |
|---|---|---|---|
| Int[8-64], UInt[8-32] | int | - | |
| UInt64 | int | signed | Superset 目前还无法处理较大的无符号 UInt64 值 |
| [U]Int[128,256] | int | string | Pandas 和 NumPy 的 int 值最多只有 64 位,因此这些值可以作为字符串返回 |
| BFloat16 | float | - | 所有 Python float 在内部都是 64 位 |
| Float32 | float | - | 所有 Python float 在内部都是 64 位 |
| Float64 | float | - | |
| Decimal | decimal.Decimal | - | |
| String | string | bytes | ClickHouse 的 String 列本身不带编码,因此也可用于存储变长二进制数据 |
| FixedString | bytes | string | FixedString 是定长字节数组,但有时也会被视为 Python 字符串 |
| Enum[8,16] | string | string, int | Python 枚举不接受空字符串,因此所有枚举都会显示为字符串或其底层 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 | Python datetime.datetime 的精度仅限于微秒。可获取原始 64 位 int 值 |
| Time | datetime.timedelta | int, string, time | 时间点以 Unix timestamp 保存。该值也可以作为 int 获取 |
| Time64 | datetime.timedelta | int, string, time | Python datetime.timedelta 的精度仅限于微秒。可获取原始 64 位 int 值 |
| 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 | - | 返回与该值所存储的 ClickHouse 数据类型相对应的 Python 类型 |
| Dynamic | object | - | 返回与该值所存储的 ClickHouse 数据类型相对应的 Python 类型 |
外部数据
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 子请求头 |
directors 表结合使用:
add_file 方法向初始 ExternalData 对象中添加额外的外部数据文件,该方法接受与构造函数相同的参数。对于 HTTP,所有外部数据都会作为 multi-part/form-data 文件上传的一部分进行传输。
时区
DateTime64 对象存储为不包含时区信息的数值,即自纪元 1970-01-01 00:00:00 UTC 起经过的秒数。对于 DateTime64 值,根据 precision 的不同,其表示也可以是自纪元以来的毫秒、微秒或纳秒。因此,任何时区信息的应用都始终发生在客户端。请注意,这会带来额外且不可忽略的计算开销,因此在性能敏感的应用中,建议将 DateTime 类型视为纪元时间戳,仅在面向用户显示和进行转换时处理时区 (例如,Pandas Timestamps 始终是表示纪元纳秒的 64 位整数,以提高性能) 。
在查询中使用带时区信息的数据类型时——尤其是 Python 的 datetime.datetime 对象——clickhouse-connect 会按照以下优先级规则在客户端应用时区:
- 如果为该查询指定了查询 method parameter
client_tzs,则应用对应列的特定时区 - 如果 ClickHouse 列带有 timezone 元数据 (即类型类似 DateTime64(3, ‘America/Denver’)) ,则应用 ClickHouse 列的 timezone。 (请注意,对于 ClickHouse 23.2 之前版本中的 DateTime 列,clickhouse-connect 无法获取此 timezone 元数据)
- 如果为该查询指定了查询 method parameter
query_tz,则应用“查询时区”。 - 如果为查询或 session 应用了 timezone setting,则应用该 timezone。 (此功能尚未在 ClickHouse 服务器 中发布)
- 最后,如果客户端
apply_server_timezoneparameter 已设置为 True (默认值) ,则应用 ClickHouse 服务器 的 timezone。
clickhouse-connect 将 始终 返回一个不包含时区信息的 Python datetime.datetime 对象。之后,如有需要,application code 可以再为这个不包含时区信息的对象附加额外的时区信息。