Como usar o DuckDB para análises ad hoc em arquivos Parquet e CSV

1. Introdução ao DuckDB e seu papel em análises ad hoc

DuckDB é um sistema de gerenciamento de banco de dados OLAP (Online Analytical Processing) embutido, projetado especificamente para consultas analíticas de alto desempenho. Diferente de bancos relacionais tradicionais como PostgreSQL ou MySQL, o DuckDB é otimizado para workloads de análise de dados, suportando processamento columnar e execução vetorizada.

Seu papel em análises ad hoc é revolucionário: permite que analistas e engenheiros de dados explorem rapidamente arquivos Parquet e CSV sem necessidade de infraestrutura complexa. As principais vantagens incluem:

  • Zero configuração: instalação simples e execução imediata
  • Processamento local: sem dependência de servidores remotos
  • Integração nativa com S3/MinIO: acesso direto a data lakes
  • Suporte a formatos modernos: Parquet, Arrow, ORC, além de CSV clássico

Para análises exploratórias, prototipagem de pipelines e consultas descentralizadas, o DuckDB oferece uma alternativa eficiente a soluções como Spark ou bancos tradicionais.

2. Instalação e primeiros passos com DuckDB

A instalação pode ser feita via pip para Python ou CLI diretamente:

# Instalação via pip
pip install duckdb

# Instalação via CLI (Linux/macOS)
curl -fsSL https://install.duckdb.org | sh

Para iniciar uma sessão interativa:

# Modo CLI
duckdb

# Conectando a um arquivo de banco
duckdb minha_analise.db

Comandos básicos úteis:

# Listar tabelas
SHOW TABLES;

# Importar CSV
.import dados.csv minha_tabela

# Modo de saída
.mode markdown

# Salvar resultados
.output resultados.md

3. Consultando arquivos CSV diretamente

O DuckDB permite consultar arquivos CSV sem importá-los para o banco, usando a função read_csv_auto:

SELECT 
    categoria,
    COUNT(*) AS total_vendas,
    ROUND(AVG(valor), 2) AS valor_medio
FROM read_csv_auto('vendas.csv')
WHERE data >= '2024-01-01'
GROUP BY categoria
ORDER BY total_vendas DESC;

Parâmetros importantes para controle de leitura:

SELECT * FROM read_csv_auto(
    'dados.csv',
    delim=';',
    header=true,
    encoding='UTF-8',
    types={'coluna_data': 'DATE', 'valor': 'DOUBLE'}
);

4. Trabalhando com arquivos Parquet de forma eficiente

Parquet é o formato ideal para análises ad hoc devido à compressão columnar e suporte a pushdown de predicados:

-- Leitura básica
SELECT * FROM read_parquet('vendas.parquet');

-- Consulta com pushdown de predicados (filtros aplicados antes da leitura)
SELECT 
    ano,
    mes,
    SUM(receita) AS receita_total
FROM read_parquet('vendas_*.parquet')
WHERE ano >= 2023
GROUP BY ano, mes;

-- Particionamento por data
SELECT * FROM read_parquet('dados/ano=2024/mes=01/*.parquet');

Vantagens do Parquet sobre CSV:

  • Compressão columnar: redução de 70-90% no tamanho
  • Pushdown de predicados: consultas mais rápidas
  • Projeções: leitura seletiva de colunas
  • Schema embutido: sem necessidade de inferência

5. Cruzando dados entre CSV e Parquet em uma única consulta

A capacidade de unir fontes heterogêneas é um dos pontos fortes do DuckDB:

WITH vendas AS (
    SELECT * FROM read_parquet('vendas.parquet')
),
clientes AS (
    SELECT * FROM read_csv_auto('clientes.csv')
)
SELECT 
    c.nome,
    c.cidade,
    COUNT(v.id_venda) AS total_compras,
    ROUND(SUM(v.valor), 2) AS valor_total
FROM vendas v
JOIN clientes c ON v.id_cliente = c.id
WHERE v.data >= '2024-01-01'
GROUP BY c.nome, c.cidade
ORDER BY valor_total DESC;

Estratégias para schemas divergentes:

-- Tratamento de tipos diferentes
SELECT 
    CAST(v.id AS VARCHAR) AS id_venda,
    c.id_cliente
FROM read_parquet('vendas.parquet') v
JOIN read_csv_auto('clientes.csv') c 
    ON v.id_cliente = CAST(c.id AS INTEGER);

6. Integração com armazenamento externo (S3, MinIO, HTTP)

DuckDB suporta acesso remoto via extensões:

-- Habilitar extensão HTTP
INSTALL httpfs;
LOAD httpfs;

-- Configurar secrets para S3
CREATE SECRET s3_secret (
    TYPE S3,
    KEY_ID 'ACCESS_KEY',
    SECRET 'SECRET_KEY',
    REGION 'us-east-1'
);

-- Consultar dados no S3
SELECT * FROM read_parquet('s3://meu-bucket/dados/*.parquet');

-- Acessar via HTTP
SELECT * FROM read_csv_auto('https://exemplo.com/dados.csv');

Exemplo prático com MinIO:

INSTALL httpfs;
LOAD httpfs;

CREATE SECRET minio_secret (
    TYPE S3,
    KEY_ID 'minioadmin',
    SECRET 'minioadmin',
    ENDPOINT 'localhost:9000',
    USE_SSL false
);

SELECT 
    COUNT(*) AS total,
    ROUND(AVG(temperatura), 2) AS temp_media
FROM read_parquet('s3://meu-bucket/dados/*.parquet');

7. Otimização de performance e boas práticas

Para extrair o máximo de desempenho:

-- Analisar plano de execução
EXPLAIN ANALYZE
SELECT * FROM read_parquet('grandes_dados.parquet')
WHERE categoria = 'eletronicos';

-- Configurar paralelismo
SET threads = 8;
SET memory_limit = '8GB';

-- Materializar consultas frequentes
CREATE TABLE vendas_resumo AS
SELECT 
    DATE_TRUNC('month', data) AS mes,
    categoria,
    SUM(valor) AS total
FROM read_parquet('vendas/*.parquet')
GROUP BY ALL;

Boas práticas para grandes volumes:

  • Use Parquet com particionamento inteligente (ex: por data)
  • Aplique filtros antes de joins
  • Evite SELECT * em tabelas largas
  • Monitore o uso de memória com PRAGMA memory_limit

8. Exportação de resultados e automação de análises

Exportação para múltiplos formatos:

-- Exportar para CSV
COPY (
    SELECT * FROM read_parquet('analise.parquet')
) TO 'resultados.csv' (HEADER, DELIMITER ',');

-- Exportar para Parquet
COPY (
    SELECT * FROM read_csv_auto('dados.csv')
) TO 'dados.parquet' (FORMAT PARQUET);

-- Exportar para JSON
COPY (
    SELECT * FROM read_parquet('vendas.parquet')
) TO 'relatorio.json' (FORMAT JSON);

Automação com Python:

import duckdb
import pandas as pd

conexao = duckdb.connect(':memory:')

# Pipeline ad hoc automatizado
query = """
    WITH dados AS (
        SELECT * FROM read_csv_auto('vendas_diarias.csv')
    )
    SELECT 
        categoria,
        SUM(valor) AS faturamento
    FROM dados
    GROUP BY categoria
    ORDER BY faturamento DESC
"""

resultado = conexao.execute(query).fetchdf()
resultado.to_csv('relatorio_diario.csv', index=False)

Combinação com Jupyter Notebook:

# Célula Jupyter
import duckdb
import plotly.express as px

df = duckdb.query("""
    SELECT 
        DATE_TRUNC('month', data) AS mes,
        SUM(valor) AS receita
    FROM read_parquet('vendas.parquet')
    GROUP BY mes
    ORDER BY mes
""").df()

fig = px.line(df, x='mes', y='receita', title='Receita Mensal')
fig.show()

Referências