Passer au contenu principal
Comprendre le modèle d’évaluation paresseuse de DataStore est essentiel pour l’utiliser efficacement et obtenir des performances optimales.

Évaluation paresseuse

DataStore utilise l’évaluation paresseuse : les opérations ne sont pas exécutées immédiatement ; elles sont enregistrées puis compilées en requêtes SQL optimisées. L’exécution n’a lieu que lorsque les résultats sont effectivement nécessaires.

Exemple : évaluation paresseuse vs évaluation immédiate

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")

# These operations are NOT executed yet
result = (ds
    .filter(ds['amount'] > 1000)    # Recorded, not executed
    .select('region', 'amount')      # Recorded, not executed
    .groupby('region')               # Recorded, not executed
    .agg({'amount': 'sum'})          # Recorded, not executed
    .sort('sum', ascending=False)    # Recorded, not executed
)

# Still no execution - just building the query plan
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

# NOW execution happens
df = result.to_df()  # <-- Triggers execution

Avantages de l’évaluation paresseuse

  1. Optimisation des requêtes : plusieurs opérations sont compilées en une seule requête SQL optimisée
  2. Pushdown des filtres : les filtres sont appliqués au niveau de la source de données
  3. Élagage des colonnes : seules les colonnes nécessaires sont lues
  4. Choix différé : le moteur d’exécution peut être choisi à l’exécution
  5. Inspection du plan : vous pouvez afficher et déboguer la requête avant de l’exécuter

Déclencheurs d’exécution

L’exécution est automatiquement déclenchée lorsque vous avez besoin des valeurs réelles :

Déclencheurs automatiques

DéclencheurExempleDescription
print() / repr()print(ds)Afficher les résultats
len()len(ds)Obtenir le nombre de lignes
.columnsds.columnsObtenir les noms des colonnes
.dtypesds.dtypesObtenir les types de colonnes
.shapeds.shapeObtenir les dimensions
.indexds.indexObtenir l’index des lignes
.valuesds.valuesObtenir le tableau NumPy
Itérationfor row in dsItérer sur les lignes
to_df()ds.to_df()Convertir en DataFrame pandas
to_pandas()ds.to_pandas()Alias de to_df
to_dict()ds.to_dict()Convertir en dictionnaire
to_numpy()ds.to_numpy()Convertir en tableau
.equals()ds.equals(other)Comparer des DataStore
Exemples :
# All these trigger execution
print(ds)              # Display
len(ds)                # 1000
ds.columns             # Index(['name', 'age', 'city'])
ds.shape               # (1000, 3)
list(ds)               # List of values
ds.to_df()             # pandas DataFrame

Opérations qui restent paresseuses

OpérationRenvoieDescription
filter()DataStoreAjoute la clause WHERE
select()DataStoreAjoute une sélection de colonnes
sort()DataStoreAjoute ORDER BY
groupby()LazyGroupByPrépare GROUP BY
join()DataStoreAjoute JOIN
ds['col']ColumnExprRéférence de colonne
ds[['col1', 'col2']]DataStoreSélection de colonnes
Exemples :
# These do NOT trigger execution - they stay lazy
result = ds.filter(ds['age'] > 25)      # Returns DataStore
result = ds.select('name', 'age')        # Returns DataStore
result = ds['name']                      # Returns ColumnExpr
result = ds.groupby('city')              # Returns LazyGroupBy

Exécution en trois phases

Les opérations de DataStore suivent un modèle d’exécution en trois phases :

Phase 1 : Construction de la requête SQL (paresseuse)

Les opérations qui peuvent être exprimées en SQL sont accumulées :
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
)
# All compiled into one SQL query

Phase 2 : point d’exécution

Lorsqu’un déclencheur se produit, le SQL accumulé est exécuté :
# Execution triggered here
df = result.to_df()  
# The single optimized SQL query runs now

Phase 3 : Opérations sur les DataFrame (le cas échéant)

Si vous enchaînez des opérations spécifiques à pandas après l’exécution :
# Mixed operations
result = (ds
    .filter(ds['amount'] > 100)          # Phase 1: SQL
    .to_df()                             # Phase 2: Execute
    .pivot_table(...)                    # Phase 3: pandas
)

Affichage des plans d’exécution

Utilisez explain() pour voir ce qui sera exécuté :
Query
ds = pd.read_csv("sales.csv")

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

# View execution plan
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
Utilisez verbose=True pour obtenir plus de détails :
query.explain(verbose=True)
Consultez la documentation complète de Débogage : explain().

Mise en cache

DataStore met en cache les résultats d’exécution afin d’éviter les requêtes redondantes.

Comment fonctionne la mise en cache

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)

# First access - executes query
print(result.shape)  # Executes and caches

# Second access - uses cache
print(result.columns)  # Uses cached result

# Third access - uses cache
df = result.to_df()  # Uses cached result

Invalidation du cache

Le cache est invalidé dès que des opérations modifient le DataStore :
result = ds.filter(ds['age'] > 25)
print(result.shape)  # Executes, caches

# New operation invalidates cache
result2 = result.filter(result['city'] == 'NYC')
print(result2.shape)  # Re-executes (different query)

Gestion manuelle du cache

# Clear cache
ds.clear_cache()

# Disable caching
from chdb.datastore.config import config
config.set_cache_enabled(False)

Combiner SQL et les opérations Pandas

DataStore gère intelligemment les opérations combinant SQL et pandas :

Opérations compatibles avec SQL

Elles sont compilées en SQL :
  • filter(), where()
  • select()
  • groupby(), agg()
  • sort(), orderby()
  • limit(), offset()
  • join(), union()
  • distinct()
  • Opérations sur les colonnes (mathématiques, comparaisons, méthodes sur les chaînes)

Opérations propres à Pandas

Ces opérations déclenchent l’exécution et font appel à Pandas :
  • apply() avec des fonctions personnalisées
  • pivot_table() avec des agrégations complexes
  • stack(), unstack()
  • Opérations sur des DataFrames déjà exécutés

Pipelines hybrides

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

# Execution + pandas phase
result = (result
    .to_df()                          # Execute SQL
    .pivot_table(...)                 # pandas operation
)

Sélection du moteur d’exécution

DataStore peut exécuter des opérations avec différents moteurs :

Auto Mode (par défaut)

from chdb.datastore.config import config

config.set_execution_engine('auto')  # Default
# Automatically selects best engine per operation

Forcer le moteur chDB

config.set_execution_engine('chdb')
# All operations use ClickHouse SQL

Forcer le moteur pandas

config.set_execution_engine('pandas')
# All operations use pandas
Consultez Configuration : moteur d’exécution pour plus de détails.

Impact sur les performances

Bien : filtrer tôt

# Good: Filter in SQL, then aggregate
result = (ds
    .filter(ds['date'] >= '2024-01-01')  # Reduces data early
    .groupby('category')
    .agg({'amount': 'sum'})
)

À éviter : filtrer trop tard

# Bad: Aggregate all, then filter
result = (ds
    .groupby('category')
    .agg({'amount': 'sum'})
    .to_df()
    .query('sum > 1000')  # Pandas filter after aggregation
)

Bon réflexe : sélectionnez les colonnes dès le début

# Good: Select columns in SQL
result = (ds
    .select('user_id', 'amount', 'date')
    .filter(ds['date'] >= '2024-01-01')
    .groupby('user_id')
    .agg({'amount': 'sum'})
)

Bon : laissez SQL faire le travail

# Good: Complex aggregation in SQL
result = (ds
    .groupby('category')
    .agg({
        'amount': ['sum', 'mean', 'count'],
        'quantity': 'sum'
    })
    .sort('sum', ascending=False)
    .limit(10)
)
# One SQL query does everything

# Bad: Multiple separate queries
sums = ds.groupby('category')['amount'].sum().to_df()
means = ds.groupby('category')['amount'].mean().to_df()
# Two queries instead of one

Résumé des bonnes pratiques

  1. Chaînez les opérations avant d’exécuter - Construisez la requête complète, puis déclenchez l’exécution une seule fois
  2. Filtrez tôt - Réduisez les données à la source
  3. Sélectionnez uniquement les colonnes nécessaires - L’élagage des colonnes améliore les performances
  4. Utilisez explain() pour comprendre l’exécution - Déboguez avant d’exécuter
  5. Laissez SQL gérer les agrégations - ClickHouse est optimisé pour cela
  6. Sachez ce qui déclenche l’exécution - Évitez un déclenchement prématuré par inadvertance
  7. Utilisez la mise en cache à bon escient - Comprenez quand le cache est invalidé
Dernière modification le 29 juin 2026