> ## 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.

> Soporte de SQLAlchemy y Alembic para ClickHouse

# Compatibilidad con SQLAlchemy

ClickHouse Connect incluye el dialecto `clickhousedb` de SQLAlchemy basado en el driver principal. El dialecto síncrono es compatible con SQLAlchemy 1.4.40 y versiones posteriores, incluido SQLAlchemy 2.x, con especial atención a las consultas de Core, el DDL de ClickHouse, la reflexión y las inserciones simples de ORM. El dialecto asíncrono requiere SQLAlchemy 2.0.44 o posterior.

Instale las dependencias de SQLAlchemy con el extra del paquete:

```bash theme={null}
pip install "clickhouse-connect[sqlalchemy]"
```

<h2 id="sqlalchemy-connect">
  Conectar con SQLAlchemy
</h2>

Cree un motor con la URL `clickhousedb://` o `clickhousedb+connect://`:

```python theme={null}
from sqlalchemy import create_engine, text

engine = create_engine(
    "clickhousedb://user:password@host:8123/mydb?compression=zstd"
)

with engine.connect() as conn:
    version = conn.execute(text("SELECT version()")).scalar_one()
    print(version)
```

<h3 id="sqlalchemy-session-ids">
  ID de sesión de ClickHouse
</h3>

De forma predeterminada, cada conexión del grupo, tanto en el dialecto síncrono como en el asíncrono, genera su propio ID de sesión de ClickHouse. Cuando las solicitudes de esa conexión llegan al mismo proceso del servidor de ClickHouse, la configuración modificada con `SET` y las tablas temporales se conservan para esa conexión. El estado de las sesiones con nombre y las comprobaciones de solapamiento dentro de una misma sesión son locales a cada proceso. En un mismo proceso del servidor, una solicitud que se solape con otra del mismo usuario e ID de sesión se rechaza de inmediato con el código de servidor 373, en lugar de ponerse en cola. Si configura un `session_id` fijo, utilice `pool_size=1, max_overflow=0` o serialice el acceso antes de que las solicitudes lleguen a ClickHouse. En ClickHouse Cloud y en otras implementaciones con balanceo de carga, las solicitudes con el mismo ID de sesión pueden llegar a servidores diferentes, por lo que no debe usar un `session_id` fijo como estado distribuido ni como mutex distribuido.

<h3 id="sqlalchemy-async-connections">
  Conexiones asíncronas
</h3>

El dialecto asíncrono requiere SQLAlchemy 2.0.44 o una versión posterior y utiliza el `AsyncClient` nativo de ClickHouse Connect. Instale sus dependencias y cree un motor asíncrono con la URL `clickhousedb+async://`:

```bash theme={null}
pip install "clickhouse-connect[sqlalchemy-async]"
```

```python theme={null}
import asyncio

from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine


async def main():
    engine = create_async_engine(
        "clickhousedb+async://user:password@host:8123/mydb"
    )
    try:
        async with engine.connect() as conn:
            version = (await conn.execute(text("SELECT version()"))).scalar_one()
            print(version)
    finally:
        await engine.dispose()


asyncio.run(main())
```

Los resultados se almacenan temporalmente en el búfer. Los cursores del lado del servidor están deshabilitados, por lo que `AsyncConnection.stream()` lanza `InvalidRequestError`. SQLAlchemy acepta `AsyncSession.stream()`, pero el dialecto almacena en el búfer el resultado completo antes de devolverlo. Para resultados de gran tamaño, utilice los métodos de streaming nativos de `AsyncClient`. El Client nativo subyacente está disponible como `driver_connection` mientras su conexión de SQLAlchemy esté extraída del grupo:

```python theme={null}
async def stream_events(engine):
    async with engine.connect() as conn:
        raw_connection = await conn.get_raw_connection()
        client = raw_connection.driver_connection
        async with await client.query_rows_stream("SELECT * FROM events") as rows:
            async for row in rows:
                print(row)
```

No utilice la conexión de SQLAlchemy de forma concurrente con su Client sin procesar. Finalice los flujos del Client sin procesar antes de salir del bloque de conexión de SQLAlchemy y no conserve el Client sin procesar después de que la conexión vuelva al grupo. SQLAlchemy es responsable del ciclo de vida del Client prestado, por lo que nunca debe llamar a `client.close()` ni a ninguno de sus métodos privados de ciclo de vida. El grupo de SQLAlchemy gestiona la concurrencia de las conexiones. Cada conexión del grupo posee un Client asíncrono nativo y, de forma predeterminada, limita su connector de aiohttp a una conexión en total y a una conexión por host. Establezca `connector_limit`, `connector_limit_per_host` o `keepalive_timeout` en la URL o en `connect_args` para sobrescribir esa configuración de transporte. Con `pool_pre_ping=True`, SQLAlchemy comprueba las conexiones reutilizadas con `SELECT 1` cada vez que se obtiene una conexión del grupo.

Actualmente, las inserciones executemany asíncronas de SQLAlchemy envían una solicitud HTTP por cada conjunto de parámetros en lugar de usar el protocolo Native de inserción masiva del driver. Utilice esta vía solo para lotes pequeños. Para cargas masivas de datos, utilice el patrón de acceso `driver_connection` gestionado por el grupo descrito anteriormente y espere (await) a `client.insert()` antes de devolver la conexión de SQLAlchemy al grupo. Dado que executemany asíncrono utiliza la vinculación de parámetros de consulta, los valores `datetime` sin zona horaria se rigen por `naive_datetime_binding`, no por el ajuste `naive_datetime_insert` que utiliza executemany Native síncrono. Las vinculaciones tipadas `DateTime64` de SQLAlchemy conservan las fracciones de segundo tanto con parámetros del lado del Client como del lado del servidor. Los parámetros sin tipo `%s` o `%(name)s` pasados a `exec_driver_sql()` mantienen el formato predeterminado en segundos enteros para los valores `datetime` sin zona horaria. Utilice valores con zona horaria para que el comportamiento de la zona horaria sea inequívoco. Utilice `client.insert()` para obtener la semántica de inserción masiva Native.

Cree y libere un motor asíncrono en el mismo bucle de eventos en el que se utiliza. Devuelva todas las conexiones obtenidas y, a continuación, espere (await) a `engine.dispose()` durante el apagado y antes de usar el motor desde otro bucle de eventos. Si el bucle propietario del motor ya se ha cerrado, espere a `engine.dispose()` en el bucle actual antes de reutilizarlo. aiohttp puede seguir notificando un transporte no cerrado si la limpieza no comienza hasta después de que el bucle propietario se haya cerrado, por lo que conviene liberar el motor antes de transferirlo siempre que sea posible. `pool_pre_ping=True` no sustituye a la liberación al mover un motor asíncrono con grupo entre bucles de eventos. Para compartir un motor entre bucles de eventos sin retener conexiones vinculadas a un bucle, configure `poolclass=NullPool`. Si la liberación se ejecuta mientras una conexión sigue en uso, el dialecto cierra esa conexión cuando se devuelve o cuando el recolector de basura la elimina. No llame a `engine.sync_engine.dispose()` desde código síncrono: SQLAlchemy no puede esperar ahí la limpieza asíncrona de las conexiones y podría registrar el error en lugar de cerrar los transportes del grupo.

Los parámetros de consulta de la URL pueden incluir ajustes de ClickHouse, opciones del Client de ClickHouse Connect como `compression`, `query_limit` y tiempos de espera, u opciones HTTP/TLS como `ca_cert`. Anteponga `ch_` a un ajuste de ClickHouse para forzar que se trate como ajuste del servidor cuando sea necesario; por ejemplo, `ch_http_max_field_name_size=99999`.

Consulte [Argumentos de conexión y configuración](/es/integrations/language-clients/python/driver-api#connection-arguments) para ver las opciones del Client disponibles.

Ejecute los helpers síncronos de SQLAlchemy, como DDL e inspección, mediante `AsyncConnection.run_sync()`:

```python theme={null}
from sqlalchemy import inspect


async def prepare_schema(engine, metadata):
    async with engine.begin() as conn:
        await conn.run_sync(metadata.create_all)
        return await conn.run_sync(
            lambda sync_conn: inspect(sync_conn).get_table_names()
        )
```

<h3 id="sqlalchemy-per-query-settings">
  Ajustes por consulta
</h3>

Pase los ajustes de ClickHouse mediante las opciones de ejecución de SQLAlchemy. Los ajustes se pueden establecer en un motor, una conexión o una sentencia. El valor de una sentencia tiene prioridad sobre un valor de conexión o de motor con la misma clave.

```python theme={null}
from sqlalchemy import text

stmt = text("SELECT getSetting('max_threads')").execution_options(
    settings={"max_threads": 2}
)

with engine.connect() as conn:
    value = conn.execute(stmt).scalar_one()
```

<h3 id="sqlalchemy-per-query-read-formats">
  Formatos de lectura por consulta
</h3>

Configure los formatos de lectura de ClickHouse en un motor, una conexión o una sentencia mediante las opciones de ejecución de SQLAlchemy con `query_formats`. Los formatos de la sentencia se aplican primero y, por tanto, sobrescriben las claves y los comodines coincidentes de la conexión o el motor.

```python theme={null}
from sqlalchemy import text

stmt = text("SELECT user_uuid FROM users").execution_options(
    query_formats={"UUID": "string"}
)

with engine.connect() as conn:
    rows = conn.execute(stmt).all()
```

<h3 id="sqlalchemy-error-handling">
  Gestión de errores
</h3>

Los errores que genera el driver a través de una conexión de SQLAlchemy utilizan las clases DB-API exportadas desde `clickhouse_connect.dbapi`. Son los mismos objetos de clase que las clases correspondientes de `clickhouse_connect.driver.exceptions`, por lo que SQLAlchemy los envuelve en la subclase `sqlalchemy.exc.DBAPIError` correspondiente. `StreamFailureError` es un `OperationalError` y se envuelve como `sqlalchemy.exc.OperationalError`.

Si una cancelación por parte del llamador puede interrumpir una llamada explícita a `AsyncConnection.invalidate()`, ejecute la invalidación en una tarea propia y espere a que finalice antes de propagar la cancelación. De este modo, SQLAlchemy puede completar la gestión interna del registro de conexión:

```python theme={null}
import asyncio


async def invalidate_safely(connection):
    invalidate_task = asyncio.create_task(connection.invalidate())
    cancellation = None
    while not invalidate_task.done():
        try:
            await asyncio.wait({invalidate_task})
        except asyncio.CancelledError as ex:
            cancellation = ex
    if cancellation is not None:
        try:
            invalidate_task.result()
        finally:
            raise cancellation
    invalidate_task.result()
```

No utilice la conexión mientras su tarea de invalidación siga en ejecución. Si se cancela una llamada directa a `await connection.invalidate()` y `connection.invalidated` sigue siendo false, vuelva a ejecutar `await` sobre `connection.invalidate()` para completar la limpieza antes de usar o cerrar la conexión.

<h3 id="sqlalchemy-server-side-parameters">
  Parámetros del lado del servidor
</h3>

SQLAlchemy normalmente procesa los parámetros del lado del cliente. Para usar parámetros del lado del servidor de ClickHouse, actívelos al crear el motor:

```python theme={null}
engine = create_engine(
    "clickhousedb://user:password@host:8123/mydb",
    server_side_params=True,
)
```

Use el mismo argumento `server_side_params=True` con `create_async_engine()` para el dialecto asíncrono.

En este modo, cada valor vinculado debe tener un tipo de SQLAlchemy compatible con ClickHouse. Las listas `IN` compatibles se convierten en parámetros `Array` tipados de ClickHouse. El compilador genera `CompileError` cuando no puede deducir un tipo compatible ni procesar de forma segura una vinculación.

Los nombres de vinculación deben ser nombres BareWord ASCII de ClickHouse. Los nombres que empiezan y terminan con `$` se rechazan porque el driver principal los reserva para parámetros de consulta binarios sin procesar.

<h2 id="sqlalchemy-core-queries">
  Consultas de SQLAlchemy Core
</h2>

El dialecto admite consultas `SELECT` de SQLAlchemy Core con `JOIN`, filtros, ordenación, límites y `OFFSET`, `DISTINCT` y consultas compuestas.

`union()`, `intersect()` y `except_()` de SQLAlchemy se compilan en `UNION DISTINCT`, `INTERSECT DISTINCT` y `EXCEPT DISTINCT` de ClickHouse. Sus equivalentes `union_all()`, `intersect_all()` y `except_all()` se compilan en los operadores `ALL` correspondientes. Esta correspondencia explícita preserva la semántica de duplicados de SQLAlchemy independientemente de los valores predeterminados de las operaciones de conjuntos de ClickHouse.

```python theme={null}
from sqlalchemy import MetaData, Table, select

metadata = MetaData(schema="mydb")
users = Table("users", metadata, autoload_with=engine)
orders = Table("orders", metadata, autoload_with=engine)
events = Table("events", metadata, autoload_with=engine)

stmt = (
    select(users.c.name, orders.c.product)
    .select_from(users.join(orders, users.c.id == orders.c.user_id))
    .order_by(users.c.name)
    .limit(10)
)

with engine.connect() as conn:
    rows = conn.execute(stmt).all()
```

Se admite la eliminación ligera `DELETE` y requiere una cláusula `WHERE` explícita:

```python theme={null}
from sqlalchemy import delete

stmt = delete(users).where(users.c.name.like("%temporary%"))
with engine.connect() as conn:
    conn.execute(stmt)
```

<h3 id="sqlalchemy-literal-rendering">
  Representación de literales
</h3>

Cuando SQLAlchemy inserta en línea un valor vinculado mediante `literal_binds` o `literal_execute`, el dialecto utiliza el entrecomillado de ClickHouse para los tipos String genéricos y los tipos de ClickHouse. Esto también se aplica a través de envolturas `TypeDecorator` y selecciones `with_variant()`. Los valores String conservan los signos de porcentaje y las barras invertidas, incluso cuando quedan otros parámetros vinculados.

Los valores `datetime` de Python con un tipo SQLAlchemy `DateTime64` de ClickHouse conservan sus microsegundos en los parámetros del lado del Client y en los literales en línea, incluidos los valores Nullable y los valores anidados en arrays y tuplas. ClickHouse aplica la precisión declarada. El tipo `datetime` de Python admite hasta seis dígitos fraccionarios. Los valores `DateTime` simples mantienen el formato de segundos enteros. En una sentencia `text()`, especifique el tipo explícitamente con `bindparam("ts", type_=DateTime64(6))` para conservar las fracciones de segundo.

Los tipos de columna de SQLAlchemy deben coincidir con el esquema del servidor. Si se declara `DateTime64` sobre una columna `DateTime` del servidor, se generan fracciones de segundo, lo que puede provocar errores de conversión durante la inserción y en las comparaciones `IN`.

En SQLAlchemy 2.x, los literales en línea de tipos genéricos `sqlalchemy.ARRAY` que contienen elementos `Tuple` de ClickHouse requieren `dimensions=1` (o el número de dimensiones superior correspondiente en el caso de arrays anidados) para que SQLAlchemy trate cada tupla como un único elemento. SQLAlchemy 1.4 no admite literales en línea para tipos genéricos `ARRAY`.

Si se reutiliza un parámetro datetime con nombre, cada aparición necesita un tipo de vinculación `DateTime64` compatible para conservar las fracciones. Una aparición sin tipo o con un tipo en conflicto mantiene el formato de segundos enteros. Establezca `type_=DateTime64(6)` en cada `bindparam` o utilice nombres de parámetro distintos con los tipos adecuados.

<h3 id="sqlalchemy-json-type-hints">
  Indicaciones de tipo en JSON
</h3>

Declara rutas JSON tipadas mediante la correspondencia `typed_paths`. El tipo de una ruta puede ser una clase de tipo de ClickHouse SQLAlchemy, una instancia configurada o una cadena con un nombre de tipo de ClickHouse. Las cadenas con nombres de tipo admiten tipos que carecen de constructor en SQLAlchemy, como `Dynamic`, y también pueden usarse para expresiones de tipo configuradas complejas. Además, conservan los nombres en un `Tuple` con nombre.

Las cadenas con nombres de tipo pueden contener tipos JSON anidados configurados, como ``Array(JSON(`child` UInt32))``. Los nombres de tipo de ClickHouse reconocidos no distinguen entre mayúsculas y minúsculas en estas cadenas y se emiten con su capitalización canónica. Cada cadena debe contener una única expresión de tipo completa. El texto sobrante al final y los argumentos JSON anidados mal formados se rechazan.

Un `Tuple()` vacío no es compatible como ruta JSON tipada, ya que ClickHouse no puede serializarlo a través del Native format de una JSON column. El driver principal admite `Tuple()` en columnas de consulta e inserción en cualquier posición, incluso anidado en tuplas posicionales o con nombre, dentro de `Array` y como `Nullable(Tuple())` allí donde el servidor lo habilite.

```python theme={null}
from sqlalchemy import Column, MetaData, Table

from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import JSON, UInt32

events = Table(
    "events",
    MetaData(),
    Column(
        "payload",
        JSON(
            typed_paths={
                "event.id": UInt32,
                "details": "Tuple(id UInt32, label Nullable(String))",
                "attributes": "Variant(String, Array(String))",
            },
            max_dynamic_paths=256,
            max_dynamic_types=16,
            skip_paths=["internal.debug"],
            skip_regexps=[r"^private\."],
        ),
    ),
)
```

Para rutas que son identificadores simples de Python, los argumentos con nombre son una forma abreviada de `typed_paths`; por ejemplo, `JSON(user_id=UInt32)`. Use `typed_paths` para rutas con puntos, espacios, comillas invertidas, puntos codificados como `%2E` o nombres que coincidan con opciones del constructor. Se admite una ruta tipada llamada `SKIP` a través de la correspondencia. Las claves de `typed_paths` y los valores de `skip_paths` son nombres decodificados. Las comillas invertidas y las comillas dobles iniciales o finales se tratan como caracteres literales de la ruta, y no como entrecomillado SQL ya aplicado. Dentro de una cadena de tipo sin procesar, las comillas invertidas y las comillas dobles son sintaxis de identificadores de ClickHouse.

Se pueden configurar hasta 1000 rutas tipadas. `max_dynamic_paths` acepta valores de 0 a 10000. `max_dynamic_types` acepta valores de 0 a 254. Estos rangos también se aplican dentro de las cadenas de tipo JSON anidadas sin procesar. Los valores predeterminados explícitos del servidor, 1024 y 32, se omiten del DDL generado. Las rutas de omisión simples se deduplican. Python no valida las cadenas de expresiones regulares porque ClickHouse utiliza la sintaxis RE2. Las expresiones regulares duplicadas se conservan.

Una ruta de omisión simple no puede llamarse exactamente `REGEXP`, porque ClickHouse reserva ese token para `SKIP REGEXP`. Nombres como `REGEXP_foo` siguen siendo válidos. En una cadena de tipo JSON sin procesar, un operando `SKIP` simple debe ser un identificador de ClickHouse o un identificador compuesto separado por puntos. Un identificador compuesto sin comillas no puede empezar por `REGEXP`; entrecomille ese primer componente cuando forme parte de los datos de la ruta. `SKIP REGEXP` debe tener un único literal de cadena entre comillas simples. Entrecomille las partes del identificador con comillas invertidas o comillas dobles cuando contengan espacios o signos de puntuación. Las indicaciones de tipo JSON sin procesar admiten `Variant(...)`; `Variant` por sí solo no tiene un constructor público de SQLAlchemy. Los miembros de `Variant` se ordenan y deduplican según los mismos nombres canónicos que utiliza ClickHouse.

El constructor ordena los argumentos en la misma forma canónica que devuelve ClickHouse. Los tipos reflejados, las copias de tipos de SQLAlchemy y la autogeneración de Alembic conservan la configuración.

<h3 id="sqlalchemy-json-subcolumns">
  Subcolumnas JSON
</h3>

Para una columna declarada o representada como `JSON` de ClickHouse, use corchetes para seleccionar un segmento cada vez de una ruta de subcolumna respaldada por almacenamiento:

```python theme={null}
from sqlalchemy import Column, MetaData, Table, select

from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import JSON, UInt32

events = Table(
    "events",
    MetaData(),
    Column("payload", JSON),
)

request_id = events.c.payload["context"]["request"].subcolumn(
    "id",
    type_=UInt32,
)

stmt = select(
    events.c.payload["severity"].label("severity"),
    request_id.label("request_id"),
)
```

`payload["severity"]` se compila en la sintaxis de identificadores con puntos de ClickHouse. Cada parte se entrecomilla por separado; por ejemplo, `` `events`.`payload`.`severity` ``. Lee la subcolumna JSON almacenada en ClickHouse y no llama a `getSubcolumn`. Encadene `[]` o `.subcolumn()` una vez por cada segmento de la ruta. Cada segmento debe ser una cadena no vacía.

Al pasar `type_` a `.subcolumn()`, la ruta con puntos se encapsula en un `CAST` de SQL y se asigna ese tipo a la expresión de SQLAlchemy. Sin `type_`, `.subcolumn("segment")` se comporta como `["segment"]`.

Una ruta sin tipo tiene el tipo `Dynamic` de ClickHouse. ClickHouse no permite usar valores `Dynamic` directamente en `ORDER BY` ni en `GROUP BY`. Pase `type_` cuando se use una subcolumna en esos casos.

Para código con tipado estático, importe `json_subcolumn` desde `clickhouse_connect.cc_sqlalchemy`. Este auxiliar también acepta un segmento cada vez y conserva el tipo de resultado de Python de `type_`:

```python theme={null}
from clickhouse_connect.cc_sqlalchemy import json_subcolumn

context = json_subcolumn(events.c.payload, "context")
request = json_subcolumn(context, "request")
request_id = json_subcolumn(request, "id", type_=UInt32)
```

En este ejemplo, los verificadores de tipos interpretan `request_id` como `ColumnElement[int]`.

Cada segmento se delimita con comillas invertidas de forma independiente, incluidos los nombres con espacios o comillas invertidas. Las comillas invertidas no hacen que un punto se trate como literal en el manejo de rutas JSON de ClickHouse. Cuando `json_type_escape_dots_in_keys` está habilitado, use la codificación `%2E` de ClickHouse para los puntos literales en las claves. Acceda a una clave denominada `a.b` como `payload["a%2Eb"]`, no como `payload["a.b"]`.

<h3 id="sqlalchemy-query-extensions">
  Extensiones de consultas de ClickHouse
</h3>

Importa `select` desde `clickhouse_connect.cc_sqlalchemy` para exponer métodos tipados de ClickHouse a los verificadores estáticos de tipos. El `sqlalchemy.select` estándar también dispone de estos métodos en tiempo de ejecución.

```python theme={null}
from clickhouse_connect.cc_sqlalchemy import select

stmt = (
    select(events.c.user_id, events.c.event_type)
    .final()
    .prewhere(events.c.event_date >= "2026-01-01")
    .sample(0.1)
    .limit_by([events.c.user_id], 3)
)
```

Los métodos `Select` de ClickHouse son:

| Method | Funcionalidad SQL |
| - | - |
| `.final()` | `FINAL` para una tabla |
| `.sample(value)` | `SAMPLE`, usando una fracción, un recuento de filas o una expresión |
| `.prewhere(expression)` | `PREWHERE`; las llamadas repetidas se combinan con `AND` |
| `.limit_by(columns, limit, offset=None)` | `LIMIT ... BY` |
| `.array_join(...)` | `ARRAY JOIN` |
| `.left_array_join(...)` | `LEFT ARRAY JOIN` |
| `.ch_join(...)` | JOIN de ClickHouse con opciones `strictness`, `distribution`, `using` y `cross` |
| `.cte(name, materialized=True)` | `WITH name AS MATERIALIZED (...)` |

`Select.with_hint()` de SQLAlchemy es una API de sugerencias de tabla. El dialecto de ClickHouse no genera sugerencias de tabla. Una sugerencia aplicable con comodín o `clickhousedb` emite un `SAWarning` y no modifica el SQL generado. Utilice `final()`, `sample()`, `prewhere()` o `limit_by()` para esas cláusulas de ClickHouse.

`Select.with_statement_hint()` es una API de directivas finales sin procesar. Añade el texto proporcionado al final del `SELECT` sin validación específica de ClickHouse. Sigue disponible para SQL estático de confianza, como `SETTINGS max_threads=1`:

```python theme={null}
stmt = select(events.c.id).with_statement_hint("SETTINGS max_threads=1")
```

Para los ajustes de ClickHouse, utilice preferentemente opciones de ejecución para que el driver los gestione por separado del texto SQL:

```python theme={null}
stmt = select(events.c.id).execution_options(settings={"max_threads": 1})
```

Por ejemplo, un `GLOBAL ANY LEFT JOIN` de ClickHouse puede encadenarse sin anidar un `FromClause` personalizado:

```python theme={null}
stmt = (
    select(events.c.id, users.c.name)
    .select_from(events)
    .ch_join(
        users,
        events.c.user_id == users.c.id,
        isouter=True,
        strictness="ANY",
        distribution="GLOBAL",
    )
)
```

Utilice la construcción `Lambda` explícita para las funciones de orden superior de ClickHouse:

```python theme={null}
from sqlalchemy import column, func

from clickhouse_connect.cc_sqlalchemy import Lambda, select

stmt = select(
    func.arrayMap(
        Lambda("x", column("x") * 2),
        events.c.metrics,
    ).label("doubled")
)
```

La construcción estándar `values()` de SQLAlchemy se compila como la sintaxis de función de tabla `VALUES` de ClickHouse, incluso cuando se usa en una expresión de tabla común. La forma CTE requiere SQLAlchemy 2.0.42 o una versión posterior, en la que se añadió `Values.cte()`.

<h3 id="sqlalchemy-materialized-ctes">
  CTE materializadas
</h3>

De forma predeterminada, ClickHouse inserta en línea una expresión de tabla común, por lo que el cuerpo de una CTE a la que se hace referencia más de una vez se ejecuta una vez por cada referencia. Pase `materialized=True` a `.cte()` para generar `WITH <name> AS MATERIALIZED (...)`, lo que calcula el cuerpo una sola vez:

```python theme={null}
from sqlalchemy import func

from clickhouse_connect.cc_sqlalchemy import select

ranked = (
    select(book.c.book_id, func.row_number().over(order_by=book.c.score.desc()).label("result_rank"))
    .where(book.c.genre == "sci-fi")
    .order_by(book.c.score.desc())
    .limit(100)
    .cte("ranked", materialized=True)
)

stmt = (
    select(book.c.book_id, ranked.c.result_rank)
    .select_from(book)
    .ch_join(ranked, book.c.book_id == ranked.c.book_id, strictness="ANY")
    .where(book.c.book_id.in_(select(ranked.c.book_id)))
    .execution_options(settings={"enable_materialized_cte": 1, "enable_analyzer": 1})
)
```

El servidor solo materializa la CTE cuando está presente la palabra clave, `enable_materialized_cte=1`, y el analizador está habilitado. Configure `enable_materialized_cte` en la sentencia, la conexión o el motor, como se muestra en [Ajustes por consulta](#sqlalchemy-per-query-settings). El analizador está habilitado de forma predeterminada en todos los servidores compatibles con esta funcionalidad, por lo que establecer explícitamente `enable_analyzer=1` es una medida de precaución. `enable_materialized_cte` es un ajuste experimental de ClickHouse. Con `enable_materialized_cte=0` o `enable_analyzer=0`, la consulta se ejecuta correctamente y devuelve las mismas filas. ClickHouse ignora silenciosamente `MATERIALIZED` y vuelve a insertar la CTE, por lo que olvidar un ajuste afecta al rendimiento sin emitir ningún aviso. Las CTE materializadas requieren ClickHouse 26.3 o una versión posterior. Los servidores anteriores rechazan la palabra clave con un error de sintaxis.

Para una sentencia creada con el `sqlalchemy.select` estándar, use en su lugar `cte()` a nivel de módulo. Recibe la sentencia como primer argumento y, por lo demás, se comporta como `Select.cte()`:

```python theme={null}
from sqlalchemy import select as sa_select

from clickhouse_connect.cc_sqlalchemy import cte

ranked = cte(sa_select(book.c.book_id), "ranked", materialized=True)
```

La palabra clave solo se representa en el dialecto de ClickHouse, por lo que una sentencia compartida con otro backend se compila allí sin modificaciones.

ClickHouse no admite CTE materializadas recursivas. Las funciones auxiliares de SQLAlchemy generan un `ValueError` cuando se establecen `recursive=True` y `materialized=True`.

<h2 id="sqlalchemy-ddl-reflection">
  DDL y reflexión
</h2>

ClickHouse Connect proporciona tipos de datos de ClickHouse, motores de tablas, definiciones de diccionarios, DDL de bases de datos y reflexión de tablas.

Las columnas `Variant` independientes se reflejan mediante un tipo interno de SQLAlchemy, y la autogeneración de Alembic conserva sus nombres de tipo canónicos sin procesar, sin cambios de tipo repetidos. Las columnas `Geometry` y `MultiPoint` se reflejan como tipos públicos de SQLAlchemy.

```python theme={null}
import sqlalchemy as db
from sqlalchemy import MetaData

from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import DateTime64, String, UInt32
from clickhouse_connect.cc_sqlalchemy.ddl.custom import CreateDatabase
from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree

with engine.connect() as conn:
    conn.execute(CreateDatabase("example_db", exists_ok=True))

    metadata = MetaData(schema="example_db")
    events = db.Table(
        "events",
        metadata,
        db.Column("id", UInt32, primary_key=True),
        db.Column("user", String),
        db.Column("created_at", DateTime64(3)),
        MergeTree(order_by="id"),
    )
    events.create(conn)

    reflected = db.Table("events", MetaData(schema="example_db"), autoload_with=conn)
    assert reflected.engine is not None
```

Las columnas reflejadas incluyen `server_default` para las expresiones `DEFAULT` y atributos específicos del dialecto, como `clickhouse_codec`, `clickhouse_ttl`, `clickhouse_materialized` y `clickhouse_alias`, cuando están presentes.

Los valores String de las cláusulas `DEFAULT`, `MATERIALIZED`, `ALIAS` y `TTL` utilizan el mecanismo de escape de cadenas de ClickHouse. El mismo mecanismo de escape se aplica a los comentarios de tablas, diccionarios y columnas, incluidos los comentarios generados por Alembic.

Los argumentos de clave de MergeTree, como `order_by`, `partition_by`, `primary_key`, `sample_by` y `ttl`, aceptan columnas y expresiones SQL de SQLAlchemy, así como cadenas simples.

`Memory()`, `Log()`, `StripeLog()`, `TinyLog()`, `Null()` y `Set()` pueden usarse sin argumentos y se conservan intactos en un ciclo completo con la autogeneración de Alembic. El argumento de diccionario existente sigue siendo compatible. Use `settings={...}` para proporcionar la configuración del motor.

`SummingMergeTree` y `ReplicatedSummingMergeTree` aceptan un argumento `columns` opcional que solo puede pasarse por palabra clave. Los argumentos posicionales existentes conservan su significado, por lo que `SummingMergeTree("id")` sigue estableciendo `ORDER BY id`.

```python theme={null}
from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import SummingMergeTree

engine_clause = SummingMergeTree("id", columns=("delta", "n_tx"))
# Sum delta and n_tx for rows with the same id.
```

Pase una cadena, una columna de SQLAlchemy, un atributo de columna mapeado o una lista o tupla no vacía de esos valores. Los elementos de cadena de las listas y tuplas se entrecomillan como identificadores. Una cadena escalar proporciona SQL sin procesar, como `"delta"` o `"(delta, n_tx)"`. El servidor requiere identificadores para estas columnas. Omita `columns` para que ClickHouse elija las columnas que se van a sumar. La reflexión y la autogeneración de Alembic conservan una lista de columnas explícita.

<h2 id="sqlalchemy-inserts">
  Inserciones y uso básico de ORM
</h2>

Se admiten las inserciones con Core y los modelos ORM sencillos. Para el dialecto síncrono, prefiera las inserciones con `executemany` de Core para las rutas de datos de gran volumen compatibles. Para inserciones masivas asíncronas, utilice la vía nativa `AsyncClient.insert()` descrita en [Conexiones asíncronas](#sqlalchemy-async-connections).

```python theme={null}
with engine.connect() as conn:
    conn.execute(
        events.insert(),
        [
            {"id": 13, "user": "user_1"},
            {"id": 79, "user": "user_2"},
        ],
    )
```

Para el dialecto síncrono, las inserciones simples de Core con `executemany` generadas por el compilador de SQLAlchemy utilizan una única inserción masiva Native. En el modo asíncrono, executemany envía una solicitud por cada conjunto de parámetros, tal como se describe en [Conexiones asíncronas](#sqlalchemy-async-connections). El SQL sin procesar y las inserciones con expresiones u otras semánticas que no pueden enrutarse de forma segura conservan el SQL original y se ejecutan una vez por cada conjunto de parámetros. Si falla un conjunto de parámetros posterior, las filas escritas por los conjuntos anteriores permanecen confirmadas.

Las sentencias explícitas de varias filas `insert(events).values([...])` funcionan con filas de diccionario, tuplas en el orden de las columnas de la tabla y expresiones SQL por fila. Pandas `to_sql(method="multi")` utiliza esta forma. Inserta las filas, pero devuelve `0` porque las sentencias INSERT textuales informan de un recuento de filas de `0` a través del cursor de DB-API. SQLAlchemy determina la lista de columnas a partir de la primera fila. Se ignoran las claves de diccionario adicionales en filas posteriores y los valores de tupla que queden fuera de esa lista de columnas. Si a una fila posterior le falta un valor seleccionado, la compilación falla. Asigne las mismas columnas a todas las filas.

Con los límites predeterminados de formularios HTTP en ClickHouse 26.4 y versiones posteriores, `server_side_params=True` solo es adecuado para lotes explícitos pequeños, por debajo de unos 1000 valores vinculados y con holgura para otros campos. La configuración del servidor permite elevar este límite. Para lotes simples de gran tamaño con el dialecto síncrono, pase las filas como segundo argumento de `execute()` para que el driver pueda utilizar su ruta de inserción masiva Native. Para datos masivos asíncronos, use `await` con el método nativo `AsyncClient.insert()`.

```python theme={null}
import sqlalchemy as db
from sqlalchemy import MetaData
from sqlalchemy.orm import Session, declarative_base

from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import String, UInt32
from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree

Base = declarative_base(metadata=MetaData(schema="example_db"))


class User(Base):
    __tablename__ = "users"
    __table_args__ = (MergeTree(order_by=["id"]),)

    id = db.Column(UInt32, primary_key=True)
    name = db.Column(String)


Base.metadata.create_all(engine)

with Session(engine) as session:
    session.add(User(id=13, name="user_1"))
    session.bulk_save_objects([User(id=79, name="user_2")])
    session.commit()
```

<h2 id="sqlalchemy-alembic">
  Migraciones con Alembic
</h2>

ClickHouse Connect incluye integración con Alembic para las migraciones de esquemas de ClickHouse. Instálalo con:

```bash theme={null}
pip install "clickhouse-connect[alembic]"
```

Para ejecutar migraciones con el dialecto asíncrono, instala ambos extras:

```bash theme={null}
pip install "clickhouse-connect[alembic,sqlalchemy-async]"
```

Crea un proyecto asíncrono de Alembic y, a continuación, sustituye el entorno generado por el ejemplo compatible con ClickHouse:

```bash theme={null}
alembic init -t async alembic
```

El archivo `alembic.ini` generado usa `script_location = %(here)s/alembic`. Mantén esa configuración si el directorio de migraciones se llama `alembic`; de lo contrario, cámbiala por el directorio que se haya pasado a `alembic init`. Reemplaza `alembic/env.py` por el [ejemplo de `env.py` de Alembic asíncrono](https://github.com/ClickHouse/clickhouse-connect/blob/main/examples/alembic_async/env.py) incluido en el repositorio y, a continuación, establece `sqlalchemy.url` en `alembic.ini`.

Importa `clickhouse_connect.cc_sqlalchemy.alembic` en el archivo `env.py` de Alembic para registrar la integración del dialecto. La autogeneración admite las operaciones habituales de evolución de tablas, incluida la creación y eliminación de tablas, la adición/modificación/eliminación de columnas, los valores predeterminados y los comentarios. Usa operaciones manuales para cambiar el nombre de tablas y columnas. Revisa cada migración generada antes de aplicarla.

Las funciones de migración de Alembic siguen siendo síncronas. Un entorno asíncrono crea un `AsyncEngine`, abre una `AsyncConnection` y pasa la función de migración síncrona a `await connection.run_sync(...)`. Las migraciones en modo offline llaman directamente a `context.configure(url=..., literal_binds=True, dialect_opts={"paramstyle": "named"})` y no crean ningún motor. El [ejemplo de `env.py` de Alembic asíncrono](https://github.com/ClickHouse/clickhouse-connect/blob/main/examples/alembic_async/env.py) incluido en el repositorio contempla ambos modos y lee la URL de conexión a través de la configuración estándar `sqlalchemy.url` de Alembic. Además, mantiene los hooks y las opciones de Alembic para ClickHouse del ejemplo completo, incluidos `include_object`, `make_include_name(...)`, `clickhouse_writer` y `version_table`. No uses `engine.sync_engine` para ejecutar o liberar migraciones asíncronas.

Las funciones auxiliares específicas de ClickHouse `op.*` cubren:

* índices de omisión de datos, incluidas las operaciones de agregar, materializar y eliminar.
* proyecciones, incluidas las operaciones de agregar, materializar y eliminar.
* modificación y restablecimiento de la configuración de tablas MergeTree.
* creación y eliminación de vistas materializadas.
* creación, eliminación y recarga de diccionarios.

Los índices de omisión de datos de ClickHouse no son índices de SQLAlchemy. `Index`, `Column(index=True)`, `op.create_index` y `op.drop_index` se rechazan para evitar DDL parciales o incorrectos. Usa `op.add_clickhouse_index` y `op.drop_clickhouse_index`.

Consulta el [ejemplo completo de Alembic](https://github.com/ClickHouse/clickhouse-connect/blob/main/clickhouse_connect/cc_sqlalchemy/alembic/WORKED_EXAMPLE.md). Los usuarios que migren desde `clickhouse-sqlalchemy` también deberían leer la [guía de migración](https://github.com/ClickHouse/clickhouse-connect/blob/main/clickhouse_connect/cc_sqlalchemy/MIGRATING_FROM_CLICKHOUSE_SQLALCHEMY.md).

<h2 id="scope-and-limitations">
  Alcance y limitaciones
</h2>

* ClickHouse no ofrece transacciones tradicionales a través de este dialecto HTTP. `engine.begin()` y `Session.commit()` organizan el trabajo del lado de Python, pero commit y rollback no tienen efecto en el servidor.
* El dialecto no implementa `UPDATE`, transacciones de dos fases, secuencias, `RETURNING` ni niveles avanzados de aislamiento. Use ClickHouse SQL explícito para las mutations del servidor cuando sea necesario.
* `Column(..., primary_key=True)` proporciona la identidad de objeto de SQLAlchemy. No crea una restricción de unicidad del lado del servidor. Defina las expresiones de ordenación y, opcionalmente, de clave primaria mediante el motor de tabla.
* Los metadatos tradicionales de claves foráneas, restricciones de unicidad e índices estándar no están disponibles porque ClickHouse no hace cumplir esas restricciones.
* La gestión de relaciones del ORM, las actualizaciones de unidad de trabajo, las cascadas y la carga de relaciones inmediata o diferida quedan fuera del alcance compatible del ORM.
