Pesquisar conteúdos

Encontre uma trilha ou aula pelo assunto.

Experimente pesquisar por “governança”, “ITIL” ou “planejamento”.

INSERT, UPDATE, DELETE, constraints e views

Modifique dados com segurança, aplique restrições no esquema e use visões para oferecer consultas reutilizáveis.
Uma tabela recebe, altera e remove linhas antes de atravessar regras de validação e ser observada por uma view que projeta parte dos dados

Modificar dados não é apenas escolher entre INSERT, UPDATE e DELETE. Uma alteração confiável precisa responder qual conjunto será atingido, qual estado será aceito e como o resultado será verificado.

Na introdução à SQL, esses comandos apareceram como membros da DML. Aqui, o foco muda da classificação para o uso operacional: construir instruções previsíveis, permitir que o esquema rejeite estados inválidos e expor consultas estáveis por meio de views.

Ao final, você deverá conseguir:

  • inserir uma ou várias linhas declarando as colunas de destino;
  • delimitar UPDATE e DELETE por predicados verificáveis;
  • usar RETURNING para observar as linhas realmente modificadas no PostgreSQL;
  • distinguir valor padrão de regra de validade;
  • escolher constraints que expressem presença, domínio, identidade e referência;
  • explicar o que uma view comum armazena — e o que ela não armazena;
  • reconhecer os limites de views atualizáveis e de seu uso como mecanismo de segurança.
Um requisito define o escopo, um SELECT antecipa as linhas, INSERT UPDATE ou DELETE executa a mudança, constraints validam o novo estado e as linhas modificadas retornam como evidência
Segurança vem do fluxo completo: requisito, prévia, modificação, validação e conferência. Nenhuma palavra-chave substitui as outras etapas.

DML transforma conjuntos de linhas

SQL é orientada a conjuntos. Uma instrução pode atingir zero, uma ou muitas linhas; o banco não pressupõe que você pretendia alterar “apenas aquela linha da tela”.

Considere uma tabela simplificada:

CREATE TABLE conteudo (
  conteudo_id bigint GENERATED ALWAYS AS IDENTITY,
  titulo text NOT NULL,
  situacao text NOT NULL DEFAULT 'rascunho',
  duracao_minutos integer NOT NULL,
  publicado_em timestamptz,
  CONSTRAINT pk_conteudo PRIMARY KEY (conteudo_id),
  CONSTRAINT ck_conteudo_situacao
    CHECK (situacao IN ('rascunho', 'revisao', 'publicado')),
  CONSTRAINT ck_conteudo_duracao
    CHECK (duracao_minutos > 0),
  CONSTRAINT ck_conteudo_publicacao
    CHECK (
      situacao <> 'publicado'
      OR publicado_em IS NOT NULL
    )
);

As três operações de DML atuam sobre estados diferentes:

OperaçãoEntrada principalEfeito esperado
INSERTvalores ou resultado de uma consultacria linhas completas, preenchendo omissões com defaults ou NULL permitido
UPDATElinhas existentes selecionadas por condiçãocalcula novos valores para as colunas indicadas
DELETElinhas existentes selecionadas por condiçãoremove as linhas do conjunto alvo

Cada resultado ainda precisa satisfazer as constraints aplicáveis.

INSERT cria linhas com um contrato explícito

Prefira declarar as colunas de destino:

INSERT INTO conteudo (
  titulo,
  duracao_minutos
)
VALUES (
  'Introdução a índices',
  18
);

situacao recebe o default rascunho; conteudo_id é gerado pelo banco; publicado_em recebe NULL, que é permitido enquanto o conteúdo não está publicado.

Sem a lista de colunas, os valores dependem da ordem física declarada na tabela. Uma evolução do esquema pode quebrar o comando ou, pior, associar valores às colunas erradas quando houver conversões compatíveis.

Uma instrução pode inserir várias linhas

INSERT INTO conteudo (titulo, duracao_minutos)
VALUES
  ('Índices B-tree', 22),
  ('Leitura de planos', 25),
  ('Estatísticas do otimizador', 20);

O comando continua sendo uma unidade SQL, mas isso não significa que qualquer lote caiba em um único INSERT. Para cargas grandes, formato de arquivo, memória, registro de transação, validação e utilitários próprios do SGBD precisam ser avaliados.

INSERT SELECT transforma e carrega um resultado

INSERT INTO conteudo_arquivo (
  conteudo_id,
  titulo,
  arquivado_em
)
SELECT
  conteudo_id,
  titulo,
  CURRENT_TIMESTAMP
FROM conteudo
WHERE situacao = 'rascunho'
  AND publicado_em IS NULL;

A consulta pode produzir zero, uma ou muitas linhas. Compare as colunas por posição, confirme tipos compatíveis e revise o conjunto do SELECT separadamente antes da inserção.

DEFAULT preenche; não valida intenção

Um default é usado quando a coluna é omitida ou quando DEFAULT é solicitado. Ele não impede um valor explícito diferente:

INSERT INTO conteudo (
  titulo,
  situacao,
  duracao_minutos,
  publicado_em
)
VALUES (
  'Transações na prática',
  'publicado',
  24,
  CURRENT_TIMESTAMP
);

O default oferece uma escolha automática. A constraint decide se o estado final é admissível.

Conflito de unicidade exige uma política

No PostgreSQL, ON CONFLICT permite declarar o que fazer quando uma constraint ou índice de unicidade detecta conflito. Suponha uma chave única para slug:

INSERT INTO conteudo (slug, titulo, duracao_minutos)
VALUES ('indices-btree', 'Índices B-tree', 22)
ON CONFLICT (slug) DO UPDATE
SET
  titulo = EXCLUDED.titulo,
  duracao_minutos = EXCLUDED.duracao_minutos
RETURNING conteudo_id, slug, titulo;

EXCLUDED representa a linha proposta. A instrução não torna qualquer processo automaticamente idempotente: você ainda precisa definir a identidade correta, quais campos podem ser substituídos e como concorrência e efeitos externos serão tratados.

ON CONFLICT e RETURNING são recursos do PostgreSQL; outros SGBDs oferecem sintaxes e garantias diferentes.

UPDATE precisa identificar o estado anterior

Um UPDATE possui três contratos: a tabela alvo, o cálculo do novo valor e o predicado que escolhe as linhas.

UPDATE conteudo
SET
  situacao = 'publicado',
  publicado_em = CURRENT_TIMESTAMP
WHERE conteudo_id = 42
  AND situacao = 'revisao';

O teste de situacao não é decorativo. Ele expressa a transição permitida: somente um conteúdo ainda em revisão pode ser publicado por esse comando.

Resultados possíveis:

  • uma linha: a transição esperada ocorreu;
  • zero linhas: o identificador não existe ou o estado anterior não era revisao;
  • mais de uma linha: não seria possível neste exemplo se conteudo_id for chave primária, mas pode ocorrer com predicados não únicos.

Zero linhas afetadas não é erro SQL. A aplicação precisa decidir se isso representa concorrência, repetição segura da operação ou requisito não atendido.

O novo valor pode usar o valor atual

UPDATE conteudo
SET duracao_minutos = duracao_minutos + 5
WHERE conteudo_id = 42;

Todos os valores da expressão são lidos no contexto da linha antes daquela atribuição produzir o novo estado. Não use incremento quando o requisito real fornece o valor absoluto; repetir uma solicitação de incremento pode aplicar o efeito duas vezes.

Atualizações a partir de outra relação pedem chave inequívoca

O PostgreSQL aceita UPDATE ... FROM:

UPDATE conteudo AS c
SET duracao_minutos = r.duracao_revisada
FROM revisao_conteudo AS r
WHERE r.conteudo_id = c.conteudo_id
  AND r.aprovada = true;

Garanta que cada linha alvo corresponda a no máximo uma linha fonte. Se o join produzir várias correspondências para a mesma linha alvo, qual delas fornecerá o valor não deve ser tratado como escolha de negócio confiável. Corrija a chave ou reduza a fonte a uma linha determinística.

Essa forma é específica do produto; verifique a sintaxe do SGBD utilizado.

DELETE remove linhas, não “apaga campos”

DELETE FROM conteudo
WHERE conteudo_id = 42
  AND situacao = 'rascunho';

DELETE remove a linha inteira. Para retirar um valor opcional, use UPDATE ... SET coluna = NULL quando o modelo permitir. Para preservar histórico, talvez o domínio peça arquivamento ou mudança de estado, e não exclusão física.

Sem WHERE, todas as linhas elegíveis são removidas:

DELETE FROM conteudo;

Essa instrução é válida. Ferramentas podem alertar, mas o banco não conhece a intenção humana de manter algumas linhas.

Chaves estrangeiras decidem o efeito sobre dependentes

Se matricula.conteudo_id referencia conteudo.conteudo_id, a ação da foreign key define o resultado:

  • RESTRICT ou NO ACTION: impedem a remoção enquanto existirem referências, observadas as regras do SGBD e do momento de validação;
  • CASCADE: remove ou atualiza registros dependentes conforme a ação;
  • SET NULL: preserva o dependente e remove a referência, exigindo coluna anulável;
  • SET DEFAULT: aplica o default, que ainda precisa satisfazer a foreign key.

Escolha pela semântica do domínio. CASCADE não é uma conveniência universal, e SET NULL não faz sentido quando uma matrícula sem conteúdo deixa de representar um fato válido.

Faça a prévia com o mesmo predicado

Antes de uma alteração sensível, transforme o alvo em uma consulta observável:

SELECT
  conteudo_id,
  titulo,
  situacao,
  publicado_em
FROM conteudo
WHERE situacao = 'revisao'
  AND publicado_em IS NULL
ORDER BY conteudo_id;

Depois, reutilize a mesma condição:

UPDATE conteudo
SET
  situacao = 'publicado',
  publicado_em = CURRENT_TIMESTAMP
WHERE situacao = 'revisao'
  AND publicado_em IS NULL;

Confira:

  1. a quantidade esperada;
  2. chaves representativas, não apenas uma contagem;
  3. valores atuais usados na decisão;
  4. ausência de duplicidade causada por joins;
  5. se o estado pode ter mudado entre a prévia e a execução.

Uma prévia feita muito antes não congela o conjunto. A próxima aula explicará como transações e isolamento alteram essa análise.

RETURNING mostra o que a instrução modificou

No PostgreSQL, INSERT, UPDATE e DELETE aceitam RETURNING:

UPDATE conteudo
SET situacao = 'revisao'
WHERE conteudo_id = 42
  AND situacao = 'rascunho'
RETURNING
  conteudo_id,
  titulo,
  situacao;

A cláusula retorna somente linhas efetivamente alteradas. Isso permite obter identificadores gerados, conferir valores calculados pelo banco e detectar um conjunto vazio sem executar um SELECT posterior.

DELETE FROM conteudo
WHERE conteudo_id = 42
  AND situacao = 'rascunho'
RETURNING conteudo_id, titulo;

Em DELETE, os valores retornados pertencem às linhas removidas. Não use RETURNING * por hábito em aplicações: colunas grandes, sensíveis ou adicionadas futuramente podem aumentar tráfego e exposição. Declare o contrato necessário.

Constraints rejeitam estados inválidos na fronteira comum

Validação na aplicação melhora a experiência do usuário, mas não cobre importações, scripts, integrações e acessos diretos. Uma constraint declarada no banco acompanha todas essas entradas.

Uma linha candidata atravessa NOT NULL, CHECK, UNIQUE ou PRIMARY KEY e FOREIGN KEY antes de ser aceita na tabela
Cada constraint responde a uma pergunta diferente. Em conjunto, elas transformam regras persistentes do domínio em um contrato verificável pelo banco.

NOT NULL exige presença

titulo text NOT NULL

NOT NULL impede o marcador NULL. Não impede texto vazio, espaços ou um valor semanticamente absurdo. Essas são regras distintas.

CHECK valida uma expressão sobre a linha

CONSTRAINT ck_conteudo_duracao
  CHECK (duracao_minutos BETWEEN 1 AND 600)

No PostgreSQL, a constraint CHECK é satisfeita quando a expressão resulta em TRUE ou NULL; ela rejeita FALSE. Portanto, se a ausência também é proibida, combine com NOT NULL.

Uma CHECK deve depender dos valores da própria linha e de uma expressão estável. Regras entre linhas pedem mecanismos como UNIQUE, EXCLUDE ou modelagem apropriada; regras entre tabelas normalmente pedem FOREIGN KEY. Consultar arbitrariamente outras linhas dentro de uma CHECK não oferece a garantia contínua que o nome sugere no PostgreSQL.

UNIQUE protege uma chave candidata

CONSTRAINT uq_conteudo_slug UNIQUE (slug)

UNIQUE impede combinações repetidas segundo a semântica de nulos do SGBD. Não confunda com PRIMARY KEY: uma tabela pode possuir várias chaves candidatas únicas, mas uma única chave primária identificada como principal.

PRIMARY KEY identifica cada linha

CONSTRAINT pk_conteudo PRIMARY KEY (conteudo_id)

No PostgreSQL, a primary key combina unicidade e não nulidade e cria um índice B-tree único. O índice ajuda a implementar a regra, mas a constraint também documenta a identidade usada por referências e ferramentas.

FOREIGN KEY preserva referências

CREATE TABLE progresso (
  estudante_id bigint NOT NULL,
  conteudo_id bigint NOT NULL,
  percentual numeric(5,2) NOT NULL,
  CONSTRAINT pk_progresso
    PRIMARY KEY (estudante_id, conteudo_id),
  CONSTRAINT fk_progresso_conteudo
    FOREIGN KEY (conteudo_id)
    REFERENCES conteudo (conteudo_id),
  CONSTRAINT ck_progresso_percentual
    CHECK (percentual BETWEEN 0 AND 100)
);

A foreign key não diz que o conteúdo é adequado ao estudante, apenas que a referência informada existe — ou é NULL, caso a coluna aceite ausência. Regras de negócio continuam precisando de modelagem explícita.

Para aprofundar identidade, referência e ações, consulte chaves primárias, estrangeiras e relacionamentos e integridade de entidade, referencial e regras de negócio.

Nomeie constraints pelo papel

Nomes como pk_conteudo, uq_conteudo_slug, fk_progresso_conteudo e ck_progresso_percentual tornam mensagens, migrações e investigação mais claras. O nome deve permanecer inteligível sem depender da ordem em que a constraint foi criada.

View oferece um nome para uma consulta

Uma view comum guarda a definição da consulta, não uma cópia materializada de suas linhas:

CREATE VIEW conteudos_publicados AS
SELECT
  conteudo_id,
  titulo,
  duracao_minutos,
  publicado_em
FROM conteudo
WHERE situacao = 'publicado';

Quando alguém consulta conteudos_publicados, o SGBD usa essa definição sobre os dados atuais das tabelas de base.

SELECT titulo, duracao_minutos
FROM conteudos_publicados
ORDER BY publicado_em DESC, conteudo_id DESC;

Não confunda com materialized view, que armazena fisicamente o resultado e precisa de uma política de atualização. Esse é outro objeto, com comportamento dependente do produto.

Duas tabelas de base alimentam uma definição de view com fontes, colunas, filtro e nomes explícitos; três consumidores consultam esse contrato lógico
A view centraliza uma forma de leitura. Os dados permanecem nas tabelas de base, e o contrato depende dos nomes, tipos e significado das colunas expostas.

Declare as colunas do contrato

Evite SELECT * em views duradouras. Liste as colunas, aliases e transformações que os consumidores podem assumir:

CREATE VIEW resumo_conteudo AS
SELECT
  c.conteudo_id,
  c.titulo,
  c.duracao_minutos,
  COUNT(p.estudante_id) AS estudantes_com_progresso
FROM conteudo AS c
LEFT JOIN progresso AS p
  ON p.conteudo_id = c.conteudo_id
GROUP BY
  c.conteudo_id,
  c.titulo,
  c.duracao_minutos;

Alterar nome, tipo ou significado de uma coluna exposta pode quebrar aplicações, relatórios e outras views. Trate a definição como interface versionada, não como atalho sem proprietário.

View não garante desempenho melhor

Uma view comum não torna uma consulta rápida por existir. O otimizador ainda precisa planejar a consulta resultante, e joins, filtros, índices e estatísticas continuam relevantes. A aula de índices e planos tratará a investigação de desempenho.

Algumas views são atualizáveis; outras não

No PostgreSQL, views simples sobre uma única tabela podem ser automaticamente atualizáveis. Agregações, operações de conjunto, DISTINCT, GROUP BY e outras construções tornam a view somente leitura por padrão.

Mesmo em uma view atualizável com filtro, uma modificação poderia gerar uma linha que deixa de aparecer nela. WITH CHECK OPTION exige que o novo estado continue visível pela condição:

CREATE VIEW rascunhos AS
SELECT
  conteudo_id,
  titulo,
  duracao_minutos,
  situacao
FROM conteudo
WHERE situacao = 'rascunho'
WITH LOCAL CHECK OPTION;

As regras de atualizabilidade, LOCAL e CASCADED variam entre produtos. Para comandos críticos, prefira não depender de comportamento implícito sem testes e documentação.

View pode participar do controle de acesso, mas não é segurança automática

Permissões sobre uma view podem oferecer acesso a um recorte sem conceder consulta direta a todas as colunas da tabela. Porém, proprietário, privilégios, funções chamadas, políticas por linha e opções como security_invoker e security_barrier mudam o modelo efetivo no PostgreSQL.

Não rotule uma view como “segura” apenas porque contém um WHERE. Modele ameaças, teste com os papéis reais e aplique menor privilégio. A trilha aprofundará usuários e privilégios em uma aula própria.

Exemplo integrado: publicar com condição e evidência

Requisito: publicar um conteúdo somente se ele ainda estiver em revisão, tiver duração válida e não possuir outro conteúdo com o mesmo slug.

O esquema protege os invariantes persistentes:

ALTER TABLE conteudo
  ADD COLUMN slug text,
  ADD CONSTRAINT uq_conteudo_slug UNIQUE (slug),
  ADD CONSTRAINT ck_conteudo_slug
    CHECK (slug IS NULL OR slug <> '');

A operação expressa a transição:

UPDATE conteudo
SET
  situacao = 'publicado',
  publicado_em = CURRENT_TIMESTAMP
WHERE conteudo_id = $1
  AND situacao = 'revisao'
RETURNING
  conteudo_id,
  slug,
  situacao,
  publicado_em;

O consumidor interpreta o resultado:

  • uma linha retornada confirma a transição;
  • nenhuma linha exige verificar existência e estado anterior;
  • violação de constraint indica que o estado proposto não satisfaz o contrato;
  • falha técnica não autoriza repetir cegamente uma operação não idempotente.

A view oferece leitura estável do estado publicado:

CREATE VIEW catalogo_publicado AS
SELECT
  conteudo_id,
  slug,
  titulo,
  duracao_minutos,
  publicado_em
FROM conteudo
WHERE situacao = 'publicado';

Cada camada tem uma responsabilidade: DML descreve a mudança, constraints protegem invariantes e a view define uma forma de leitura. Nenhuma delas substitui as demais.

Erros comuns

  • omitir a lista de colunas no INSERT: o comando fica acoplado à ordem do esquema;
  • confundir DEFAULT com validação: um valor automático não impede valores explícitos inválidos;
  • executar UPDATE ou DELETE sem prévia: o conjunto real pode ser maior ou diferente do imaginado;
  • usar predicado não único esperando uma linha: zero ou várias linhas são resultados válidos;
  • não verificar zero linhas afetadas: uma transição condicional pode não ter ocorrido;
  • fazer UPDATE FROM com várias fontes por alvo: o valor escolhido não representa uma regra determinística;
  • usar DELETE quando o domínio exige histórico: remoção física pode apagar evidência necessária;
  • escolher CASCADE por conveniência: dependentes podem ser removidos além do pretendido;
  • supor que CHECK rejeita NULL: no PostgreSQL, NULL não produz FALSE; use NOT NULL quando necessário;
  • consultar outras linhas arbitrariamente em CHECK: a constraint não mantém esse tipo de garantia no PostgreSQL;
  • usar RETURNING * sem contrato: dados e colunas desnecessários podem ser expostos;
  • imaginar que view duplica ou congela dados: uma view comum executa sua consulta sobre o estado atual;
  • usar SELECT * na definição da view: o contrato fica implícito e frágil;
  • assumir que toda view é atualizável: a consulta definidora determina essa possibilidade;
  • tratar view filtrada como segurança suficiente: permissões e contexto de execução precisam ser avaliados.

Checklist antes de modificar ou expor dados

  1. O requisito descreve o estado anterior e o estado desejado?
  2. A lista de colunas do INSERT está explícita?
  3. Defaults e colunas omitidas produzem um estado válido?
  4. O predicado foi executado como SELECT e revisado?
  5. A chave usada corresponde à cardinalidade esperada?
  6. Zero, uma ou muitas linhas são tratadas pela aplicação?
  7. Há joins que multiplicam a fonte de uma atualização?
  8. As ações de foreign key representam o ciclo de vida do domínio?
  9. Constraints persistentes cobrem presença, domínio, identidade e referência?
  10. RETURNING devolve apenas a evidência necessária?
  11. A operação precisa ser idempotente ou protegida contra repetição?
  12. A view declara nomes, tipos e significado das colunas?
  13. Consumidores e dependências foram identificados antes de alterar a view?
  14. Atualizabilidade e permissões foram testadas no SGBD e versão reais?
  15. A mudança precisa integrar uma transação com outras operações?

O que você deve guardar

INSERT, UPDATE e DELETE transformam conjuntos e podem atingir cardinalidades diferentes da expectativa humana. Predicados, chaves e conferência tornam o alcance explícito. Constraints mantêm invariantes na fronteira compartilhada do banco. Views nomeiam contratos de consulta sobre os dados atuais e precisam ser tratadas como interfaces com dependências.

Na próxima aula, veremos como transações agrupam operações e como atomicidade, consistência, isolamento e durabilidade ajudam a preservar resultados diante de falhas e concorrência.

Referências