Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Instalar chDB para Python

Requisitos

  • Python 3.8+
  • Plataformas compatibles: macOS y Linux (x86_64 y ARM64)

Instalación

pip install chdb

Uso

Interfaz de línea de comandos

Ejecute consultas SQL directamente desde la línea de comandos:

# Consulta básica
python3 -m chdb "SELECT 1, 'abc'" Pretty

# Consulta con formato
python3 -m chdb "SELECT version()" JSON

Uso básico de Python

import chdb

# Consulta simple
result = chdb.query("SELECT 1 as id, 'Hello World' as message", "CSV")
print(result)

# Obtener estadísticas de la consulta
print(f"Rows read: {result.rows_read()}")
print(f"Bytes read: {result.bytes_read()}")
print(f"Execution time: {result.elapsed()} seconds")

API basada en conexiones (recomendada)

Para mejorar la gestión de recursos y el rendimiento:

import chdb

# Create connection (":memory:" is a temporary, non-persistent database)
conn = chdb.connect(":memory:")
# Or use file-based: conn = chdb.connect("mydata.db")

# Create cursor for query execution
cur = conn.cursor()

# Execute queries
cur.execute("SELECT number, toString(number) as str FROM system.numbers LIMIT 3")

# Fetch results in different ways
print(cur.fetchone())    # Single row: (0, '0')
print(cur.fetchmany(2))  # Multiple rows: ((1, '1'), (2, '2'))

# Get metadata
print(cur.column_names())  # ['number', 'str']
print(cur.column_types())  # ['UInt64', 'String']

# Use cursor as iterator
for row in cur:
    print(row)

# Always close resources
cur.close()
conn.close()

Métodos de ingestión de datos

Fuentes de datos basadas en archivos

chDB admite más de 70 formatos de datos para consultar archivos directamente:

import chdb

# Prepara tus datos
# ...

# Consultar archivos Parquet
result = chdb.query("""
    SELECT customer_id, sum(amount) as total
    FROM file('sales.parquet', Parquet) 
    GROUP BY customer_id 
    ORDER BY total DESC 
    LIMIT 10
""", 'JSONEachRow')

# Consultar CSV con encabezados
result = chdb.query("""
    SELECT * FROM file('data.csv', CSVWithNames) 
    WHERE column1 > 100
""", 'DataFrame')

# Múltiples formatos de archivo
result = chdb.query("""
    SELECT * FROM file('logs*.jsonl', JSONEachRow)
    WHERE timestamp > '2024-01-01'
""", 'Pretty')

Ejemplos de formato de salida

# DataFrame para análisis
df = chdb.query('SELECT * FROM system.numbers LIMIT 5', 'DataFrame')
print(type(df))  # <class 'pandas.core.frame.DataFrame'>

# tabla Arrow para interoperabilidad  
arrow_table = chdb.query('SELECT * FROM system.numbers LIMIT 5', 'ArrowTable')
print(type(arrow_table))  # <class 'pyarrow.lib.Table'>

# JSON para APIs
json_result = chdb.query('SELECT version()', 'JSON')
print(json_result)

# Formato Pretty para depuración
pretty_result = chdb.query('SELECT * FROM system.numbers LIMIT 3', 'Pretty')
print(pretty_result)

Operaciones con DataFrame

API heredada de DataFrame

import chdb.dataframe as cdf
import pandas as pd

# Unir múltiples DataFrames
df1 = pd.DataFrame({'a': [1, 2, 3], 'b': ["one", "two", "three"]})
df2 = pd.DataFrame({'c': [1, 2, 3], 'd': ["①", "②", "③"]})

result_df = cdf.query(
    sql="SELECT * FROM __tbl1__ t1 JOIN __tbl2__ t2 ON t1.a = t2.c",
    tbl1=df1, 
    tbl2=df2
)
print(result_df)

# Consultar el DataFrame resultante
summary = result_df.query('SELECT b, sum(a) FROM __table__ GROUP BY b')
print(summary)
import chdb
import pandas as pd
import pyarrow as pa

# Consultar DataFrame de Pandas directamente
df = pd.DataFrame({
    "customer_id": [1, 2, 3, 1, 2],
    "product": ["A", "B", "A", "C", "A"],
    "amount": [100, 200, 150, 300, 250],
    "metadata": [
        {'category': 'electronics', 'priority': 'high'},
        {'category': 'books', 'priority': 'low'},
        {'category': 'electronics', 'priority': 'medium'},
        {'category': 'clothing', 'priority': 'high'},
        {'category': 'books', 'priority': 'low'}
    ]
})

# Consulta directa de DataFrame con soporte JSON
result = chdb.query("""
    SELECT 
        customer_id,
        sum(amount) as total_spent,
        toString(metadata.category) as category
    FROM Python(df)
    WHERE toString(metadata.priority) = 'high'
    GROUP BY customer_id, toString(metadata.category)
    ORDER BY total_spent DESC
""").show()

# Consultar tabla Arrow
arrow_table = pa.table({
    "id": [1, 2, 3, 4],
    "name": ["Alice", "Bob", "Charlie", "David"],
    "score": [98, 89, 86, 95]
})

chdb.query("""
    SELECT name, score
    FROM Python(arrow_table)
    ORDER BY score DESC
""").show()

Sesiones con estado

Las sesiones conservan el estado de la consulta a lo largo de múltiples operaciones, lo que permite flujos de trabajo complejos:

from chdb import session

# Sesión temporal (limpieza automática)
sess = session.Session()

# O sesión persistente con ruta específica
# sess = session.Session("/path/to/data")

# Crear base de datos y tablas
sess.query("CREATE DATABASE IF NOT EXISTS analytics ENGINE = Atomic")
sess.query("USE analytics")

sess.query("""
    CREATE TABLE sales (
        id UInt64,
        product String,
        amount Decimal(10,2),
        sale_date Date
    ) ENGINE = MergeTree() 
    ORDER BY (sale_date, id)
""")

# Insertar datos
sess.query("""
    INSERT INTO sales VALUES 
        (1, 'Laptop', 999.99, '2024-01-15'),
        (2, 'Mouse', 29.99, '2024-01-16'),
        (3, 'Keyboard', 79.99, '2024-01-17')
""")

# Crear vistas materializadas
sess.query("""
    CREATE MATERIALIZED VIEW daily_sales AS
    SELECT 
        sale_date,
        count() as orders,
        sum(amount) as revenue
    FROM sales 
    GROUP BY sale_date
""")

# Consultar la vista
result = sess.query("SELECT * FROM daily_sales ORDER BY sale_date", "Pretty")
print(result)

# La sesión gestiona los recursos automáticamente
sess.close()  # Opcional: se cierra automáticamente al eliminar el objeto

Funciones avanzadas de la sesión

# Session with custom settings
sess = session.Session(
    path="/tmp/analytics_db",
)

# Query performance optimization
result = sess.query("""
    SELECT product, sum(amount) as total
    FROM sales 
    GROUP BY product
    ORDER BY total DESC
    SETTINGS max_threads = 4
""", "JSON")

Véase también: test_stateful.py.

Interfaz de Python DB-API 2.0

Interfaz estándar de base de datos para garantizar la compatibilidad con aplicaciones de Python existentes:

import chdb.dbapi as dbapi

# Verificar información del driver
print(f"chDB driver version: {dbapi.get_client_info()}")

# Crear conexión
conn = dbapi.connect()
cursor = conn.cursor()

# Ejecutar consultas con parámetros
cursor.execute("""
    SELECT number, number * ? as doubled 
    FROM system.numbers 
    LIMIT ?
""", (2, 5))

# Obtener metadatos
print("Column descriptions:", cursor.description)
print("Row count:", cursor.rowcount)

# Obtener resultados
print("First row:", cursor.fetchone())
print("Next 2 rows:", cursor.fetchmany(2))

# Obtener las filas restantes
for row in cursor.fetchall():
    print("Row:", row)

# Operaciones por lotes
data = [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie')]
cursor.execute("""
    CREATE TABLE temp_users (
        id UInt64,
        name String
    ) ENGINE = MergeTree()
    ORDER BY (id)
""")
cursor.executemany(
    "INSERT INTO temp_users (id, name) VALUES (?, ?)", 
    data
)

Funciones definidas por el usuario (UDF) en Python

chDB admite UDF de Python nativas que se ejecutan en el mismo proceso, con argumentos tipados, inferencia automática de tipos y manejo configurable de NULL y excepciones.

from chdb import query, func
from chdb.sqltypes import INT64

@func([INT64, INT64], INT64)
def add(a, b):
    return a + b

result = query("SELECT add(2, 3)")
print(result)  # 5

Para consultar la guía completa sobre métodos de registro, sistema de tipos, manejo de NULL y excepciones, y compatibilidad con DateTime, consulte la guía de UDF de Python. Para consultar la referencia completa de la API, vea la referencia de la API de UDF de Python. El antiguo decorador @chdb_udf sigue estando disponible, pero ha sido reemplazado por esta API; consulte la API heredada.

Procesamiento de consultas en streaming

Procese grandes volúmenes de datos con un uso de memoria constante:

from chdb import session

sess = session.Session()

# Configurar conjunto de datos grande
sess.query("""
    CREATE TABLE large_data ENGINE = Memory() AS 
    SELECT number as id, toString(number) as data 
    FROM numbers(1000000)
""")

# Ejemplo 1: Streaming básico con gestor de contexto
total_rows = 0
with sess.send_query("SELECT * FROM large_data", "CSV") as stream:
    for chunk in stream:
        chunk_rows = len(chunk.data().split('\n')) - 1
        total_rows += chunk_rows
        print(f"Processed chunk: {chunk_rows} rows")
        
        # Terminación anticipada si es necesario
        if total_rows > 100000:
            break

print(f"Total rows processed: {total_rows}")

# Ejemplo 2: Iteración manual con limpieza explícita
stream = sess.send_query("SELECT * FROM large_data WHERE id % 100 = 0", "JSONEachRow")
processed_count = 0

while True:
    chunk = stream.fetch()
    if chunk is None:
        break
    
    # Procesar datos del fragmento
    lines = chunk.data().strip().split('\n')
    for line in lines:
        if line:  # Omitir líneas vacías
            processed_count += 1
    
    print(f"Processed {processed_count} records so far...")
    
stream.close()  # Importante: limpieza explícita

# Ejemplo 3: Integración con Arrow para bibliotecas externas
import pyarrow as pa

from deltalake import write_deltalake

# Transmitir resultados en formato Arrow
stream = sess.send_query("SELECT * FROM large_data LIMIT 100000", "Arrow")

# Crear RecordBatchReader con tamaño de lote personalizado
batch_reader = stream.record_batch(rows_per_batch=10000)

# Exportar a Delta Lake
write_deltalake(
    table_or_uri="./my_delta_table",
    data=batch_reader,
    mode="overwrite"
)

stream.close()
sess.close()

Motor de tabla de Python

Consultar DataFrames de Pandas

import chdb
import pandas as pd

# DataFrame complejo con datos anidados
df = pd.DataFrame({
    "customer_id": [1, 2, 3, 4, 5, 6],
    "customer_name": ["Alice", "Bob", "Charlie", "Alice", "Bob", "David"],
    "orders": [
        {"order_id": 101, "amount": 250.50, "items": ["laptop", "mouse"]},
        {"order_id": 102, "amount": 89.99, "items": ["book"]},
        {"order_id": 103, "amount": 1299.99, "items": ["phone", "case", "charger"]},
        {"order_id": 104, "amount": 45.50, "items": ["pen", "paper"]},
        {"order_id": 105, "amount": 199.99, "items": ["headphones"]},
        {"order_id": 106, "amount": 15.99, "items": ["cable"]}
    ]
})

# Consultas avanzadas con operaciones JSON
result = chdb.query("""
    SELECT 
        customer_name,
        count() as order_count,
        sum(toFloat64(orders.amount)) as total_spent,
        arrayStringConcat(
            arrayDistinct(
                arrayFlatten(
                    groupArray(orders.items)
                )
            ), 
            ', '
        ) as all_items
    FROM Python(df)
    GROUP BY customer_name
    HAVING total_spent > 100
    ORDER BY total_spent DESC
""").show()

# Window functions sobre DataFrames
window_result = chdb.query("""
    SELECT 
        customer_name,
        toFloat64(orders.amount) as amount,
        sum(toFloat64(orders.amount)) OVER (
            PARTITION BY customer_name 
            ORDER BY toInt32(orders.order_id)
        ) as running_total
    FROM Python(df)
    ORDER BY customer_name, toInt32(orders.order_id)
""", "Pretty")
print(window_result)

Fuentes de datos personalizadas con PyReader

Implemente lectores personalizados para fuentes de datos especializadas:

import chdb

from typing import List, Tuple, Any
import json

class DatabaseReader(chdb.PyReader):
    """Custom reader for database-like data sources"""
    
    def __init__(self, connection_string: str):
        # Simulate database connection
        self.data = self._load_data(connection_string)
        self.cursor = 0
        self.batch_size = 1000
        super().__init__(self.data)
    
    def _load_data(self, conn_str):
        # Simulate loading from database
        return {
            "id": list(range(1, 10001)),
            "name": [f"user_{i}" for i in range(1, 10001)],
            "score": [i * 10 + (i % 7) for i in range(1, 10001)],
            "metadata": [
                json.dumps({"level": i % 5, "active": i % 3 == 0})
                for i in range(1, 10001)
            ]
        }
    
    def get_schema(self) -> List[Tuple[str, str]]:
        """Define table schema with explicit types"""
        return [
            ("id", "UInt64"),
            ("name", "String"),
            ("score", "Int64"),
            ("metadata", "String")  # JSON stored as string
        ]
    
    def read(self, col_names: List[str], count: int) -> List[List[Any]]:
        """Read data in batches"""
        if self.cursor >= len(self.data["id"]):
            return []  # No more data
        
        end_pos = min(self.cursor + min(count, self.batch_size), len(self.data["id"]))
        
        # Return data for requested columns
        result = []
        for col in col_names:
            if col in self.data:
                result.append(self.data[col][self.cursor:end_pos])
            else:
                # Handle missing columns
                result.append([None] * (end_pos - self.cursor))
        
        self.cursor = end_pos
        return result

### JSON Type Inference and Handling                                

chDB automatically handles complex nested data structures:

```python
import pandas as pd
import chdb

# DataFrame with mixed JSON objects
df_with_json = pd.DataFrame({
    "user_id": [1, 2, 3, 4],
    "profile": [
        {"name": "Alice", "age": 25, "preferences": ["music", "travel"]},
        {"name": "Bob", "age": 30, "location": {"city": "NYC", "country": "US"}},
        {"name": "Charlie", "skills": ["python", "sql", "ml"], "experience": 5},
        {"score": 95, "rank": "gold", "achievements": [{"title": "Expert", "date": "2024-01-01"}]}
    ]
})

# Control JSON inference with settings
result = chdb.query("""
    SELECT 
        user_id,
        profile.name as name,
        profile.age as age,
        length(profile.preferences) as pref_count,
        profile.location.city as city
    FROM Python(df_with_json)
    SETTINGS pandas_analyze_sample = 1000  -- Analyze all rows for JSON detection
""", "Pretty")
print(result)

# Advanced JSON operations
complex_json = chdb.query("""
    SELECT 
        user_id,
        JSONLength(toString(profile)) as json_fields,
        JSONType(toString(profile), 'preferences') as pref_type,
        if(
            JSONHas(toString(profile), 'achievements'),
            JSONExtractString(toString(profile), 'achievements[0].title'),
            'None'
        ) as first_achievement
    FROM Python(df_with_json)
""", "JSONEachRow")
print(complex_json)

Rendimiento y optimización

Benchmarks

chDB supera de forma sistemática a otros motores embebidos:

  • Operaciones de DataFrame: entre 2 y 5 veces más rápidas que las bibliotecas de DataFrame tradicionales para consultas analíticas
  • Procesamiento de Parquet: competitivo frente a los principales motores columnares
  • Eficiencia de memoria: menor uso de memoria que las alternativas

Más detalles sobre los resultados del benchmark

Consejos de rendimiento

import chdb

# 1. Usar los formatos de salida adecuados
df_result = chdb.query("SELECT * FROM large_table", "DataFrame")  # Para análisis
arrow_result = chdb.query("SELECT * FROM large_table", "Arrow")    # Para interoperabilidad
native_result = chdb.query("SELECT * FROM large_table", "Native")   # Para chDB-to-chDB

# 2. Optimizar consultas con configuraciones
fast_result = chdb.query("""
    SELECT customer_id, sum(amount) 
    FROM sales 
    GROUP BY customer_id
    SETTINGS 
        max_threads = 8,
        max_memory_usage = '4G',
        use_uncompressed_cache = 1
""", "DataFrame")

# 3. Aprovechar el streaming para grandes conjuntos de datos
from chdb import session

sess = session.Session()

# Configurar un conjunto de datos grande
sess.query("""
    CREATE TABLE large_sales ENGINE = Memory() AS 
    SELECT 
        number as sale_id,
        number % 1000 as customer_id,
        rand() % 1000 as amount
    FROM numbers(10000000)
""")

# Procesamiento de flujo con uso de memoria constante
total_amount = 0
processed_rows = 0

with sess.send_query("SELECT customer_id, sum(amount) as total FROM large_sales GROUP BY customer_id", "JSONEachRow") as stream:
    for chunk in stream:
        lines = chunk.data().strip().split('\n')
        for line in lines:
            if line:  # Omitir líneas vacías
                import json
                row = json.loads(line)
                total_amount += row['total']
                processed_rows += 1
        
        print(f"Processed {processed_rows} customer records, running total: {total_amount}")
        
        # Terminación anticipada para demo
        if processed_rows > 1000:
            break

print(f"Final result: {processed_rows} customers processed, total amount: {total_amount}")

# Transmitir a sistemas externos (p. ej., Delta Lake)
stream = sess.send_query("SELECT * FROM large_sales LIMIT 1000000", "Arrow")
batch_reader = stream.record_batch(rows_per_batch=50000)

# Procesar en lotes
for batch in batch_reader:
    print(f"Processing batch with {batch.num_rows} rows...")
    # Transformar o exportar cada lote
    # df_batch = batch.to_pandas()
    # process_batch(df_batch)

stream.close()
sess.close()

Repositorio de GitHub

Navigation