Inserting data with ClickHouse Connect: Advanced usage
InsertContexts
ClickHouse Connect executes Native-format inserts, the insert and insert_df methods, within an InsertContext. The insert_arrow, insert_df_arrow, and raw_insert methods send their payloads directly and don’t use one. The InsertContext includes all the values sent as arguments to the client insert method. In addition, when an InsertContext is originally constructed, ClickHouse Connect retrieves the data types for the insert columns required for efficient Native format inserts. By reusing the InsertContext for multiple inserts, this “pre-query” is avoided and inserts are executed more quickly and efficiently.
An InsertContext can be acquired using the client create_insert_context method. The method takes the same arguments as the insert function, except for context itself. Note that only the data property of InsertContexts should be modified for reuse. This is consistent with its intended purpose of providing a reusable object for repeated inserts of new data to the same table.
test_data = [[13, "v1", "v2"], [79, "v3", "v4"]]
ic = client.create_insert_context(table="test_table", data=test_data)
client.insert(context=ic)
assert client.command("SELECT count() FROM test_table") == 2
new_data = [[101, "v5", "v6"], [113, "v7", "v8"]]
ic.data = new_data
client.insert(context=ic)
qr = client.query("SELECT * FROM test_table ORDER BY key DESC")
assert qr.row_count == 4
assert qr.first_row[0] == 113InsertContexts include mutable state that is updated during the insert process, so they’re not thread safe.
Write formats
Write formats are implemented for a limited number of types. In most cases ClickHouse Connect automatically determines the correct write format for a column from its first non-null data value. For example, when the first value for a DateTime column is an integer, the client treats it as an epoch second.
It is normally unnecessary to override a write format, but the methods in clickhouse_connect.datatypes.format can set one globally. Container wrappers such as Array, Nullable, and LowCardinality preserve the element type’s formatting behavior.
Write format options
| ClickHouse Type | Native Python Type | Write Formats | Comments |
|---|---|---|---|
| Int[8-64], UInt[8-32] | int | ||
| UInt64 | int | ||
| [U]Int[128,256] | int | ||
| BFloat16 | float | ||
| Float32 | float | ||
| Float64 | float | ||
| Decimal | decimal.Decimal | ||
| Interval* | int | Values are signed 64-bit counts in the interval type’s unit. | |
| String | str or bytes | A column must consistently contain text or bytes. | |
| FixedString | bytes | string | String values are padded with zero bytes. Empty bytes are written as all zero bytes. |
| Enum[8,16] | str or int | Insert labels as strings or their underlying integer values. | |
| Date | datetime.date | int | Integer values are interpreted as days since 1970-01-01. |
| Date32 | datetime.date | int | Integer values are interpreted as signed day offsets. |
| DateTime | datetime.datetime | int | Integer values are interpreted as epoch seconds. |
| DateTime64 | datetime.datetime | int | Integer values are interpreted as ticks at the column precision. |
| Time | datetime.timedelta | int, string, time | Integer values are interpreted as seconds. |
| Time64 | datetime.timedelta | int, string, time | Scales 0 through 9 are supported. Integer values are interpreted as ticks at the column precision. NumPy timedelta values and DataFrame inserts work at every scale. Python time types are limited to microseconds. |
| IPv4 | ipaddress.IPv4Address |
string | Properly formatted strings can be inserted as IPv4 addresses |
| IPv6 | ipaddress.IPv6Address |
string | Properly formatted strings can be inserted as IPv6 addresses |
| Tuple | dict or tuple | Use () for Tuple(). |
|
| Map | dict | ||
| Nested | Sequence[dict] | ||
| UUID | uuid.UUID | string | Properly formatted strings can be inserted as ClickHouse UUIDs |
| JSON | dict | string | Dictionaries and JSON object strings are supported. The legacy Object('json') type is not supported. |
| Variant | object | Values use native member serialization. Use clickhouse_connect.datatypes.dynamic.typed_variant when Python types are ambiguous. |
|
| Dynamic | object | Values are currently inserted through their String representation. | |
| MultiPoint | Sequence[tuple] | Each point is a 2-tuple. Inserting MultiPoint values requires ClickHouse 26.8 or later. | |
| Geometry | tuple or list | Point values are 2-tuples. Wrap list-based values, including MultiPoint, with typed_variant to select a geometry member. |
|
| QBit | Sequence[float] | NumPy is used automatically for faster bit transposition when installed. |
Specialized insert methods
ClickHouse Connect provides specialized insert methods for common data formats:
insert_df– Insert a Pandas DataFrame as column-oriented Native data. It also supports explicit column names/types or a reusableInsertContext.insert_arrow– Insert a PyArrow Table using the ClickHouse Arrow input format.insert_df_arrow– Insert an Arrow-backed Pandas DataFrame or a Polars DataFrame. Pandas columns must all use Arrow-backed dtypes.
All three methods accept database, settings, and per-request HTTP transport_settings.
Pandas DataFrame insert
import clickhouse_connect
import pandas as pd
client = clickhouse_connect.get_client()
df = pd.DataFrame({
"id": [13, 79],
"name": ["user_1", "user_2"],
"age": [25, 30],
})
client.insert_df("users", df)PyArrow Table insert
import clickhouse_connect
import pyarrow as pa
client = clickhouse_connect.get_client()
arrow_table = pa.table({
"id": [13, 79],
"name": ["user_1", "user_2"],
"age": [25, 30],
})
client.insert_arrow("users", arrow_table)Arrow-backed DataFrame insert (pandas 2.x)
import clickhouse_connect
import pandas as pd
client = clickhouse_connect.get_client()
# Convert to Arrow-backed dtypes for better performance
df = pd.DataFrame({
"id": [13, 79],
"name": ["user_1", "user_2"],
"age": [25, 30],
}).convert_dtypes(dtype_backend="pyarrow")
client.insert_df_arrow("users", df)Create a table from a PyArrow schema
create_table_from_arrow_schema builds a CREATE TABLE statement from common scalar Arrow fields. The mapping covers signed and unsigned integers, floating-point values, booleans, strings, dates, and timestamps. It intentionally creates non-nullable ClickHouse columns and raises TypeError for unsupported Arrow types, so review the generated DDL before executing it.
import clickhouse_connect
import pyarrow as pa
from clickhouse_connect.driver.ddl import create_table_from_arrow_schema
client = clickhouse_connect.get_client()
schema = pa.schema(
[
("id", pa.uint32()),
("name", pa.string()),
("event_time", pa.timestamp("ms", tz="UTC")),
]
)
ddl = create_table_from_arrow_schema(
table_name="arrow_events",
schema=schema,
engine="MergeTree",
engine_params={"ORDER BY": "id"},
)
client.command(ddl)Time zones
When inserting Python datetime objects into DateTime or DateTime64 columns, ClickHouse Connect converts them to epoch values.
Timezone-aware datetime objects
Timezone-aware objects preserve the represented instant. The source timezone does not need to match the timezone declared on the ClickHouse column.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
client.command("CREATE TABLE events (event_time DateTime) ENGINE Memory")
data = [
[datetime(2023, 6, 15, 10, 30, tzinfo=timezone.utc)],
[datetime(2023, 6, 15, 10, 30, tzinfo=ZoneInfo("America/Denver"))],
[datetime(2023, 6, 15, 10, 30, tzinfo=ZoneInfo("Asia/Tokyo"))],
]
client.insert("events", data, column_names=["event_time"])
results = client.query(
"SELECT event_time FROM events ORDER BY event_time",
query_tz="UTC",
tz_mode="aware",
)
assert [row[0].hour for row in results.result_rows] == [1, 10, 16]Timezone-naive datetime objects
The global naive_datetime_insert setting controls native Python object inserts of naive datetime values. It also applies to naive ISO strings accepted by DateTime64 columns.
"local"is the default in 1.x. Python interprets the value in the process timezone when.timestamp()is called. This preserves the existing behavior."server"interprets the value as wall time in the timezone declared by theDateTimeorDateTime64column. If the column has no timezone, it uses the server timezone reported when the client connected.
Set the option before an insert. It is read when each native insert column containing Python datetime objects or DateTime64 ISO strings is serialized, so the change applies to existing clients and reusable insert contexts.
from datetime import datetime
from clickhouse_connect import common
common.set_setting("naive_datetime_insert", "server")
naive_time = datetime(2023, 6, 15, 10, 30)
client.insert("events", [[naive_time]], column_names=["event_time"])With "server", ClickHouse Connect attaches the target tzinfo before converting the value to an epoch. For IANA time zones, it follows the standard library rules for daylight saving transitions. A fall overlap uses the datetime’s fold value. The default fold=0 selects the offset before the transition, while fold=1 selects the offset after it. A spring gap uses the same offset selection and is not rejected or normalized.
Nonexistent spring gap wall times may not round trip through a wall-mode query parameter because ClickHouse text parsing can select a different offset. Use an aware datetime or a valid wall time when the instant matters.
The option only applies to native Python object inserts of datetime values and naive ISO strings accepted by DateTime64. Naive datetime64-dtype NumPy and Pandas columns keep their existing UTC wall time conversion.
To represent a specific instant independent of either mode, attach the intended timezone or provide an epoch integer explicitly.
from datetime import datetime, timezone
utc_time = datetime(2023, 6, 15, 10, 30, tzinfo=timezone.utc)
client.insert("events", [[utc_time]], column_names=["event_time"])
naive_time = datetime(2023, 6, 15, 10, 30)
epoch_timestamp = int(naive_time.replace(tzinfo=timezone.utc).timestamp())
client.insert("events", [[epoch_timestamp]], column_names=["event_time"])Naive datetime query parameters use the separate naive_datetime_binding setting. Its default "wall" mode sends the wall fields without host-local conversion. See the Parameters argument section.
DateTime columns with timezone metadata
ClickHouse columns can declare timezone metadata, for example DateTime('America/Denver') or DateTime64(3, 'Asia/Tokyo'). The metadata controls how values are presented when queried.
When inserting a timezone-aware value, ClickHouse Connect preserves the represented instant. For a naive value, the naive_datetime_insert setting controls whether the process timezone or column timezone is used. When queried, the result uses the column timezone unless a per-column override is supplied with the column_tzs argument. The query_tz argument doesn’t override a column’s declared timezone.
from datetime import datetime
from zoneinfo import ZoneInfo
client.command(
"CREATE TABLE events_with_timezone "
"(event_time DateTime('America/Los_Angeles')) "
"ENGINE Memory"
)
data = datetime(2023, 6, 15, 10, 30, tzinfo=ZoneInfo("America/New_York"))
client.insert("events_with_timezone", [[data]], column_names=["event_time"])
result = client.query("SELECT event_time FROM events_with_timezone")
returned = result.first_row[0]
assert returned.hour == 7
assert returned.tzinfo == ZoneInfo("America/Los_Angeles")File inserts
clickhouse_connect.driver.tools.insert_file streams a local file into an existing table and delegates parsing to ClickHouse.
| Parameter | Type | Default | Description |
|---|---|---|---|
client |
Client |
Required | Synchronous client used for the insert. |
table |
str | Required | Simple or database-qualified target table. |
file_path |
str | Required | Local path to the input file. |
fmt |
str | "CSV" or "CSVWithNames" |
Input format. Defaults to "CSV" when column_names is supplied and "CSVWithNames" otherwise. |
column_names |
Sequence[str] | None |
Columns represented by the file. Not required for formats that include names. |
database |
str | None |
Target database when the table is not qualified. |
settings |
dict | None |
See Settings argument. |
compression |
str | None |
Existing file compression, such as "zstd", "lz4", or "gzip". gzip is inferred from .gz and .gzip filenames. |
Input-format settings such as input_format_allow_errors_ratio and input_format_allow_errors_num can be passed through settings.
import clickhouse_connect
from clickhouse_connect.driver.tools import insert_file
client = clickhouse_connect.get_client()
insert_file(
client,
"example_table",
"my_data.csv",
settings={
"input_format_allow_errors_ratio": 0.2,
"input_format_allow_errors_num": 5,
},
)For an AsyncClient, await insert_file_async with the same arguments:
from clickhouse_connect.driver.tools import insert_file_async
await insert_file_async(async_client, "example_table", "my_data.csv")The async helper reads the file in a worker thread before awaiting raw_insert, so the file contents are held in memory.