QueryContexts
ClickHouse Connect executes standard queries within a QueryContext. The QueryContext contains the key structures that are used to build queries against the ClickHouse database, and the configuration used to process the result into a QueryResult or other response data structure. That includes the query itself, parameters, settings, read formats, and other properties.
A QueryContext can be acquired using the client create_query_context method. This method takes the same parameters as the core query method. This query context can then be passed to the query, query_df, or query_np methods as the context keyword argument instead of any or all of the other arguments to those methods. Note that additional arguments specified for the method call will override any properties of QueryContext.
The clearest use case for a QueryContext is to send the same query with different binding parameter values. All parameter values can be updated by calling the QueryContext.set_parameters method with a dictionary, or any single value can be updated by calling QueryContext.set_parameter with the desired key, value pair.
qc = client.create_query_context(
query="SELECT {k:Int32}",
parameters={"k": 13},
)
result = client.query(context=qc)
assert result.first_row == (13,)
qc.set_parameter("k", 79)
result = client.query(context=qc)
assert result.first_row == (79,)Note that QueryContexts aren’t thread safe, but a copy can be obtained in a multi-threaded environment by calling the QueryContext.updated_copy method.
Streaming queries
The ClickHouse Connect Client provides multiple methods for retrieving data as a stream (implemented as a Python generator):
query_column_block_stream– Returns query data in blocks as a sequence of columns using native Python objectsquery_row_block_stream– Returns query data as a block of rows using native Python objectsquery_rows_stream– Returns query data as a sequence of rows using native Python objectsquery_np_stream– Returns each ClickHouse block of query data as a NumPy arrayquery_df_stream– Returns each ClickHouse Block of query data as a Pandas DataFramequery_arrow_stream– Returns query data as PyArrowRecordBatchobjectsquery_df_arrow_stream– Returns each Arrow batch as a Pandas or Polars DataFrame, selected bydataframe_library
Each method returns a StreamContext that must be opened with a with statement. Async client streaming methods are awaited and opened with async with.
Data blocks
ClickHouse Connect processes all data from the primary query method as a stream of blocks received from the ClickHouse server. These blocks are transmitted in the custom “Native” format to and from ClickHouse. A “block” is simply a sequence of columns of binary data, where each column contains an equal number of data values of the specified data type. (As a columnar database, ClickHouse stores this data in a similar form.) The size of a block returned from a query is governed by two user settings that can be set at several levels (user profile, user, session, or query). They’re:
- max_block_size – Maximum block size in rows.
- preferred_block_size_bytes – Preferred block size in bytes.
Regardless of preferred_block_size_bytes, a block will not exceed max_block_size rows. The actual size can be smaller and should not be treated as stable.
When using one of the Client query_*_stream methods, results are returned on a block by block basis. ClickHouse Connect only loads a single block at a time. This allows processing large amounts of data without the need to load all of a large result set into memory. Note the application should be prepared to process any number of blocks and the exact size of each block can’t be controlled.
HTTP data buffer for slow processing
If an application consumes blocks much more slowly than the server produces them, the HTTP connection can close before processing completes. Increase the common http_buffer_size setting when the application has enough memory to buffer more response data. The default is 10 MiB. lz4 and zstd response bytes remain compressed in this buffer, which increases its effective capacity.
StreamContexts
Each of the query_*_stream methods (like query_row_block_stream) returns a ClickHouse StreamContext object, which is a combined Python context/generator. This is the basic usage:
with client.query_row_block_stream(
"SELECT pickup, dropoff, pickup_longitude, pickup_latitude FROM taxi_trips"
) as stream:
for block in stream:
for row in block:
process_trip(row)Note that trying to use a StreamContext without a with statement will raise an error. The use of a Python context ensures that the stream (in this case, a streaming HTTP response) will be properly closed even if not all the data is consumed and/or an exception is raised during processing. Also, StreamContexts can only be used once to consume the stream. Trying to use a StreamContext after it has exited will produce a StreamClosedError.
If the connection fails while a result is being read, a StreamFailureError is raised instead of silently returning a truncated result. Its message follows the client’s show_clickhouse_errors setting.
You can use the source property of the StreamContext to access the parent result object, which includes column names and types. For most streams this is a QueryResult; the query_np_stream and query_df_stream methods expose a NumpyResult instead.
Stream types
The query_column_block_stream method returns the block as a sequence of column data stored as native Python data types. Using the above taxi_trips queries, the data returned will be a list where each element of the list is another list (or tuple) containing all the data for the associated column. So block[0] would be a tuple containing nothing but strings. Column oriented formats are most used for doing aggregate operations for all the values in a column, like adding up total fares.
The query_row_block_stream method returns the block as a sequence of rows like a traditional relational database. For taxi trips, the data returned will be a list where each element of the list is another list representing a row of data. So block[0] would contain all the fields in order for the first taxi trip, block[1] would contain a row for all the fields in the second taxi trip, and so on. Row-oriented results are normally used for display or transformation processes.
The query_rows_stream method automatically moves to the next block and yields one row at a time. It is the row-by-row counterpart to query_row_block_stream.
The query_np_stream method returns each block as a NumPy array. When all result columns share a NumPy dtype, the array is two-dimensional with shape (rows, columns). Mixed results are returned as a one-dimensional structured array or use the object dtype.
The query_df_stream method returns each ClickHouse Block as a two-dimensional Pandas DataFrame. Here’s an example which shows that the StreamContext object can be used as a context in a deferred fashion (but only once).
df_stream = client.query_df_stream("SELECT * FROM hits")
column_names = df_stream.source.column_names
with df_stream:
for df in df_stream:
process_dataframe(df)The query_df_arrow_stream method converts Arrow batches to Pandas or Polars DataFrames. Select the library with dataframe_library, which defaults to "pandas".
Finally, query_arrow_stream wraps a ClickHouse ArrowStream response in a StreamContext. Each iteration returns a PyArrow RecordBatch.
Streaming examples
Stream rows
import clickhouse_connect
client = clickhouse_connect.get_client()
# Stream large result sets row by row
with client.query_rows_stream("SELECT number, number * 2 as doubled FROM system.numbers LIMIT 100000") as stream:
for row in stream:
print(row) # Process each row
# Output:
# (0, 0)
# (1, 2)
# (2, 4)
# Additional rows followStream row blocks
import clickhouse_connect
client = clickhouse_connect.get_client()
# Stream in blocks of rows (more efficient than row-by-row)
with client.query_row_block_stream("SELECT number, number * 2 FROM system.numbers LIMIT 100000") as stream:
for block in stream:
print(f"Received block with {len(block)} rows")Stream Pandas DataFrames
import clickhouse_connect
client = clickhouse_connect.get_client()
# Stream query results as Pandas DataFrames
with client.query_df_stream("SELECT number, toString(number) AS str FROM system.numbers LIMIT 100000") as stream:
for df in stream:
# Process each DataFrame block
print(f"Received DataFrame with {len(df)} rows")
print(df.head(3))Stream Arrow batches
import clickhouse_connect
client = clickhouse_connect.get_client()
# Stream query results as Arrow record batches
with client.query_arrow_stream("SELECT * FROM large_table") as stream:
for arrow_batch in stream:
# Process each Arrow batch
print(f"Received Arrow batch with {arrow_batch.num_rows} rows")Async stream rows
import asyncio
import clickhouse_connect
async def main():
async_client = await clickhouse_connect.get_async_client()
async with await async_client.query_rows_stream(
"SELECT number FROM numbers(100000)"
) as stream:
async for row in stream:
print(row)
asyncio.run(main())NumPy, Pandas, and Arrow queries
ClickHouse Connect provides specialized query methods for working with NumPy, Pandas, and Arrow data structures. These methods allow you to retrieve query results directly in these popular data formats without manual conversion.
NumPy queries
The query_np method returns query results as a NumPy array instead of a ClickHouse Connect QueryResult.
import clickhouse_connect
client = clickhouse_connect.get_client()
# Query returns a NumPy array
np_array = client.query_np("SELECT number, number * 2 AS doubled FROM system.numbers LIMIT 5")
print(type(np_array))
# Output:
# <class 'numpy.ndarray'>
print(np_array)
# Output:
# [[0 0]
# [1 2]
# [2 4]
# [3 6]
# [4 8]]Pandas queries
The query_df method returns query results as a Pandas DataFrame instead of a ClickHouse Connect QueryResult.
import clickhouse_connect
client = clickhouse_connect.get_client()
# Query returns a Pandas DataFrame
df = client.query_df("SELECT number, number * 2 AS doubled FROM system.numbers LIMIT 5")
print(type(df))
# Output: <class 'pandas.core.frame.DataFrame'>
print(df)
# Output:
# number doubled
# 0 0 0
# 1 1 2
# 2 2 4
# 3 3 6
# 4 4 8PyArrow queries
The query_arrow method returns a PyArrow Table using ClickHouse’s Arrow output format directly. It accepts query, parameters, settings, external_data, and transport_settings. The use_strings option controls whether ClickHouse String columns are emitted as Arrow strings or binary values.
import clickhouse_connect
client = clickhouse_connect.get_client()
# Query returns a PyArrow Table
arrow_table = client.query_arrow("SELECT number, toString(number) AS str FROM system.numbers LIMIT 3")
print(type(arrow_table))
# Output:
# <class 'pyarrow.lib.Table'>
print(arrow_table)
# Output:
# pyarrow.Table
# number: uint64 not null
# str: string not null
# ----
# number: [[0,1,2]]
# str: [["0","1","2"]]Arrow-backed DataFrames
ClickHouse Connect supports efficient DataFrame creation from Arrow results through query_df_arrow and query_df_arrow_stream. These methods avoid conversion through Python row objects and reuse Arrow buffers where the target library permits:
query_df_arrow: Executes the query using the ClickHouseArrowoutput format and returns a DataFrame.dataframe_library="pandas"returns a Pandas 2.0 or later DataFrame usingpd.ArrowDtype.dataframe_library="polars"returns a Polars DataFrame created throughpl.from_arrow.
query_df_arrow_stream: Streams Arrow batches as Pandas or Polars DataFrames.
Query to Arrow-backed DataFrame
import clickhouse_connect
client = clickhouse_connect.get_client()
# Query returns a Pandas DataFrame with Arrow dtypes (requires pandas 2.x)
df = client.query_df_arrow(
"SELECT number, toString(number) AS str FROM system.numbers LIMIT 3",
dataframe_library="pandas"
)
print(df.dtypes)
# Output:
# number uint64[pyarrow]
# str string[pyarrow]
# dtype: object
# Or use Polars
polars_df = client.query_df_arrow(
"SELECT number, toString(number) AS str FROM system.numbers LIMIT 3",
dataframe_library="polars"
)
print(polars_df.dtypes)
# Output:
# [UInt64, String]
# Streaming into batches of DataFrames (polars shown)
with client.query_df_arrow_stream(
"SELECT number, toString(number) AS str FROM system.numbers LIMIT 100000", dataframe_library="polars"
) as stream:
for df_batch in stream:
print(f"Received {type(df_batch)} batch with {len(df_batch)} rows and dtypes: {df_batch.dtypes}")Notes and caveats
- ClickHouse controls the Arrow schema. Types without a direct Arrow representation can be returned using a compatible physical type, including binary fields. Inspect
table.schemaor DataFrame dtypes before applying application-specific conversions. - Arrow-backed Pandas results require Pandas 2.0 or later.
use_stringscontrols whether ClickHouseStringcolumns use Arrow string or binary fields when the server supportsoutput_format_arrow_string_as_string.tz_mode="schema"is not yet supported by Arrow-based query methods. They warn and preserve the timezone metadata supplied by the Arrow response.
Read formats
Read formats control values returned by query, query_np, and query_df. They don’t apply to raw or Arrow methods because those methods use a server output format directly. For example, setting the UUID read format to "string" returns UUID strings instead of uuid.UUID objects.
The “data type” argument for any formatting function can include wildcards. The format is a single lowercase string. Container wrappers such as Array, Nullable, and LowCardinality preserve the selected format for their element type.
Read formats can be set at several levels:
- Globally, using the methods defined in the
clickhouse_connect.datatypes.formatpackage. This will control the format of the configured datatype for all queries.
from clickhouse_connect.datatypes.format import set_read_format
# Return both IPv6 and IPv4 values as strings
set_read_format("IPv*", "string")
# Return all Date types as the underlying epoch second or epoch day
set_read_format("Date*", "int")- For an entire query, using the optional
query_formatsdictionary argument. In that case any column (or subcolumn) of the specified data types will use the configured format.
# Return any UUID column as a string
client.query(
"SELECT user_id, user_uuid, device_uuid FROM users",
query_formats={"UUID": "string"},
)- For a specific result column, use the optional
column_formatsdictionary. Each key is a returned column name. Its value is a format string or a nested mapping from ClickHouse type names to formats, which is useful for Tuples, Maps, and other container types.
# Return IPv6 values in the `dev_address` column as strings
client.query(
"SELECT device_id, dev_address, gw_address FROM devices",
column_formats={"dev_address": "string"},
)Read format options (Python types)
| ClickHouse Type | Native Python Type | Read Formats | Comments |
|---|---|---|---|
| Int[8-64], UInt[8-32] | int | string | |
| UInt64 | int | signed | Superset doesn’t currently handle large unsigned UInt64 values |
| [U]Int[128,256] | int | string | Pandas and NumPy int values are 64 bits maximum, so these can be returned as strings |
| BFloat16 | float | - | All Python floats are 64 bits internally |
| Float32 | float | string | All Python floats are 64 bits internally |
| Float64 | float | string | |
| Decimal | decimal.Decimal | - | |
| Interval* | int | string | Values are signed 64-bit counts in the interval type’s unit. |
| String | str | bytes | ClickHouse String columns have no inherent encoding, so they’re also used for variable length binary data |
| FixedString | bytes | string | FixedStrings are fixed size byte arrays, but sometimes are treated as Python strings |
| Enum[8,16] | str | int | The native format returns labels; int returns the underlying integer. |
| Date | datetime.date | int | The integer format returns days since 1970-01-01. |
| Date32 | datetime.date | int | The integer format returns the wider signed day offset. |
| DateTime | datetime.datetime | int | The integer format returns epoch seconds. |
| DateTime64 | datetime.datetime | int | The integer format returns ticks at the column precision. Python datetime is limited to microseconds. |
| Time | datetime.timedelta | int, string, time | The integer format returns seconds. The time format is limited to values that fit datetime.time. |
| Time64 | datetime.timedelta | int, string, time | Scales 0 through 9 are supported. The integer format returns ticks at the column precision. Python timedelta is limited to microseconds. |
| IPv4 | ipaddress.IPv4Address |
string, int | IP addresses can be read as strings or integers. |
| IPv6 | ipaddress.IPv6Address |
string | IP addresses can be read as strings and properly formatted can be inserted as IP addresses |
| Tuple | dict or tuple | tuple, dict, json | Named tuples return dictionaries by default; unnamed tuples return tuples. A Tuple() value returns (). |
| Map | dict | - | |
| Nested | Sequence[dict] | - | |
| UUID | uuid.UUID | string | UUIDs can be read as strings formatted as per RFC 4122 |
| JSON | dict | string | A python dictionary is returned by default. The string format will return a JSON string |
| Variant | object | typed | typed returns TypedVariant(value, type_name) so the originating member type is preserved. |
| Dynamic | object | - | Returns the matching Python type for the ClickHouse datatype stored for the value |
| MultiPoint | list[tuple] | - | Each point is returned as a 2-tuple. |
| Geometry | tuple or list | typed | Point values are 2-tuples. Other members, including MultiPoint, use nested lists. Select typed with the Geometry key to preserve the member type. Variant format settings do not apply. |
| QBit | list[float] | - | NumPy is used automatically for faster bit transposition when installed. |
query_np, query_np_stream, query_df, and query_df_stream support Time64 scales 0, 3, 6, and 9. Other scales raise ProgrammingError because they do not have a matching NumPy time unit. Use a standard Python query with the int or string read format to preserve those precisions. The experimental Rust codec documents an exception for Time64 stored inside Dynamic.
External data
ClickHouse queries can accept external data in any supported input format. The client sends the data as part of the request, and the query can reference it as a temporary external table. See the ClickHouse external data documentation. Client query methods accept a clickhouse_connect.driver.external.ExternalData object through the external_data parameter.
| Name | Type | Description |
|---|---|---|
| file_path | str | Path to a file on the local system path to read the external data from. Either file_path or data is required |
| file_name | str | The name of the external data “file”. If not provided, taken from the file name portion of file_path. The external table name is the file name without its extension |
| data | bytes | The external data in binary form (instead of being read from a file). Either data or file_path is required |
| fmt | str | ClickHouse input format of the data. Defaults to TSV |
| types | str or seq of str | A list of column data types in the external data. If a string, types should be separated by commas. Either types or structure is required |
| structure | str or seq of str | A list of column name + data type in the data (see examples). Either structure or types is required |
| mime_type | str | Optional MIME type of the file data. Currently ClickHouse ignores this HTTP subheader |
This example joins an external CSV file to a directors table stored on the server:
import clickhouse_connect
from clickhouse_connect.driver.external import ExternalData
client = clickhouse_connect.get_client()
ext_data = ExternalData(
file_path="/data/movies.csv",
fmt="CSV",
structure=[
"movie String",
"year UInt16",
"rating Decimal32(3)",
"director String",
],
)
result = client.query(
"SELECT name, avg(rating) "
"FROM directors INNER JOIN movies ON directors.name = movies.director "
"GROUP BY directors.name",
external_data=ext_data,
).result_rowsAdditional external data files can be added to the initial ExternalData object using the add_file method, which takes the same parameters as the constructor. For HTTP, all external data is transmitted as part of a multi-part/form-data file upload.
The chDB backend does not support external data.
Time zones
ClickHouse DateTime and DateTime64 values are transmitted as epoch-based numeric values. ClickHouse Connect converts them to Python datetime objects using column metadata, query overrides, and the client’s timezone policy.
The client has two independent timezone options:
tz_sourceselects the fallback timezone for columns without explicit timezone metadata:"auto"is the default. It uses the server timezone when the client can resolve it safely across daylight-saving transitions, otherwise it uses the local timezone."server"always uses the server timezone."local"always uses the local process timezone.
tz_modecontrols timezone awareness:"naive_utc"is the default. UTC and UTC-equivalent results are returned as naivedatetimeobjects for backward compatibility."aware"preserves UTC tzinfo and returns timezone-aware UTC values."schema"returns timezone-aware values only when the column type declares a timezone, and naive values for bareDateTime/DateTime64columns.
For normal "naive_utc" and "aware" queries, the active timezone is selected in this order:
- A per-column
column_tzsoverride. - Timezone metadata on the ClickHouse column type.
- The query-wide
query_tzoverride. - Timezone information returned with the HTTP response.
- The fallback selected by
tz_source.
tz_mode="schema" ignores query and fallback timezones, but an explicit column_tzs override still takes precedence.
result = client.query(
"SELECT "
"toDateTime('2026-01-15 12:00:00', 'UTC') AS utc_time, "
"toDateTime('2026-01-15 12:00:00', 'America/Denver') AS denver_time",
tz_mode="aware",
)
assert result.first_row[0].tzinfo is not None
assert result.first_row[1].tzinfo is not NoneTimezone names are resolved with the standard library zoneinfo module. Windows installs receive tzdata automatically. On minimal Linux images without an IANA timezone database, install clickhouse-connect[tzdata].
Pandas results preserve each ClickHouse type’s natural resolution, such as datetime64[s] for DateTime and datetime64[ms] for DateTime64(3). The Arrow-backed DataFrame methods query_df_arrow and query_df_arrow_stream don’t yet implement tz_mode="schema" and will emit a warning when it is requested. query_arrow and query_arrow_stream return the timezone metadata from the Arrow response unchanged.