DevGuideTK
← Trilha Backend

SQL e NoSQL

Os modelos de dados disponíveis e os critérios para escolher o banco de um sistema.

A mesma operação revela a diferença entre os modelos: criar um pedido com os seus itens. No relacional, são várias linhas em duas tabelas, gravadas numa transação. No modelo de documentos, é um único documento.

No PostgreSQL, com transação

Os exemplos usam o driver mais comum de cada linguagem, sem ORM, e as tabelas definidas no nível Conceito. Todos seguem o mesmo roteiro: abrir a transação, gravar o pedido, gravar os itens e confirmar, ou desfazer tudo se algo falhar.

import { Pool } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

type Item = { produtoId: number; quantidade: number };

export async function criarPedido(clienteId: number, itens: Item[]) {
  const conexao = await pool.connect();
  try {
    await conexao.query("BEGIN");

    const { rows } = await conexao.query(
      `INSERT INTO pedidos (cliente_id, status)
       VALUES ($1, 'aberto') RETURNING id`,
      [clienteId],
    );
    const pedidoId = rows[0].id;

    for (const item of itens) {
      await conexao.query(
        `INSERT INTO itens_pedido (pedido_id, produto_id, quantidade)
         VALUES ($1, $2, $3)`,
        [pedidoId, item.produtoId, item.quantidade],
      );
    }

    await conexao.query("COMMIT");
    return pedidoId;
  } catch (erro) {
    await conexao.query("ROLLBACK");
    throw erro;
  } finally {
    conexao.release();
  }
}

O que muda de uma linguagem para outra

DriverMarcador de parâmetroComo a transação é desfeita
Nodepg$1, $2ROLLBACK explícito no catch
Pythonpsycopg 3%sAutomático, se o bloco with lançar exceção
Godatabase/sql com driver do PostgreSQL$1, $2defer tx.Rollback(), sem efeito depois do Commit
JavaJDBC?rollback() explícito no catch
C#Npgsql$1, $2Automático, ao descartar a transação sem Commit

Dois pontos que o código gerado erra com frequência:

  • No Node, chamar pool.query em vez de conexao.query dentro da transação manda cada comando para uma conexão possivelmente diferente do pool. O BEGIN vai para uma conexão, o INSERT para outra, e a transação não existe de fato.
  • No Java, a conexão volta ao pool com autoCommit desligado se o código não restaurar o valor. Algumas bibliotecas de pool restauram; outras não.

Em todos os exemplos, os valores vão como parâmetros, nunca concatenados na string do SQL. É isso que impede a injeção de SQL.

No MongoDB, um documento

No modelo de documentos, o pedido e os itens formam um único documento, gravado de uma vez:

import { MongoClient } from "mongodb";

const cliente = new MongoClient(process.env.MONGODB_URL!);
const pedidos = cliente.db("loja").collection("pedidos");

export async function criarPedido(
  clienteId: number,
  nomeCliente: string,
  itens: { produtoId: number; quantidade: number }[],
) {
  const { insertedId } = await pedidos.insertOne({
    cliente: { id: clienteId, nome: nomeCliente }, // cópia intencional
    status: "aberto",
    itens,
    criadoEm: new Date(),
  });
  return insertedId;
}

Não há transação no código, e ela não faz falta: no MongoDB, a gravação de um único documento já é atômica. O pedido nunca fica sem itens.

A transação volta a ser necessária quando a operação toca mais de um documento, como baixar o estoque de cada produto junto com a criação do pedido. O MongoDB aceita transações entre documentos, mas elas custam mais e exigem um conjunto de réplicas. Se o desenho exige transações o tempo todo, o modelo relacional provavelmente serve melhor a esses dados.

As consultas, lado a lado

PerguntaPostgreSQLMongoDB
Um pedido com os itensJOIN entre pedidos e itens_pedidofindOne({ _id }); os itens já estão no documento
Pedidos de um clienteWHERE cliente_id = $1, com índicefind({ "cliente.id": id }), com índice
Produtos mais vendidos no mêsGROUP BY produto_id com SUMPipeline de agregação com $unwind e $group
O cliente mudou de nomeUm UPDATE em clientesDecidir se os pedidos antigos também mudam; se sim, updateMany

A última linha resume a troca: o documento facilita a leitura e transfere à aplicação a responsabilidade pelos dados repetidos.

Lista de revisão

Na escolha

  • As principais consultas do sistema estão listadas, e o banco atende todas bem?
  • O sistema precisa de transações entre várias entidades? Com que frequência?
  • A equipe sabe operar, monitorar e fazer backup desse banco?

No relacional

  • As regras importantes estão no banco: chaves estrangeiras, NOT NULL, UNIQUE, CHECK?
  • Operações com várias gravações estão numa transação, na mesma conexão?
  • Todo valor vai como parâmetro, nunca concatenado no SQL?

Em documentos

  • Existe validação de esquema no banco ou uma camada obrigatória na aplicação?
  • Nenhuma lista embutida cresce sem limite?
  • Para cada dado copiado entre documentos, está decidido quem atualiza as cópias?
  • As consultas frequentes têm índice?

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • A mesma funcionalidade em SQL e em documentos

    Quero implementar a mesma funcionalidade num banco relacional e num banco de documentos, para sentir as diferenças.
    
    Pergunte em qual linguagem eu trabalho.
    
    A funcionalidade é um carrinho de compras que vira pedido: adicionar itens, finalizar o pedido baixando o estoque e listar os pedidos de um cliente com os produtos.
    
    Vamos em etapas, primeiro com PostgreSQL e depois com MongoDB. Em cada etapa, deixe que eu tente e depois mostre a sua versão:
    1. A modelagem: tabelas com restrições, e documentos com validação.
    2. Finalizar o pedido com baixa de estoque, discutindo onde é preciso transação em cada banco.
    3. A listagem de pedidos com os produtos.
    4. Um relatório dos produtos mais vendidos no mês.
    
    No final, monte comigo uma tabela comparando as duas versões: linhas de código, onde ficaram as regras de integridade e o que foi mais trabalhoso em cada uma.
  • Usar JSONB no PostgreSQL

    Quero aprender a usar colunas JSONB no PostgreSQL para dados com formato variável, sem abrir mão do modelo relacional.
    
    Pergunte em qual linguagem e driver eu trabalho.
    
    Explique, com exemplos de SQL e de código:
    1. A diferença entre JSON e JSONB.
    2. Como gravar, ler e alterar partes de um documento JSONB.
    3. Os operadores de consulta mais usados e como criar índices GIN e índices sobre expressões.
    4. Como validar o formato do JSONB com restrições CHECK ou na aplicação.
    5. Quando um campo deve sair do JSONB e virar uma coluna própria.
    
    Use o exemplo de um catálogo em que cada categoria de produto tem atributos diferentes.
  • Revisar o acesso ao banco

    Vou colar abaixo código que acessa um banco de dados, relacional ou de documentos, e talvez o esquema ou o desenho dos documentos. Revise com foco no uso correto do modelo de dados.
    
    Verifique, e para cada item diga se está correto, incorreto ou ausente, citando a linha:
    1. Os valores vão como parâmetros, nunca concatenados na consulta?
    2. Operações com várias gravações estão numa transação, na mesma conexão ou sessão?
    3. As regras de integridade importantes estão no banco, ou ao menos numa camada obrigatória?
    4. Há listas embutidas que podem crescer sem limite?
    5. Para dados copiados, está claro quem atualiza as cópias?
    6. As consultas frequentes têm índice?
    7. O modelo escolhido combina com as consultas que o código faz?
    
    Para cada problema, explique a consequência em produção e mostre a correção. Não reescreva o que já está correto.
    
    Código e esquema:
    [cole aqui]