> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> 使用 ClickHouse Connect 进行高级插入

# 高级插入

<h2 id="inserting-data-with-clickhouse-connect--advanced-usage">
  使用 ClickHouse Connect 插入数据：高级用法
</h2>

<h3 id="insertcontexts">
  InsertContexts
</h3>

ClickHouse Connect 的 Native-format 插入操作，以及 `insert` 和 `insert_df` 方法，都在 `InsertContext` 中执行。`insert_arrow`、`insert_df_arrow` 和 `raw_insert` 方法会直接发送其载荷，而不会使用 `InsertContext`。`InsertContext` 包含传递给客户端 `insert` 方法的所有参数值。此外，在初次构造 `InsertContext` 时，ClickHouse Connect 还会获取插入列的数据类型，以便高效地进行 Native format 插入。复用同一个 `InsertContext` 执行多次插入时，就可以避免这类“预查询”，从而让插入更快、更高效。

可以使用客户端的 `create_insert_context` 方法获取 `InsertContext`。该方法接受的参数与 `insert` 函数相同，但 `context` 本身除外。请注意，复用 `InsertContext` 时，只应修改其 `data` 属性。这也符合它的设计目的：为向同一张表重复插入新数据提供一个可复用的对象。

```python theme={null}
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] == 113
```

`InsertContext`s 包含会在 insert 过程中更新的可变状态，因此不具备线程安全性。

<h3 id="write-formats">
  写入格式
</h3>

只有少数类型实现了写入格式。在大多数情况下，ClickHouse Connect 会根据列中第一个非 NULL 的数据值自动判断正确的写入格式。例如，当 `DateTime` 列的第一个值是整数时，client 会将其视为纪元秒。

通常无需覆盖写入格式，但 `clickhouse_connect.datatypes.format` 中的方法可以在全局范围内进行设置。像 `Array`、`Nullable` 和 `LowCardinality` 这样的容器包装器会保留其元素类型的格式行为。

<h4 id="write-format-options">
  写入格式选项
</h4>

| ClickHouse 类型 | 原生 Python 类型 | 写入格式 | 注释 |
| - | - | - | - |
| 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 | | 值为以该时间间隔类型单位计量的有符号 64 位计数。 |
| String | str or bytes | | 同一列中的值必须始终统一为文本或字节类型。 |
| FixedString | bytes | string | 字符串值会以零字节填充。空字节会写入为全零字节。 |
| Enum\[8,16] | str or int | | 可将标签作为字符串插入，或插入其底层整数值。 |
| Date | datetime.date or datetime.datetime | int | 整数值会被解释为自 1970-01-01 起的天数。 |
| Date32 | datetime.date or datetime.datetime | int | 整数值会被解释为有符号的天数偏移。 |
| DateTime | datetime.datetime | int | 整数值会被解释为纪元秒。 |
| DateTime64 | datetime.datetime | int | 整数值会被解释为按列精度表示的 tick。 |
| Time | datetime.timedelta | int, string, time | 整数值会被解释为秒。 |
| Time64 | datetime.timedelta | int, string, time | 支持 0 到 9 的标度。整数值会被解释为按列精度表示的 tick。NumPy timedelta 值和 DataFrame 插入在所有标度下均可正常工作。Python time 类型仅支持到微秒。 |
| IPv4 | `ipaddress.IPv4Address` | string | 格式正确的字符串可作为 IPv4 地址插入 |
| IPv6 | `ipaddress.IPv6Address` | string | 格式正确的字符串可作为 IPv6 地址插入 |
| Tuple | dict or tuple | | 对于 `Tuple()`，请使用 `()`。 |
| Map | dict | | `pairs` 读取格式不会影响插入时的输入。如需在往返转换中无损保留重复键，请使用 Arrow。 |
| Nested | Sequence\[dict] | | |
| UUID | uuid.UUID | string | 格式正确的字符串可作为 ClickHouse UUID 插入 |
| JSON | dict | string | 支持字典和 JSON 对象字符串。旧版 `Object('json')` 类型不受支持。 |
| Variant | object | | 值使用原生成员序列化。在 Python 类型存在歧义时，请使用 `clickhouse_connect.datatypes.dynamic.typed_variant`。 |
| Dynamic | object | | 当前值通过其 String 表示形式插入。 |
| MultiPoint | Sequence\[tuple] | | 每个点为一个二元组。插入 MultiPoint 值需要 ClickHouse 26.8 或更高版本。 |
| Geometry | tuple or list | | Point 值为二元组。对于基于列表的值 (包括 MultiPoint) ，请使用 `typed_variant` 包装以选择相应的几何成员类型。 |
| QBit | Sequence\[float] | | 安装 NumPy 后，会自动使用它来更快地进行比特转置。 |

<h4 id="date-and-date32-values">
  Date 和 Date32 值
</h4>

原生插入允许在 `Date` 或 `Date32` 列中混合写入 Python 的 `date` 和 `datetime` 值。对于 `datetime` 值，会直接取其自身的日历日期 (即 `.date()` 的返回值) ，不进行时区转换。这一规则同样适用于 `Nullable`、`Array`、`Tuple` 和 `LowCardinality` 列。

```python theme={null}
from datetime import date, datetime, timedelta, timezone

value = datetime(2024, 1, 1, 0, 30, tzinfo=timezone(timedelta(hours=14)))
client.command("CREATE TABLE event_dates (event_date Date) ENGINE Memory")
client.insert("event_dates", [[value], [date(2024, 1, 2)]])

result = client.query("SELECT event_date FROM event_dates ORDER BY event_date")
assert result.result_rows == [(date(2024, 1, 1),), (date(2024, 1, 2),)]
```

此规则适用于 Python 对象，包括 Pandas 的 `object` 列。带时区信息的 Pandas `datetime64` 列使用 UTC 日历日期。如需保留原始日历日期，请在插入前将这些值转换为 Python `date` 对象。NumPy `datetime64` 值不包含时区元数据。

日期时间查询参数遵循[绑定规则](/zh/integrations/language-clients/python/driver-api#parameters-argument)。带时区信息的 `datetime` 在格式化为 `Date` 或 `Date32` 值之前，会先转换为服务器时区，这可能导致日历日期发生变化。如果插入操作与绑定参数必须使用相同的日历日期，请显式传入 `value.date()`。

<h3 id="specialized-insert-methods">
  专用插入方法
</h3>

ClickHouse Connect 为常见数据格式提供了专用的插入方法：

* `insert_df` -- 将 Pandas DataFrame 作为列式 Native 数据插入。它还支持显式指定列名/类型，或使用可复用的 `InsertContext`。
* `insert_arrow` -- 使用 ClickHouse Arrow 输入格式插入 PyArrow Table。
* `insert_df_arrow` -- 插入基于 Arrow 的 Pandas DataFrame 或 Polars DataFrame。Pandas 的所有列都必须使用基于 Arrow 的 dtype。

这三种方法都接受 `database`、`settings` 和按请求指定的 HTTP `transport_settings`。

<Note>
  NumPy array 是合法的 Sequence of Sequences，因此可作为主 `insert` 方法的 `data` 参数使用，无需专用方法。
</Note>

<h4 id="pandas-dataframe-insert">
  插入 Pandas DataFrame
</h4>

```python theme={null}
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)
```

<h4 id="pyarrow-table-insert">
  PyArrow Table 插入操作
</h4>

```python theme={null}
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)
```

<h4 id="arrow-backed-dataframe-insert-pandas-2">
  基于 Arrow 的 DataFrame 插入 (pandas 2.x)
</h4>

```python theme={null}
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)
```

<h3 id="create-table-from-pyarrow-schema">
  根据 PyArrow schema 创建表
</h3>

`create_table_from_arrow_schema` 会根据常见的标量 Arrow field 构建 `CREATE TABLE` 语句。该映射涵盖有符号和无符号整数、浮点值、布尔值、字符串、日期和时间戳。它会刻意创建不可为 NULL 的 ClickHouse 列，并在遇到不受支持的 Arrow type 时引发 `TypeError`，因此请在执行前先检查生成的 DDL。

```python theme={null}
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)
```

<h3 id="time-zones">
  时区
</h3>

将 Python `datetime` 对象插入 `DateTime` 或 `DateTime64` 列时，ClickHouse Connect 会将其转换为自纪元开始计数的值。

<h4 id="timezone-aware-datetime-objects">
  带时区信息的 datetime 对象
</h4>

带时区信息的对象会保留其所表示的时刻。源时区无需与 ClickHouse 列中声明的时区一致。

```python theme={null}
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]
```

<Note>
  ClickHouse Connect 使用标准库中的 `zoneinfo` 模块。该驱动不再依赖 `pytz`。
</Note>

<h4 id="timezone-naive-datetime-objects">
  不带时区的 datetime 对象
</h4>

全局 `naive_datetime_insert` 设置用于控制插入不带时区的原生 Python `datetime` 对象。它也适用于 `DateTime64` 列接受的不带时区的 ISO 字符串。

* `"local"` 是 1.x 中的默认值。调用 `.timestamp()` 时，Python 会根据进程时区解释该值，从而保持现有行为。
* `"server"` 会将该值解释为 `DateTime` 或 `DateTime64` 列所声明时区中的墙钟时间。如果该列未指定时区，则使用客户端连接时获取的服务器时区。

请在插入前设置该选项。序列化每个包含 Python `datetime` 对象或 `DateTime64` ISO 字符串的原生插入列时，都会读取此选项，因此更改会应用于现有客户端和可复用的插入上下文。

```python theme={null}
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"])
```

使用 `"server"` 时，ClickHouse Connect 会先附加目标 `tzinfo`，再将值转换为纪元时间。对于 IANA 时区，它遵循标准库处理夏令时切换的规则。秋季重叠时段使用 datetime 的 `fold` 值。默认的 `fold=0` 选择切换前的偏移量，而 `fold=1` 选择切换后的偏移量。春季间隙采用相同的偏移量选择方式，不会被拒绝或归一化。

不存在的春季间隙墙钟时间可能无法通过墙钟模式查询参数往返转换，因为 ClickHouse 的文本解析可能会选择不同的偏移量。当具体时刻很重要时，请使用带时区信息的 `datetime` 或有效的墙钟时间。

该选项仅适用于原生 Python `datetime` 对象的插入，以及 `DateTime64` 接受的不带时区的 ISO 字符串。不带时区的 `datetime64`-dtype 的 NumPy 和 Pandas 列会保留其现有的 UTC 墙钟时间转换方式。

若要表示不依赖于任一模式的特定时刻，请附加预期的时区，或显式提供纪元整数。

```python theme={null}
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"])
```

不含时区信息的 `datetime` 查询参数使用独立的 `naive_datetime_binding` 设置。默认的 `"wall"` 模式会按原样发送日期时间字段，不进行主机本地时区转换。请参阅[参数 argument](/zh/integrations/language-clients/python/driver-api#parameters-argument)部分。

<h4 id="datetime-columns-with-timezone-metadata">
  带有时区元数据的 DateTime 列
</h4>

ClickHouse 列可以声明时区元数据，例如 `DateTime('America/Denver')` 或 `DateTime64(3, 'Asia/Tokyo')`。这些元数据决定了值在查询时的显示方式。

插入带时区信息的值时，ClickHouse Connect 会保留其所表示的时刻。对于不带时区信息的值，`naive_datetime_insert` 设置决定使用进程时区还是列时区。查询时，结果会使用该列的时区，除非通过 `column_tzs` 参数为特定列提供覆盖设置。`query_tz` 参数不会覆盖列中已声明的时区。

```python theme={null}
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")
```

<h2 id="file-inserts">
  File 插入
</h2>

`clickhouse_connect.driver.tools.insert_file` 会以流式方式将本地文件插入现有表中，并由 ClickHouse 负责解析。

| 参数 | 类型 | 默认值 | 说明 |
| - | - | - | - |
| `client` | `Client` | 必填 | 用于执行插入的同步客户端。 |
| `table` | str | 必填 | 简单表名或带数据库限定的目标表。 |
| `file_path` | str | 必填 | 输入文件的本地路径。 |
| `fmt` | str | `"CSV"` or `"CSVWithNames"` | 输入格式。提供 `column_names` 时，默认值为 `"CSV"`；否则默认值为 `"CSVWithNames"`。 |
| `column_names` | Sequence\[str] | `None` | 文件中表示的列。对于包含列名的格式，不需要提供。 |
| `database` | str | `None` | 当表名未带数据库限定时使用的目标数据库。 |
| `settings` | dict | `None` | 参见 [Settings 参数](/zh/integrations/language-clients/python/driver-api#settings-argument-1)。 |
| `compression` | str | `None` | 文件当前使用的压缩格式，例如 `"zstd"`、`"lz4"` 或 `"gzip"`。对于 `.gz` 和 `.gzip` 文件名，会自动推断为 gzip。 |

可通过 `settings` 传递输入格式设置，例如 `input_format_allow_errors_ratio` 和 `input_format_allow_errors_num`。

```python theme={null}
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,
    },
)
```

对于 `AsyncClient`，使用相同参数 await `insert_file_async`：

```python theme={null}
from clickhouse_connect.driver.tools import insert_file_async

await insert_file_async(async_client, "example_table", "my_data.csv")
```

async 辅助函数会先在工作线程中读取文件，再等待 `raw_insert`，因此文件内容会保存在内存中。
