INSERT, UPDATE, DELETE, constraints e views

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
UPDATEeDELETEpor predicados verificáveis; - usar
RETURNINGpara 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.
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ção | Entrada principal | Efeito esperado |
|---|---|---|
INSERT | valores ou resultado de uma consulta | cria linhas completas, preenchendo omissões com defaults ou NULL permitido |
UPDATE | linhas existentes selecionadas por condição | calcula novos valores para as colunas indicadas |
DELETE | linhas existentes selecionadas por condição | remove 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_idfor 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:
RESTRICTouNO 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:
- a quantidade esperada;
- chaves representativas, não apenas uma contagem;
- valores atuais usados na decisão;
- ausência de duplicidade causada por joins;
- 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.
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.
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,
NULLnão produzFALSE; useNOT NULLquando 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
- O requisito descreve o estado anterior e o estado desejado?
- A lista de colunas do
INSERTestá explícita? - Defaults e colunas omitidas produzem um estado válido?
- O predicado foi executado como
SELECTe revisado? - A chave usada corresponde à cardinalidade esperada?
- Zero, uma ou muitas linhas são tratadas pela aplicação?
- Há joins que multiplicam a fonte de uma atualização?
- As ações de foreign key representam o ciclo de vida do domínio?
- Constraints persistentes cobrem presença, domínio, identidade e referência?
RETURNINGdevolve apenas a evidência necessária?- A operação precisa ser idempotente ou protegida contra repetição?
- A view declara nomes, tipos e significado das colunas?
- Consumidores e dependências foram identificados antes de alterar a view?
- Atualizabilidade e permissões foram testadas no SGBD e versão reais?
- 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
- ISO — ISO/IEC 9075-2:2023, SQL/Foundation. Acesso em 30 ago. 2026.
- PostgreSQL Global Development Group — Inserting Data. Acesso em 30 ago. 2026.
- PostgreSQL Global Development Group — Updating Data. Acesso em 30 ago. 2026.
- PostgreSQL Global Development Group — Deleting Data. Acesso em 30 ago. 2026.
- PostgreSQL Global Development Group — Returning Data from Modified Rows. Acesso em 30 ago. 2026.
- PostgreSQL Global Development Group — Constraints. Acesso em 30 ago. 2026.
- PostgreSQL Global Development Group — CREATE VIEW. Acesso em 30 ago. 2026.
