Skip to main content
ClickHouse Connect includes the clickhousedb SQLAlchemy dialect on top of the core driver. It supports SQLAlchemy 1.4.40 and later, including SQLAlchemy 2.x, with a focus on Core queries, ClickHouse DDL, reflection, and simple ORM inserts. Install the SQLAlchemy dependencies with the package extra:

Connect with SQLAlchemy

Create an engine with either the clickhousedb:// or clickhousedb+connect:// URL form:
URL query parameters can contain ClickHouse settings, ClickHouse Connect client options such as compression, query_limit, and timeouts, or HTTP/TLS options such as ca_cert. Prefix a ClickHouse setting with ch_ to force it to be treated as a server setting when needed, for example ch_http_max_field_name_size=99999. See Connection arguments and settings for the available client options.

Per-query settings

Pass ClickHouse settings through SQLAlchemy execution options. Settings can be set on an engine, connection, or statement. A statement value takes precedence over a connection or engine value with the same key.

Per-query read formats

Set ClickHouse read formats on an engine, connection, or statement through SQLAlchemy execution options with query_formats, with statement formats applied first so they override matching connection or engine keys and wildcards.

Server-side parameters

SQLAlchemy normally renders client-side parameters. Opt in to ClickHouse server-side parameters when creating the engine:
In this mode every bound value must have a ClickHouse-compatible SQLAlchemy type. Supported IN lists become typed ClickHouse Array parameters. The compiler raises CompileError when it cannot derive a compatible type or safely process a bind. Bind names must be ClickHouse ASCII BareWord names. Names that start and end with $ are rejected because the core driver reserves them for raw binary query parameters.

Core queries

The dialect supports SQLAlchemy Core SELECT queries with joins, filters, ordering, limits and offsets, DISTINCT, and compound selects. SQLAlchemy union(), intersect(), and except_() compile to ClickHouse UNION DISTINCT, INTERSECT DISTINCT, and EXCEPT DISTINCT. Their union_all(), intersect_all(), and except_all() counterparts compile to the corresponding ALL operators. This explicit mapping preserves SQLAlchemy’s duplicate semantics regardless of ClickHouse set-operation defaults.
Lightweight DELETE is supported and requires an explicit WHERE clause:

Literal rendering

When SQLAlchemy inlines a bound value through literal_binds or literal_execute, the dialect uses ClickHouse quoting for generic string types and ClickHouse types. This also applies through TypeDecorator wrappers and with_variant() selections. String values retain percent signs and backslashes even when other bound parameters remain.

JSON type hints

Declare typed JSON paths with the typed_paths mapping. A path type can be a ClickHouse SQLAlchemy type class, a configured instance, or a ClickHouse type name string. Type name strings support types without a SQLAlchemy constructor, such as Dynamic, and can still be used for complex configured type expressions. They preserve names in a named Tuple. Type name strings can contain configured nested JSON types such as Array(JSON(`child` UInt32)). Recognized ClickHouse type names are case-insensitive in these strings and are emitted with their canonical capitalization. A string must contain one complete type expression. Trailing text and malformed nested JSON arguments are rejected. An empty Tuple() is not supported as a JSON typed path because ClickHouse cannot serialize it through a JSON column’s Native format. The core driver supports Tuple() in query and insert columns at any position, including nested in positional or named tuples, inside Array, and as Nullable(Tuple()) where enabled by the server.
For simple Python identifier paths, keyword arguments are shorthand for typed_paths, for example JSON(user_id=UInt32). Use typed_paths for dotted paths, spaces, backticks, %2E encoded dots, or names that match constructor options. A typed path named SKIP is supported through the mapping. Keys in typed_paths and values in skip_paths are decoded names. Leading or trailing backticks and double quotes are treated as literal path characters, not as pre-applied SQL quoting. Inside a raw type string, backticks and double quotes are ClickHouse identifier syntax. Up to 1000 typed paths can be configured. max_dynamic_paths accepts 0 through 10000. max_dynamic_types accepts 0 through 254. These ranges also apply inside raw nested JSON type strings. Explicit server defaults of 1024 and 32 are omitted from generated DDL. Plain skip paths are deduplicated. Regular expression strings are not validated by Python because ClickHouse uses RE2 syntax. Duplicate regular expressions are preserved. A plain skip path cannot be named exactly REGEXP because ClickHouse reserves that token for SKIP REGEXP. Names such as REGEXP_foo remain valid. In a raw JSON type string, a plain SKIP operand must be one ClickHouse identifier or a dot-separated compound identifier. An unquoted compound identifier cannot start with REGEXP; quote that first component when it is path data. SKIP REGEXP must have one single-quoted string literal. Quote identifier parts with backticks or double quotes when they contain spaces or punctuation. Raw JSON type hints support Variant(...); standalone Variant has no public SQLAlchemy constructor. Variant members are ordered and deduplicated by the same canonical names used by ClickHouse. The constructor orders arguments in the same canonical form returned by ClickHouse. Reflected types, SQLAlchemy type copies, and Alembic autogeneration preserve the configuration.

JSON subcolumns

For a column declared or reflected as ClickHouse JSON, use square brackets to select one segment of a storage-backed subcolumn path at a time:
payload["severity"] compiles to ClickHouse dotted identifier syntax. Each part is quoted separately, for example `events`.`payload`.`severity`. It reads ClickHouse’s stored JSON subcolumn and does not call getSubcolumn. Chain [] or .subcolumn() once for each path segment. Each segment must be a non-empty string. Passing type_ to .subcolumn() wraps the dotted path in a SQL CAST and assigns that type to the SQLAlchemy expression. Without type_, .subcolumn("segment") behaves like ["segment"]. An untyped path has ClickHouse’s Dynamic type. ClickHouse does not allow Dynamic values directly in ORDER BY or GROUP BY. Pass type_ when a subcolumn is used there. For statically typed code, import json_subcolumn from clickhouse_connect.cc_sqlalchemy. The helper also takes one segment at a time and preserves the Python result type from type_:
In this example, type checkers see request_id as ColumnElement[int]. Each segment is quoted independently, including names with spaces or backticks. Backticks do not make a dot literal to ClickHouse JSON path handling. When json_type_escape_dots_in_keys is enabled, use ClickHouse’s %2E encoding for literal dots in keys. Access a key named a.b as payload["a%2Eb"], not payload["a.b"].

ClickHouse query extensions

Import select from clickhouse_connect.cc_sqlalchemy to expose typed ClickHouse methods to static type checkers. The standard sqlalchemy.select also has these methods at runtime.
The ClickHouse Select methods are: SQLAlchemy’s Select.with_hint() is a table hint API. The ClickHouse dialect does not render table hints. An applicable wildcard or clickhousedb hint emits an SAWarning and leaves the generated SQL unchanged. Use final(), sample(), prewhere(), or limit_by() for those ClickHouse clauses. Select.with_statement_hint() is a raw tail directive API. It appends the supplied text to the end of the SELECT without ClickHouse-specific validation. This remains available for trusted static SQL such as SETTINGS max_threads=1:
For ClickHouse settings, prefer execution options so the driver handles the settings separately from the SQL text:
For example, a ClickHouse GLOBAL ANY LEFT JOIN can be chained without nesting a custom FromClause:
Use the explicit Lambda construct for ClickHouse higher-order functions:
The standard SQLAlchemy values() construct compiles to ClickHouse’s VALUES table-function syntax, including when used in a common table expression. The CTE form requires SQLAlchemy 2.0.42 or later, where Values.cte() was added.

Materialized CTEs

By default ClickHouse inlines a common table expression, so a CTE referenced more than once has its body executed once per reference. Pass materialized=True to .cte() to emit WITH <name> AS MATERIALIZED (...), which computes the body once:
The server materializes the CTE only when the keyword is present, enable_materialized_cte=1, and the analyzer is enabled. Set enable_materialized_cte on the statement, connection, or engine as shown in Per-query settings. The analyzer is enabled by default on every server that supports this feature, so setting enable_analyzer=1 explicitly is defensive. enable_materialized_cte is an experimental ClickHouse setting. With enable_materialized_cte=0 or enable_analyzer=0, the query succeeds and returns the same rows. ClickHouse silently ignores MATERIALIZED and inlines the CTE again, so a forgotten setting costs performance without raising anything. Materialized CTEs require ClickHouse 26.3 or later. Older servers reject the keyword as a syntax error. For a statement built with the standard sqlalchemy.select, use the module-level cte() instead. It takes the statement as its first argument and otherwise mirrors Select.cte():
The keyword renders only on the ClickHouse dialect, so a statement shared with another backend compiles unchanged there. ClickHouse does not support recursive materialized CTEs. The SQLAlchemy helpers raise ValueError when recursive=True and materialized=True are both set.

DDL and reflection

ClickHouse Connect provides ClickHouse data types, table engines, dictionary constructs, database DDL, and table reflection. Standalone Variant columns reflect through an internal SQLAlchemy type, and Alembic autogenerate preserves their canonical raw type names without repeated type changes. Geometry and MultiPoint columns reflect as public SQLAlchemy types.
Reflected columns carry server_default for DEFAULT expressions and dialect-specific attributes such as clickhouse_codec, clickhouse_ttl, clickhouse_materialized, and clickhouse_alias when present. String values in DEFAULT, MATERIALIZED, ALIAS, and TTL clauses use ClickHouse string escaping. The same escaping applies to table, dictionary, and column comments, including comments emitted by Alembic. MergeTree key arguments such as order_by, partition_by, primary_key, sample_by, and ttl accept SQLAlchemy column and SQL expressions as well as plain strings.

Inserts and basic ORM use

Core inserts and simple ORM models are supported. Prefer Core inserts for bulk data paths.

Alembic migrations

ClickHouse Connect includes Alembic integration for ClickHouse schema migrations. Install it with:
Import clickhouse_connect.cc_sqlalchemy.alembic in Alembic’s env.py to register the dialect integration. Autogenerate supports common table evolution, including table creation and removal, column add/alter/drop, defaults, and comments. Use manual operations for table and column renames. Review every generated migration before applying it. ClickHouse-specific op.* helpers cover:
  • Data skipping indexes, including add, materialize, and drop operations.
  • Projections, including add, materialize, and drop operations.
  • MergeTree table setting modification and reset.
  • Materialized view creation and removal.
  • Dictionary creation, removal, and reload.
ClickHouse data skipping indexes are not SQLAlchemy indexes. Index, Column(index=True), op.create_index, and op.drop_index are rejected to avoid partial or incorrect DDL. Use op.add_clickhouse_index and op.drop_clickhouse_index. See the complete Alembic worked example. Users migrating from clickhouse-sqlalchemy should also read the migration guide.

Scope and limitations

  • ClickHouse does not provide traditional transactions through this HTTP dialect. engine.begin() and Session.commit() organize Python-side work, but commit and rollback are no-ops on the server.
  • UPDATE, two-phase transactions, sequences, RETURNING, and advanced isolation levels are not implemented by the dialect. Use explicit ClickHouse SQL for server mutations when needed.
  • Column(..., primary_key=True) supplies SQLAlchemy object identity. It does not create a server-side uniqueness constraint. Define sorting and optional primary-key expressions through the table engine.
  • Traditional foreign-key, unique-constraint, and standard index metadata are not available because ClickHouse does not enforce those constraints.
  • ORM relationship management, unit-of-work updates, cascades, and eager or lazy relationship loading are outside the supported ORM scope.
Last modified on September 3, 2026