Desafio PHP

Relatório com fetch modes e transação

Enunciado

Crie um script que gerencie pedidos. Crie tabelas clientes (id, nome) e pedidos (id, cliente_id, valor). Insira 3 clientes e vários pedidos. Usando uma transação, insira um novo pedido para um cliente existente. Em seguida, gere um relatório que liste cada cliente e o total de seus pedidos, usando PDO::FETCH_GROUP e PDO::FETCH_ASSOC para agrupar os resultados. Exiba o total geral de pedidos.

Requisitos

  • Usar beginTransaction e commit para inserir o novo pedido.
  • Usar prepared statements para todas as inserções e consultas.
  • Utilizar fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC) para agrupar pedidos por cliente.
  • Exibir o nome do cliente, a lista de valores de seus pedidos e o total geral.

Código inicial

<?php
// Seu código aqui

Saída esperada

Ana: 100, 200
Bruno: 150
Carla: 300
Total geral: 750
Ver dica

Para agrupar, faça um JOIN e use fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC). A chave do array será o cliente_id ou nome. Ajuste a consulta para que a primeira coluna seja o agrupador.

Mostrar solução
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Cria tabelas
$pdo->exec('CREATE TABLE clientes (id INTEGER PRIMARY KEY, nome TEXT)');
$pdo->exec('CREATE TABLE pedidos (id INTEGER PRIMARY KEY, cliente_id INTEGER, valor REAL)');

// Insere clientes
$stmt = $pdo->prepare('INSERT INTO clientes (nome) VALUES (:nome)');
$stmt->execute([':nome' => 'Ana']);
$stmt->execute([':nome' => 'Bruno']);
$stmt->execute([':nome' => 'Carla']);

// Insere pedidos iniciais
$stmt = $pdo->prepare('INSERT INTO pedidos (cliente_id, valor) VALUES (:cliente_id, :valor)');
$stmt->execute([':cliente_id' => 1, ':valor' => 100]);
$stmt->execute([':cliente_id' => 1, ':valor' => 200]);
$stmt->execute([':cliente_id' => 2, ':valor' => 150]);
$stmt->execute([':cliente_id' => 3, ':valor' => 300]);

// Transação para inserir novo pedido
$pdo->beginTransaction();
try {
    $stmt = $pdo->prepare('INSERT INTO pedidos (cliente_id, valor) VALUES (:cliente_id, :valor)');
    $stmt->execute([':cliente_id' => 3, ':valor' => 50]);
    $pdo->commit();
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Erro ao inserir pedido: " . $e->getMessage() . "\n";
}

// Relatório agrupado por cliente
$sql = 'SELECT c.nome, p.valor
        FROM clientes c
        JOIN pedidos p ON p.cliente_id = c.id
        ORDER BY c.nome, p.valor';
$stmt = $pdo->query($sql);
$dados = $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC);

$totalGeral = 0;
foreach ($dados as $nome => $pedidos) {
    $valores = array_column($pedidos, 'valor');
    $totalCliente = array_sum($valores);
    $totalGeral += $totalCliente;
    echo $nome . ': ' . implode(', ', $valores) . "\n";
}
echo "Total geral: $totalGeral\n";

Passo a passo

  1. Conectamos ao SQLite em memória e configuramos exceções.
  2. Criamos as tabelas clientes e pedidos.
  3. Inserimos três clientes com prepared statement.
  4. Inserimos quatro pedidos iniciais distribuídos entre os clientes.
  5. Iniciamos uma transação, inserimos um novo pedido para a Carla (id 3) e commitamos.
  6. Executamos uma consulta com JOIN que retorna nome e valor, ordenada por nome.
  7. Usamos fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC): o PDO agrupa os resultados pela primeira coluna (nome), e cada grupo é um array associativo com a coluna valor.
  8. Iteramos sobre o array agrupado, extraímos os valores com array_column, somamos por cliente e acumulamos o total geral.
  9. Exibimos cada cliente com seus valores e o total geral.

Por que funciona

O FETCH_GROUP agrupa as linhas pela primeira coluna do resultado, criando um array multidimensional onde a chave é o valor dessa coluna. Combinado com FETCH_ASSOC, cada linha interna é um array associativo. A transação garante que a inserção do novo pedido seja atômica. O prepared statement protege contra injeção.

Erros comuns

  • Não ordenar por nome antes de agrupar: o agrupamento funciona, mas a ordem dos grupos pode ser imprevisível. Use ORDER BY.
  • Esquecer de usar array_column para extrair os valores: se tentar array_sum($pedidos) diretamente, dará erro porque $pedidos é um array de arrays.
  • Usar FETCH_GROUP sem a coluna de agrupamento como primeira na SELECT: o agrupamento será feito pela coluna errada.

Outra forma de resolver

Poderia usar GROUP_CONCAT no SQL para concatenar os valores em uma string, mas perderia a flexibilidade de manipular os dados em PHP.

Saída esperada

Ana: 100, 200
Bruno: 150
Carla: 300, 50
Total geral: 800