Dados Relacionais com Polars

Benilton S Carvalho

Estruturas Relacionais com Duas Tabelas

  • Em bancos de dados relacionais, informações frequentemente estão organizadas em múltiplas tabelas que se relacionam entre si através de chaves.
  • A chave é uma coluna (ou conjunto de colunas) que permite identificar e associar os dados de uma tabela com os de outra.

Dados Clientes

import polars as pl

# Criando DataFrames
clientes = pl.DataFrame({
    "cliente_id": [1, 2, 3, 4],
    "nome": ["Ana", "Bruno", "Clara", "Daniel"]
})

print(clientes)
shape: (4, 2)
┌────────────┬────────┐
│ cliente_id ┆ nome   │
│ ---        ┆ ---    │
│ i64        ┆ str    │
╞════════════╪════════╡
│ 1          ┆ Ana    │
│ 2          ┆ Bruno  │
│ 3          ┆ Clara  │
│ 4          ┆ Daniel │
└────────────┴────────┘

Dados Compras

pedidos = pl.DataFrame({
    "pedido_id": [101, 102, 103, 104, 105],
    "cliente_id": [1, 2, 3, 1, 5],
    "valor": [100.50, 250.75, 75.00, 130.00, 79.00]
})

print(pedidos)
shape: (5, 3)
┌───────────┬────────────┬────────┐
│ pedido_id ┆ cliente_id ┆ valor  │
│ ---       ┆ ---        ┆ ---    │
│ i64       ┆ i64        ┆ f64    │
╞═══════════╪════════════╪════════╡
│ 101       ┆ 1          ┆ 100.5  │
│ 102       ┆ 2          ┆ 250.75 │
│ 103       ┆ 3          ┆ 75.0   │
│ 104       ┆ 1          ┆ 130.0  │
│ 105       ┆ 5          ┆ 79.0   │
└───────────┴────────────┴────────┘

JOINs

  • Um JOIN combina registros de duas tabelas com base em uma coluna comum (chave).
  • É amplamente utilizado em manipulação de dados relacionais.
  • Deve ser empregado para combinar informações de duas (ou mais) tabelas que compartilham chaves entre si.

INNER JOIN

Retorna apenas as linhas que têm correspondências (de chaves) nas duas tabelas.

Exemplo INNER JOIN

res_ij = clientes.join(pedidos, on="cliente_id", how="inner")
print(res_ij)
shape: (4, 4)
┌────────────┬───────┬───────────┬────────┐
│ cliente_id ┆ nome  ┆ pedido_id ┆ valor  │
│ ---        ┆ ---   ┆ ---       ┆ ---    │
│ i64        ┆ str   ┆ i64       ┆ f64    │
╞════════════╪═══════╪═══════════╪════════╡
│ 1          ┆ Ana   ┆ 101       ┆ 100.5  │
│ 2          ┆ Bruno ┆ 102       ┆ 250.75 │
│ 3          ┆ Clara ┆ 103       ┆ 75.0   │
│ 1          ┆ Ana   ┆ 104       ┆ 130.0  │
└────────────┴───────┴───────────┴────────┘

LEFT/RIGHT JOIN

Retorna todas as linhas da tabela à esquerda [direita] e as correspondentes da direita [esquerda] (se houver).

Exemplo LEFT JOIN

res_lj = clientes.join(pedidos, on="cliente_id", how="left")
print(res_lj)
shape: (5, 4)
┌────────────┬────────┬───────────┬────────┐
│ cliente_id ┆ nome   ┆ pedido_id ┆ valor  │
│ ---        ┆ ---    ┆ ---       ┆ ---    │
│ i64        ┆ str    ┆ i64       ┆ f64    │
╞════════════╪════════╪═══════════╪════════╡
│ 1          ┆ Ana    ┆ 101       ┆ 100.5  │
│ 1          ┆ Ana    ┆ 104       ┆ 130.0  │
│ 2          ┆ Bruno  ┆ 102       ┆ 250.75 │
│ 3          ┆ Clara  ┆ 103       ┆ 75.0   │
│ 4          ┆ Daniel ┆ null      ┆ null   │
└────────────┴────────┴───────────┴────────┘

Exemplo RIGHT JOIN

res_rj = clientes.join(pedidos, on="cliente_id", how="right")
print(res_rj)
shape: (5, 4)
┌───────┬───────────┬────────────┬────────┐
│ nome  ┆ pedido_id ┆ cliente_id ┆ valor  │
│ ---   ┆ ---       ┆ ---        ┆ ---    │
│ str   ┆ i64       ┆ i64        ┆ f64    │
╞═══════╪═══════════╪════════════╪════════╡
│ Ana   ┆ 101       ┆ 1          ┆ 100.5  │
│ Bruno ┆ 102       ┆ 2          ┆ 250.75 │
│ Clara ┆ 103       ┆ 3          ┆ 75.0   │
│ Ana   ┆ 104       ┆ 1          ┆ 130.0  │
│ null  ┆ 105       ┆ 5          ┆ 79.0   │
└───────┴───────────┴────────────┴────────┘

OUTER JOIN

Retorna todas as linhas quando há uma correspondência em uma das tabelas.

Exemplo OUTER JOIN

res_oj = clientes.join(pedidos, on="cliente_id", how="outer")
print(res_oj)
shape: (6, 5)
┌────────────┬────────┬───────────┬──────────────────┬────────┐
│ cliente_id ┆ nome   ┆ pedido_id ┆ cliente_id_right ┆ valor  │
│ ---        ┆ ---    ┆ ---       ┆ ---              ┆ ---    │
│ i64        ┆ str    ┆ i64       ┆ i64              ┆ f64    │
╞════════════╪════════╪═══════════╪══════════════════╪════════╡
│ 1          ┆ Ana    ┆ 101       ┆ 1                ┆ 100.5  │
│ 2          ┆ Bruno  ┆ 102       ┆ 2                ┆ 250.75 │
│ 3          ┆ Clara  ┆ 103       ┆ 3                ┆ 75.0   │
│ 1          ┆ Ana    ┆ 104       ┆ 1                ┆ 130.0  │
│ null       ┆ null   ┆ 105       ┆ 5                ┆ 79.0   │
│ 4          ┆ Daniel ┆ null      ┆ null             ┆ null   │
└────────────┴────────┴───────────┴──────────────────┴────────┘

CROSS JOIN

Retorna o produto cartesiano de ambas as tabelas.

Exemplo CROSS JOIN

res_cj = clientes.join(pedidos, on="cliente_id", how="cross")
print(res_cj)
shape: (20, 5)
┌────────────┬────────┬───────────┬──────────────────┬────────┐
│ cliente_id ┆ nome   ┆ pedido_id ┆ cliente_id_right ┆ valor  │
│ ---        ┆ ---    ┆ ---       ┆ ---              ┆ ---    │
│ i64        ┆ str    ┆ i64       ┆ i64              ┆ f64    │
╞════════════╪════════╪═══════════╪══════════════════╪════════╡
│ 1          ┆ Ana    ┆ 101       ┆ 1                ┆ 100.5  │
│ 1          ┆ Ana    ┆ 102       ┆ 2                ┆ 250.75 │
│ 1          ┆ Ana    ┆ 103       ┆ 3                ┆ 75.0   │
│ 1          ┆ Ana    ┆ 104       ┆ 1                ┆ 130.0  │
│ 1          ┆ Ana    ┆ 105       ┆ 5                ┆ 79.0   │
│ …          ┆ …      ┆ …         ┆ …                ┆ …      │
│ 4          ┆ Daniel ┆ 101       ┆ 1                ┆ 100.5  │
│ 4          ┆ Daniel ┆ 102       ┆ 2                ┆ 250.75 │
│ 4          ┆ Daniel ┆ 103       ┆ 3                ┆ 75.0   │
│ 4          ┆ Daniel ┆ 104       ┆ 1                ┆ 130.0  │
│ 4          ┆ Daniel ┆ 105       ┆ 5                ┆ 79.0   │
└────────────┴────────┴───────────┴──────────────────┴────────┘

Operações em Tabelas Combinadas

  • Ao final de uma operação do tipo JOIN, a tabela resultante continua sendo um DataFrame.
  • Todas as operações para DataFrame podem ser aplicadas:
    • Filtros
    • Seleções
    • Operações Agregadas

P1: Qual é o valor médio das compras realizadas para cada cliente identificado?

Como responder P1?

print(clientes)
shape: (4, 2)
┌────────────┬────────┐
│ cliente_id ┆ nome   │
│ ---        ┆ ---    │
│ i64        ┆ str    │
╞════════════╪════════╡
│ 1          ┆ Ana    │
│ 2          ┆ Bruno  │
│ 3          ┆ Clara  │
│ 4          ┆ Daniel │
└────────────┴────────┘
print(pedidos)
shape: (5, 3)
┌───────────┬────────────┬────────┐
│ pedido_id ┆ cliente_id ┆ valor  │
│ ---       ┆ ---        ┆ ---    │
│ i64       ┆ i64        ┆ f64    │
╞═══════════╪════════════╪════════╡
│ 101       ┆ 1          ┆ 100.5  │
│ 102       ┆ 2          ┆ 250.75 │
│ 103       ┆ 3          ┆ 75.0   │
│ 104       ┆ 1          ┆ 130.0  │
│ 105       ┆ 5          ┆ 79.0   │
└───────────┴────────────┴────────┘

Como responder P1?

res = res_ij.group_by(["nome", "cliente_id"]).agg(pl.col("valor").mean())
print(res)
shape: (3, 3)
┌───────┬────────────┬────────┐
│ nome  ┆ cliente_id ┆ valor  │
│ ---   ┆ ---        ┆ ---    │
│ str   ┆ i64        ┆ f64    │
╞═══════╪════════════╪════════╡
│ Clara ┆ 3          ┆ 75.0   │
│ Ana   ┆ 1          ┆ 115.25 │
│ Bruno ┆ 2          ┆ 250.75 │
└───────┴────────────┴────────┘

P2: Informe os nomes e a quantidade de compras com valor mínimo de $100.00 realizadas por cada cliente.

Como responder P2?

print(clientes)
shape: (4, 2)
┌────────────┬────────┐
│ cliente_id ┆ nome   │
│ ---        ┆ ---    │
│ i64        ┆ str    │
╞════════════╪════════╡
│ 1          ┆ Ana    │
│ 2          ┆ Bruno  │
│ 3          ┆ Clara  │
│ 4          ┆ Daniel │
└────────────┴────────┘
print(pedidos)
shape: (5, 3)
┌───────────┬────────────┬────────┐
│ pedido_id ┆ cliente_id ┆ valor  │
│ ---       ┆ ---        ┆ ---    │
│ i64       ┆ i64        ┆ f64    │
╞═══════════╪════════════╪════════╡
│ 101       ┆ 1          ┆ 100.5  │
│ 102       ┆ 2          ┆ 250.75 │
│ 103       ┆ 3          ┆ 75.0   │
│ 104       ┆ 1          ┆ 130.0  │
│ 105       ┆ 5          ┆ 79.0   │
└───────────┴────────────┴────────┘

Como responder P2?

res = (res_oj.with_columns(pl.col("valor") > 100)
       .group_by("nome")
       .agg(pl.col("valor").sum()))
print(res)
shape: (5, 2)
┌────────┬───────┐
│ nome   ┆ valor │
│ ---    ┆ ---   │
│ str    ┆ u32   │
╞════════╪═══════╡
│ Bruno  ┆ 1     │
│ Daniel ┆ 0     │
│ Clara  ┆ 0     │
│ null   ┆ 0     │
│ Ana    ┆ 2     │
└────────┴───────┘

JOIN com Múltiplas Colunas como Chave

vendas = pl.DataFrame({
    "id_venda": [1, 2, 3],
    "id_cl": [1, 2, 1],
    "id_prod": [101, 102, 103],
    "qtde": [2, 1, 1]
})

detalhes_pedidos = pl.DataFrame({
    "id_ped": [201, 202, 203],
    "cl_id": [1, 2, 1],
    "id_prod": [101, 102, 104],
    "valor": [50.00, 75.00, 100.00]
})

Detalhes das Tabelas

print(vendas)
shape: (3, 4)
┌──────────┬───────┬─────────┬──────┐
│ id_venda ┆ id_cl ┆ id_prod ┆ qtde │
│ ---      ┆ ---   ┆ ---     ┆ ---  │
│ i64      ┆ i64   ┆ i64     ┆ i64  │
╞══════════╪═══════╪═════════╪══════╡
│ 1        ┆ 1     ┆ 101     ┆ 2    │
│ 2        ┆ 2     ┆ 102     ┆ 1    │
│ 3        ┆ 1     ┆ 103     ┆ 1    │
└──────────┴───────┴─────────┴──────┘
print(detalhes_pedidos)
shape: (3, 4)
┌────────┬───────┬─────────┬───────┐
│ id_ped ┆ cl_id ┆ id_prod ┆ valor │
│ ---    ┆ ---   ┆ ---     ┆ ---   │
│ i64    ┆ i64   ┆ i64     ┆ f64   │
╞════════╪═══════╪═════════╪═══════╡
│ 201    ┆ 1     ┆ 101     ┆ 50.0  │
│ 202    ┆ 2     ┆ 102     ┆ 75.0  │
│ 203    ┆ 1     ┆ 104     ┆ 100.0 │
└────────┴───────┴─────────┴───────┘

Realizando um JOIN com Múltiplas Colunas

final = vendas.join(detalhes_pedidos,
                    left_on = ["id_cl", "id_prod"],
                    right_on = ["cl_id", "id_prod"],
                    how = "inner")
print(final)
shape: (2, 6)
┌──────────┬───────┬─────────┬──────┬────────┬───────┐
│ id_venda ┆ id_cl ┆ id_prod ┆ qtde ┆ id_ped ┆ valor │
│ ---      ┆ ---   ┆ ---     ┆ ---  ┆ ---    ┆ ---   │
│ i64      ┆ i64   ┆ i64     ┆ i64  ┆ i64    ┆ f64   │
╞══════════╪═══════╪═════════╪══════╪════════╪═══════╡
│ 1        ┆ 1     ┆ 101     ┆ 2    ┆ 201    ┆ 50.0  │
│ 2        ┆ 2     ┆ 102     ┆ 1    ┆ 202    ┆ 75.0  │
└──────────┴───────┴─────────┴──────┴────────┴───────┘