Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

SQL: SELECT, INSERT, UPDATE e DELETE no PostgreSQL

Aprenda as quatro operações básicas de SQL numa tabela de livros, com WHERE, RETURNING, constraints, saídas reais e um roteiro contra alterações acidentais.

Rodolfo Mori5 min de leitura

SELECT, INSERT, UPDATE e DELETE são os quatro comandos usados para ler, criar, alterar e remover linhas em SQL. Nesta lição, você vai aplicar os quatro no catálogo de uma livraria e conferir cada mudança pela saída do PostgreSQL.

O conjunto costuma receber o nome CRUD: create, read, update e delete. INSERT faz o create, SELECT faz o read e os outros dois mantêm o próprio nome. CRUD é uma sigla de aplicação; no banco, os nomes que aparecem na documentação e nos logs são os comandos SQL.

Pense no balcão de estoque. INSERT abre uma ficha, SELECT consulta fichas, UPDATE corrige uma ficha existente e DELETE a retira do catálogo. A tabela é o fichário e WHERE é o critério escrito no pedido do atendente. O limite da analogia: SQL trabalha com conjuntos. Um comando pode atingir zero, uma ou milhares de linhas de uma vez, e o banco não pergunta se era isso mesmo que você queria.

Confira onde você está antes de escrever

Abra o psql conectado ao database livraria. Antes de criar qualquer coisa, confirme database, usuário e versão. Esse hábito evita executar uma manutenção na base errada:

sql
SELECT current_database() AS banco,
       current_user AS usuario,
       current_setting('server_version') AS versao;
banco | usuario | versao ----------+----------+-------- livraria | postgres | 18.6 (1 row)

Em produção, o usuário dificilmente será postgres; ele deve ter somente as permissões necessárias. Aqui usamos o administrador porque o banco é um laboratório local isolado.

A tabela já nasce sabendo o que rejeitar

O catálogo precisa de identidade, título, autor, preço, estoque e um marcador de ativo. A estrutura abaixo também coloca as duas regras numéricas no banco: Use um database vazio: este laboratório é independente dos outros artigos.

sql
CREATE TABLE livros (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  titulo text NOT NULL,
  autor text NOT NULL,
  preco numeric(10,2) NOT NULL CHECK (preco > 0),
  estoque integer NOT NULL DEFAULT 0 CHECK (estoque >= 0),
  ativo boolean NOT NULL DEFAULT true
);
CREATE TABLE

numeric(10,2) guarda um valor decimal com duas casas sem a aproximação binária de ponto flutuante. DEFAULT não significa “aceita qualquer coisa”: ele entra somente quando a coluna é omitida. Se alguém enviar NULL para estoque, o NOT NULL ainda rejeita.

Se chave, tipo e constraint ainda parecem decisões soltas, o guia de PostgreSQL mostra o lugar de cada uma no banco.

INSERT: cadastre e peça o recibo da linha

Liste as colunas no comando. Assim, os valores continuam ligados a nomes mesmo que a tabela ganhe outro campo no futuro. RETURNING traz os ids gerados e os valores que realmente entraram:

sql
INSERT INTO livros (titulo, autor, preco, estoque)
VALUES
  ('Dom Casmurro', 'Machado de Assis', 39.90, 8),
  ('Torto Arado', 'Itamar Vieira Junior', 54.90, 3),
  ('A Hora da Estrela', 'Clarice Lispector', 34.50, 0)
RETURNING id, titulo, preco, estoque;
id | titulo | preco | estoque ----+-------------------+-------+--------- 1 | Dom Casmurro | 39.90 | 8 2 | Torto Arado | 54.90 | 3 3 | A Hora da Estrela | 34.50 | 0 (3 rows)

INSERT 0 3

INSERT 0 3 é a tag do comando: três linhas foram inseridas. A tabela acima dela veio do RETURNING. Numa API, você pode devolver esse id ao cliente sem fazer outra viagem ao banco.

SELECT: descreva o conjunto que quer receber

Agora a pergunta é “quais livros ativos estão disponíveis, do mais barato para o mais caro, no máximo dois?”. Cada parte da consulta corresponde a um pedaço dessa frase:

sql
SELECT titulo, preco, estoque
FROM livros
WHERE ativo = true AND estoque > 0
ORDER BY preco
LIMIT 2;
titulo | preco | estoque --------------+-------+--------- Dom Casmurro | 39.90 | 8 Torto Arado | 54.90 | 3 (2 rows)

WHERE não é um laço escrito por você; é uma condição que cada linha do resultado precisa satisfazer. A ordem sem ASC é crescente. LIMIT corta a saída depois da ordenação — sem ORDER BY, falar em “primeiros dois” não tem um significado estável.

Um SELECT também pode calcular colunas que não estão armazenadas. O preço com 10% de desconto nasce no resultado, sem alterar o catálogo:

sql
SELECT titulo,
       preco,
       round(preco * 0.90, 2) AS preco_com_desconto
FROM livros
ORDER BY id;
titulo | preco | preco_com_desconto -------------------+-------+-------------------- Dom Casmurro | 39.90 | 35.91 Torto Arado | 54.90 | 49.41 A Hora da Estrela | 34.50 | 31.05 (3 rows)

AS cria um apelido para a coluna do resultado. Ele não renomeia a coluna da tabela. Essa diferença fica importante quando o SELECT alimentar uma API ou um relatório.

UPDATE: mude só o conjunto que você conferiu

A Livraria Horizonte recebeu cinco exemplares de Torto Arado e ajustou o preço. A expressão usa o estoque atual da própria linha:

sql
UPDATE livros
SET estoque = estoque + 5,
    preco = 52.90
WHERE titulo = 'Torto Arado'
RETURNING id, titulo, preco, estoque;
id | titulo | preco | estoque ----+-------------+-------+--------- 2 | Torto Arado | 52.90 | 8 (1 row)

UPDATE 1

O UPDATE 1 merece tanta atenção quanto os valores. Se você esperava uma linha e recebeu UPDATE 0, o filtro não encontrou nada. Se recebeu UPDATE 300, pare e investigue antes de seguir. A contagem não é decoração do terminal; é uma checagem da intenção.

Antes de qualquer UPDATE manual, troque temporariamente a primeira palavra por SELECT * e confira as linhas com o mesmo WHERE. Depois restaure o comando. Quando a alteração envolver mais de uma tabela, use a lição de transações.

O erro de constraint protege o estado anterior

Vamos tentar deixar o estoque negativo. A sintaxe está correta, mas o valor viola a regra declarada na criação da tabela:

sql
UPDATE livros
SET estoque = -1
WHERE id = 1;
ERROR: new row for relation "livros" violates check constraint "livros_estoque_check" DETAIL: Failing row contains (1, Dom Casmurro, Machado de Assis, 39.90, -1, t).

O PostgreSQL avalia a nova versão da linha, vê que estoque >= 0 seria falso e rejeita o comando inteiro. O dado anterior não fica parcialmente alterado:

sql
SELECT id, titulo, estoque
FROM livros
WHERE id = 1;
id | titulo | estoque ----+--------------+--------- 1 | Dom Casmurro | 8 (1 row)

Esse erro não pede que você remova a constraint. Ele avisa que a aplicação tentou produzir um estado proibido. A correção pode ser validar quantidade, recusar a venda ou usar um UPDATE condicionado por estoque disponível.

DELETE: remova linhas, não a estrutura

Para tornar o efeito visível, cadastre uma linha desativada e depois remova somente registros nessa condição:

sql
INSERT INTO livros (titulo, autor, preco, estoque, ativo)
VALUES ('Cadastro duplicado', 'Equipe da loja', 10.00, 0, false)
RETURNING id, titulo, ativo;
id | titulo | ativo ----+--------------------+------- 4 | Cadastro duplicado | f (1 row)

INSERT 0 1

sql
DELETE FROM livros
WHERE ativo = false
RETURNING id, titulo;
id | titulo ----+-------------------- 4 | Cadastro duplicado (1 row)

DELETE 1

DELETE remove linhas; DROP TABLE remove a tabela e sua definição. Sem WHERE, DELETE FROM livros apaga todas as linhas e o PostgreSQL não abre uma caixa de confirmação. O roteiro seguro é o mesmo do UPDATE: SELECT com o filtro, confira a contagem, execute dentro de transação quando houver risco e leia a tag final.

Feche a rodada com uma pergunta de negócio

Uma consulta agregada confirma o estado final: três títulos permanecem e há 16 unidades em estoque.

sql
SELECT count(*) AS livros_restantes,
       sum(estoque) AS unidades_em_estoque
FROM livros;
livros_restantes | unidades_em_estoque ------------------+--------------------- 3 | 16 (1 row)

Agora faça sua missão: cadastre Quarto de Despejo com cinco unidades, consulte somente livros abaixo de R$ 50, venda duas unidades com UPDATE ... RETURNING e desative um cadastro sem excluí-lo. O resultado passa se a última consulta mostrar quatro livros, o estoque novo for três e nenhuma linha fora do seu WHERE tiver mudado.

Uma tabela só foi suficiente para aprender os verbos. A livraria real separa autor, cliente, pedido e item para não repetir a mesma informação. O próximo passo é combinar essas tabelas com JOIN; o desenho que cria essas relações aparece depois em modelagem relacional.

  • sql
  • postgresql
  • select
  • insert
  • update
  • delete

Perguntas frequentes

Qual a diferença entre SELECT e os outros três comandos?
SELECT lê linhas e monta um resultado. INSERT cria linhas, UPDATE altera linhas existentes e DELETE remove linhas. Os três últimos mudam o estado do banco e precisam de atenção especial ao filtro.
UPDATE e DELETE precisam de WHERE?
A sintaxe permite omitir WHERE, mas então todas as linhas são atingidas. Em trabalho de manutenção, faça antes um SELECT com o mesmo filtro e use uma transação quando a operação tiver risco.
Para que serve RETURNING no PostgreSQL?
RETURNING devolve colunas das linhas inseridas, atualizadas ou removidas. Ele permite obter o id gerado e conferir a mudança sem enviar um SELECT separado.
Por que não usar SELECT asterisco em toda consulta?
Porque a aplicação passa a depender de todas as colunas e recebe dados que talvez não use. Nomear as colunas documenta o contrato e evita que uma nova coluna altere silenciosamente o formato da resposta.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em PostgreSQL 18.6 em postgres:18-alpine e Docker 29.5.3, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. PostgreSQL 18 — Consultando uma tabela — postgresql.org
  2. PostgreSQL 18 — Inserindo linhas — postgresql.org
  3. PostgreSQL 18 — UPDATE — postgresql.org
  4. PostgreSQL 18 — Dados modificados com RETURNING — postgresql.org

Continue por aqui