Pular para o conteúdo principal
Entender o modelo de avaliação preguiçosa do DataStore é essencial para usá-lo com eficiência e obter o melhor desempenho.

Avaliação preguiçosa

O DataStore usa avaliação preguiçosa - as operações não são executadas imediatamente; em vez disso, são registradas e compiladas em consultas SQL otimizadas. A execução só acontece quando os resultados são realmente necessários.

Exemplo: Preguiçoso vs Eager

from pathlib import Path
Path("sales.csv").write_text("""\
region,product,category,amount,quantity,price,date,order_id
East,Widget,Electronics,5200,10,120,2024-01-15,1001
West,Gadget,Electronics,800,5,160,2024-02-20,1002
East,Gizmo,Home,6500,3,100,2024-03-10,1003
North,Widget,Electronics,4500,6,150,2024-06-18,1004
West,Gadget,Electronics,2000,8,250,2024-09-14,1005
""")

from chdb import datastore as pd

ds = pd.read_csv("sales.csv")

# Estas operações NÃO foram executadas ainda
result = (ds
    .filter(ds['amount'] > 1000)    # Registrado, não executado
    .select('region', 'amount')      # Registrado, não executado
    .groupby('region')               # Registrado, não executado
    .agg({'amount': 'sum'})          # Registrado, não executado
    .sort('sum', ascending=False)    # Registrado, não executado
)

# Ainda sem execução - apenas construindo o plano de consulta
print(result.to_sql())
# SELECT region, SUM(amount) AS sum
# FROM file('sales.csv', 'CSVWithNames')
# WHERE amount > 1000
# GROUP BY region
# ORDER BY sum DESC

# AGORA a execução acontece
df = result.to_df()  # <-- Dispara a execução

Benefícios da Avaliação Preguiçosa

  1. Otimização de Consultas: várias operações são compiladas em uma única consulta SQL otimizada
  2. Pushdown de Filtros: os filtros são aplicados no nível da fonte de dados
  3. Poda de Colunas: apenas as colunas necessárias são lidas
  4. Decisões Adiadas: o mecanismo de execução pode ser escolhido em tempo de execução
  5. Inspeção do Plano: você pode visualizar e depurar a consulta antes de executá-la

Gatilhos de execução

A execução é acionada automaticamente quando você precisa dos valores reais:

Gatilhos automáticos

GatilhoExemploDescrição
print() / repr()print(ds)Exibe os resultados
len()len(ds)Obtém a contagem de linhas
.columnsds.columnsObtém os nomes das colunas
.dtypesds.dtypesObtém os tipos das colunas
.shapeds.shapeObtém as dimensões
.indexds.indexObtém o índice das linhas
.valuesds.valuesObtém o array NumPy
Iteraçãofor row in dsItera sobre as linhas
to_df()ds.to_df()Converte para pandas
to_pandas()ds.to_pandas()Alias de to_df
to_dict()ds.to_dict()Converte para dict
to_numpy()ds.to_numpy()Converte para array
.equals()ds.equals(other)Compara DataStores
Exemplos:
# Todos estes acionam a execução
print(ds)              # Exibir
len(ds)                # 1000
ds.columns             # Index(['name', 'age', 'city'])
ds.shape               # (1000, 3)
list(ds)               # Lista de valores
ds.to_df()             # pandas DataFrame

Operações que permanecem preguiçosas

OperaçãoRetornaDescrição
filter()DataStoreAdiciona cláusula WHERE
select()DataStoreAdiciona seleção de colunas
sort()DataStoreAdiciona ORDER BY
groupby()LazyGroupByPrepara GROUP BY
join()DataStoreAdiciona JOIN
ds['col']ColumnExprReferência de coluna
ds[['col1', 'col2']]DataStoreSeleção de colunas
Exemplos:
# Estas NÃO acionam a execução - permanecem preguiçosas
result = ds.filter(ds['age'] > 25)      # Retorna DataStore
result = ds.select('name', 'age')        # Retorna DataStore
result = ds['name']                      # Retorna ColumnExpr
result = ds.groupby('city')              # Retorna LazyGroupBy

Execução em três fases

As operações do DataStore seguem um modelo de execução em três fases:

Fase 1: Construção da consulta SQL (Preguiçoso)

As operações que podem ser expressas em SQL são acumuladas:
result = (ds
    .filter(ds['status'] == 'active')   # WHERE
    .select('user_id', 'amount')         # SELECT
    .groupby('user_id')                  # GROUP BY
    .agg({'amount': 'sum'})              # SUM()
    .sort('sum', ascending=False)        # ORDER BY
    .limit(10)                           # LIMIT
)
# Tudo compilado em uma única consulta SQL

Fase 2: Ponto de execução

Quando um gatilho é acionado, o SQL acumulado é executado:
# Execução acionada aqui
df = result.to_df()  
# A consulta SQL otimizada é executada agora

Fase 3: Operações com DataFrame (se houver)

Se você encadear operações apenas do pandas após a execução:
# Operações mistas
result = (ds
    .filter(ds['amount'] > 100)          # Fase 1: SQL
    .to_df()                             # Fase 2: Executar
    .pivot_table(...)                    # Fase 3: pandas
)

Visualizando planos de execução

Use explain() para ver o que será executado:
Query
ds = pd.read_csv("sales.csv")

query = (ds
    .filter(ds['amount'] > 1000)
    .groupby('region')
    .agg({'amount': ['sum', 'mean']})
)

# Visualizar plano de execução
query.explain()
Response
Pipeline:
  1. Source: file('sales.csv', 'CSVWithNames')
  2. Filter: amount > 1000
  3. GroupBy: region
  4. Aggregate: sum(amount), avg(amount)

Generated SQL:
SELECT region, SUM(amount) AS sum, AVG(amount) AS mean
FROM file('sales.csv', 'CSVWithNames')
WHERE amount > 1000
GROUP BY region
Use verbose=True para obter mais detalhes:
query.explain(verbose=True)
Consulte Depuração: explain() para acessar a documentação completa.

Armazenamento em cache

O DataStore armazena em cache os resultados da execução para evitar consultas redundantes.

Como o cache funciona

from pathlib import Path
Path("data.csv").write_text("""\
name,age,city,salary,department
Alice,25,NYC,55000,Engineering
Bob,30,LA,65000,Product
Charlie,35,NYC,80000,Engineering
Diana,28,SF,70000,Design
Eve,42,NYC,95000,Product
""")

ds = pd.read_csv("data.csv")
result = ds.filter(ds['age'] > 25)

# Primeiro acesso - executa a consulta
print(result.shape)  # Executa e armazena em cache

# Segundo acesso - usa o cache
print(result.columns)  # Usa o resultado em cache

# Terceiro acesso - usa o cache
df = result.to_df()  # Usa o resultado em cache

Invalidação do cache

O cache é invalidado quando operações alteram o DataStore:
result = ds.filter(ds['age'] > 25)
print(result.shape)  # Executa, armazena em cache

# Nova operação invalida o cache
result2 = result.filter(result['city'] == 'NYC')
print(result2.shape)  # Reexecuta (consulta diferente)

Controle manual do cache

# Limpar cache
ds.clear_cache()

# Desativar cache
from chdb.datastore.config import config
config.set_cache_enabled(False)

Combinando operações de SQL e Pandas

O DataStore lida de forma inteligente com operações que combinam SQL e pandas:

Operações compatíveis com SQL

São convertidas em SQL:
  • filter(), where()
  • select()
  • groupby(), agg()
  • sort(), orderby()
  • limit(), offset()
  • join(), union()
  • distinct()
  • Operações em colunas (matemática, comparação, métodos de string)

Operações exclusivas do Pandas

Estas operações acionam a execução e usam o Pandas:
  • apply() com funções personalizadas
  • pivot_table() com agregações complexas
  • stack(), unstack()
  • Operações em DataFrames já executados

Pipelines híbridos

# Fase SQL
result = (ds
    .filter(ds['amount'] > 100)      # SQL
    .groupby('category')              # SQL
    .agg({'amount': 'sum'})           # SQL
)

# Fase de execução + pandas
result = (result
    .to_df()                          # Executar SQL
    .pivot_table(...)                 # operação pandas
)

Seleção do mecanismo de execução

O DataStore pode executar operações com diferentes mecanismos:

Modo automático (padrão)

from chdb.datastore.config import config

config.set_execution_engine('auto')  # Padrão
# Seleciona automaticamente o melhor mecanismo por operação

Forçar o mecanismo chDB

config.set_execution_engine('chdb')
# Todas as operações usam ClickHouse SQL

Forçar o mecanismo do pandas

config.set_execution_engine('pandas')
# Todas as operações usam pandas
Consulte Configuração: Mecanismo de execução para mais detalhes.

Impactos no desempenho

Bom: aplique o filtro no início

# Bom: Filtrar no SQL, depois agregar
result = (ds
    .filter(ds['date'] >= '2024-01-01')  # Reduz os dados antecipadamente
    .groupby('category')
    .agg({'amount': 'sum'})
)

Ruim: filtrar só no fim

# Ruim: Agregar tudo e depois filtrar
result = (ds
    .groupby('category')
    .agg({'amount': 'sum'})
    .to_df()
    .query('sum > 1000')  # Filtro do Pandas após a agregação
)

Bom: selecione as colunas desde o início

# Bom: Selecionar colunas em SQL
result = (ds
    .select('user_id', 'amount', 'date')
    .filter(ds['date'] >= '2024-01-01')
    .groupby('user_id')
    .agg({'amount': 'sum'})
)

Bom: deixe o SQL fazer o trabalho

# Bom: Agregação complexa em SQL
result = (ds
    .groupby('category')
    .agg({
        'amount': ['sum', 'mean', 'count'],
        'quantity': 'sum'
    })
    .sort('sum', ascending=False)
    .limit(10)
)
# Uma consulta SQL faz tudo

# Ruim: Múltiplas consultas separadas
sums = ds.groupby('category')['amount'].sum().to_df()
means = ds.groupby('category')['amount'].mean().to_df()
# Duas consultas em vez de uma

Resumo das práticas recomendadas

  1. Encadeie as operações antes de executar - Monte a consulta completa e só então acione a execução uma vez
  2. Filtre o quanto antes - Reduza os dados na origem
  3. Selecione apenas as colunas necessárias - A eliminação de colunas melhora o desempenho
  4. Use explain() para entender a execução - Depure antes de executar
  5. Deixe o SQL cuidar das agregações - O ClickHouse é otimizado para isso
  6. Fique atento aos gatilhos de execução - Evite execuções antecipadas acidentais
  7. Use cache com critério - Entenda quando o cache é invalidado
Última modificação em 10 de junho de 2026