SQL ALTER TABLE
Nenhuma tabela nasce perfeita. A Loja Aurora começou sem o telefone dos clientes, e agora a Ana quer mandar mensagem a eles. Apagar a tabela e criar de novo não dá: os 30 clientes estão lá dentro. Para mudar a estrutura de uma tabela que já tem dados, existe o ALTER TABLE ("altere a tabela"). Nesta aula você vai ver as quatro mudanças que o SQLite faz com ele, as que ele não faz, e o roteiro seguro para essas outras: criar uma tabela nova e mudar os dados de casa.
- Renomear uma tabela (
RENAME TO) e uma coluna (RENAME COLUMN). - Acrescentar uma coluna (
ADD COLUMN), com e sem valor padrão, e as regras que ela não pode ter. - Apagar uma coluna (
DROP COLUMN), e quando o SQLite se recusa. - O que o SQLite não aceita: mudar o tipo de uma coluna, acrescentar um
CHECK, mudar várias coisas num comando só. - O roteiro para recriar uma tabela sem perder nada, inclusive uma que outras tabelas referenciam.
- Os erros reais e como o MySQL, o PostgreSQL e o SQL Server fazem.
Os exemplos rodam de verdade no seu navegador, e cada Rodar começa do banco limpo: pode mudar à vontade, nada fica estragado.
A ideia: reformar a casa com a família dentro
Uma tabela com dados é como uma casa com gente morando. Algumas reformas são fáceis e rápidas: trocar a placa com o nome da rua (renomear), pôr uma prateleira nova na parede (acrescentar coluna), tirar um quadro (apagar coluna). Outras são estruturais, como trocar o encanamento todo: aí não tem jeito, a família vai para uma casa nova já pronta, leva a mudança, e a casa velha é demolida. No SQLite, mudar o tipo de uma coluna é uma reforma desse segundo tipo.
| Você quer | No SQLite |
|---|---|
| Renomear a tabela | ALTER TABLE … RENAME TO … |
| Renomear uma coluna | ALTER TABLE … RENAME COLUMN … TO … |
| Acrescentar uma coluna | ALTER TABLE … ADD COLUMN … |
| Apagar uma coluna | ALTER TABLE … DROP COLUMN … |
Mudar o tipo, acrescentar CHECK, UNIQUE, chave… | Não tem comando: recriar a tabela (seção 6) |
1. Renomear a tabela: RENAME TO
A Ana decidiu que "fornecedores" vai se chamar "parceiros":
ALTER TABLE fornecedores RENAME TO parceiros; SELECT id, nome, cidade FROM parceiros LIMIT 3;
- ALTER TABLE fornecedores
- "Altere a tabela fornecedores": qual tabela vai mudar.
- RENAME TO
- "Renomeie para".
- parceiros
- O nome novo. Os dados não mudam em nada.
E a tabela produtos, que tinha uma chave estrangeira apontando para fornecedores? O SQLite atualizou sozinho:
ALTER TABLE fornecedores RENAME TO parceiros; SELECT sql FROM sqlite_schema WHERE name = 'produtos';
Repare na linha do fornecedor_id: agora ela diz REFERENCES "parceiros"(id). As aspas duplas foram postas pelo próprio SQLite; aqui elas marcam um nome, não um texto. O nome antigo, porém, deixou de existir: SELECT * FROM fornecedores; agora dá no such table: fornecedores. Todo programa e toda consulta salva que usavam o nome antigo param de funcionar. Renomeie com cuidado.
2. Renomear uma coluna: RENAME COLUMN
A coluna nascimento dos clientes ficaria mais clara como data_nascimento:
ALTER TABLE clientes RENAME COLUMN nascimento TO data_nascimento; SELECT nome, data_nascimento FROM clientes LIMIT 3;
- RENAME COLUMN nascimento
- "Renomeie a coluna nascimento". A palavra
COLUMN("coluna") pode ser omitida:RENAME nascimento TO …também funciona, mas com ela fica mais claro. - TO data_nascimento
- "Para data_nascimento". Os valores continuam os mesmos, na mesma coluna.
Experimente: o pessoal da loja chama o estado de "UF". Antes do SELECT, renomeie a coluna estado da tabela clientes para uf, e mude o SELECT para mostrar nome e uf dos clientes do Piauí ('PI').
SELECT nome, estado FROM clientes WHERE estado = 'PI';
ALTER TABLE clientes RENAME COLUMN estado TO uf; SELECT nome, uf FROM clientes WHERE uf = 'PI';
3. Acrescentar uma coluna: ADD COLUMN
Finalmente, o telefone dos clientes:
ALTER TABLE clientes ADD COLUMN telefone TEXT; SELECT id, nome, telefone FROM clientes LIMIT 3;
- ADD COLUMN
- "Acrescente a coluna". Como no
RENAME, a palavraCOLUMNé opcional. - telefone TEXT
- A coluna nova, escrita igual a uma linha do
CREATE TABLE: nome, tipo e, se quiser, regras.
A coluna nova vai sempre para o fim da tabela, e todas as linhas que já existiam ficam com ela NULL: o banco não tem como adivinhar o telefone de ninguém. No terminal, dá para ver como o SQLite guardou a mudança: ele simplesmente emenda a coluna no fim do CREATE TABLE original.
$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> ALTER TABLE clientes ADD COLUMN telefone TEXT;
sqlite> .schema clientes
CREATE TABLE clientes (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
email TEXT,
cidade TEXT,
estado TEXT,
nascimento TEXT,
vip INTEGER NOT NULL DEFAULT 0
, telefone TEXT);
sqlite> .quit
Com valor padrão
A Ana vai começar um programa de pontos. Todo cliente começa com 0 pontos, e o campo não pode ficar vazio:
ALTER TABLE clientes ADD COLUMN pontos INTEGER NOT NULL DEFAULT 0; SELECT id, nome, pontos FROM clientes LIMIT 3;
Com DEFAULT 0 (aula DEFAULT), os 30 clientes que já existiam ganharam 0, e os próximos também ganharão, se ninguém disser outro número. Aqui o DEFAULT é obrigatório: sem ele, o NOT NULL não teria o que pôr nas linhas antigas (veja Quando dá errado).
| A coluna nova pode ter | A coluna nova não pode ter |
|---|---|
| Qualquer tipo | PRIMARY KEY |
DEFAULT com um valor fixo (0, 'PI') | UNIQUE |
NOT NULL, se tiver um DEFAULT que não seja NULL | DEFAULT CURRENT_TIMESTAMP (ou outro valor que muda) |
REFERENCES, se o DEFAULT for NULL | NOT NULL sem DEFAULT |
Experimente: acrescente à tabela clientes a coluna pontos, inteira, que nunca fica vazia e começa em 0. Depois mostre id, nome e pontos de todos os clientes.
SELECT id, nome FROM clientes;
ALTER TABLE clientes ADD COLUMN pontos INTEGER NOT NULL DEFAULT 0; SELECT id, nome, pontos FROM clientes;
4. Apagar uma coluna: DROP COLUMN
A Ana desistiu da marcação de cliente VIP:
ALTER TABLE clientes DROP COLUMN vip; SELECT * FROM clientes LIMIT 2;
- DROP COLUMN vip
- "Jogue fora a coluna vip": a coluna some, e os valores dela em todas as linhas também. Não tem desfazer. Se houver alguma chance de precisar dos valores, faça uma cópia antes (aula Criar Tabela a partir de Consulta) ou um backup (aula Backup).
Para apagar duas colunas, são dois comandos, um depois do outro: ALTER TABLE clientes DROP COLUMN email; e ALTER TABLE clientes DROP COLUMN cidade;. O SQLite não aceita uma lista separada por vírgula.
O SQLite se recusa a apagar uma coluna quando outra parte do banco depende dela. Os casos mais comuns:
| A coluna… | A mensagem |
|---|---|
| é a chave primária | cannot drop PRIMARY KEY column: "id" |
tem UNIQUE | cannot drop UNIQUE column: "a" |
| está num índice | error in index idx_cidade after drop column: no such column: cidade |
aparece no CHECK de outra coluna | error in table t after drop column: no such column: a |
está numa FOREIGN KEY (…) escrita no fim da tabela | error in table t after drop column: unknown column "a" in foreign key definition |
| não existe | no such column: "fone" |
As mensagens do tipo error in … after drop column querem dizer "erro em tal lugar depois de apagar a coluna": o SQLite tentou, viu que o índice ou a regra ficaria quebrada, e desfez tudo. Para apagar uma coluna dessas, primeiro apague o que depende dela (o índice, por exemplo, com DROP INDEX) ou recrie a tabela (seção 6).
5. O que o SQLite não faz com ALTER TABLE
Quem vem de outro banco tenta logo mudar o tipo de uma coluna. No SQLite deste guia (a versão 3.50) não existe comando para isso:
ALTER TABLE produtos MODIFY preco NUMERIC;
No terminal, a mensagem vem com a seta apontando onde o SQLite se perdeu:
sqlite> ALTER TABLE produtos MODIFY preco NUMERIC;
Parse error: near "MODIFY": syntax error
ALTER TABLE produtos MODIFY preco NUMERIC;
^--- error here
A forma do PostgreSQL, ALTER TABLE produtos ALTER COLUMN preco TYPE NUMERIC;, também dá erro de sintaxe, desta vez near "ALTER". Também não dá para acrescentar uma regra a uma tabela pronta (ADD CONSTRAINT, aula CHECK), nem fazer várias mudanças num comando só.
A documentação atual do SQLite mostra que versões mais recentes que a deste guia (a partir da 3.53) passaram a aceitar ALTER COLUMN … SET NOT NULL e DROP NOT NULL. Mudar o tipo continua fora da lista. Confira a sua versão com SELECT sqlite_version();; aqui, ela é 3.50.4.
6. Recriar a tabela: a mudança de casa
Para as reformas "estruturais", a documentação do SQLite dá um roteiro de 12 passos. Ele parece grande, mas a ideia é a da família que muda de casa: construir a casa nova, levar a mudança, demolir a velha, pôr a placa com o nome antigo na nova. Vamos começar pelo caso simples.
O caso simples: uma tabela que ninguém referencia
A Ana criou a tabela de cupons com o desconto como TEXT, por engano. Ordenando pelo desconto, a ordem sai errada, porque texto se ordena letra por letra ('10.5' vem antes de '5', como "abacate" vem antes de "banana"):
CREATE TABLE cupons (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
desconto TEXT NOT NULL
);
INSERT INTO cupons (codigo, desconto)
VALUES ('NATAL', '15'), ('BEMVINDO', '10.5'), ('PIAUI', '5');
SELECT codigo, desconto, typeof(desconto)
FROM cupons
ORDER BY desconto;
A mudança de casa, em quatro passos:
CREATE TABLE cupons (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
desconto TEXT NOT NULL
);
INSERT INTO cupons (codigo, desconto)
VALUES ('NATAL', '15'), ('BEMVINDO', '10.5'), ('PIAUI', '5');
-- 1. a casa nova, já do jeito certo
CREATE TABLE cupons_nova (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
desconto REAL NOT NULL
);
-- 2. a mudança: copia tudo, convertendo o desconto
INSERT INTO cupons_nova (id, codigo, desconto)
SELECT id, codigo, CAST(desconto AS REAL)
FROM cupons;
-- 3. demole a velha
DROP TABLE cupons;
-- 4. a placa: a nova ganha o nome antigo
ALTER TABLE cupons_nova RENAME TO cupons;
SELECT codigo, desconto, typeof(desconto)
FROM cupons
ORDER BY desconto;
- CREATE TABLE cupons_nova
- A tabela nova, com a estrutura que você quer. Aqui você pode mudar tipos, acrescentar
CHECK,UNIQUE, tudo. - INSERT INTO … SELECT …
- Copia todas as linhas da velha para a nova numa tacada só (aula INSERT INTO SELECT). Copie o
idtambém, para os números não mudarem. - CAST(desconto AS REAL)
- "Converta o desconto para REAL": transforma o texto
'15'no número15.0. - DROP TABLE cupons
- Apaga a tabela velha (aula DROP TABLE).
- RENAME TO cupons
- A nova assume o nome antigo, e quem usava
cuponscontinua funcionando.
Agora typeof diz real, e a ordem ficou certa: 5, 10.5, 15.
O roteiro completo: uma tabela que outras referenciam
A tabela produtos é mais delicada: os 220 itens de pedido apontam para ela por chave estrangeira. Se a regra estiver ligada, o DROP TABLE produtos falha (veja a aula FOREIGN KEY). A Ana quer acrescentar duas regras: preço maior que zero e estoque nunca negativo. Este é o roteiro de 12 passos da documentação, simplificado para o que a Loja Aurora tem (ela não tem índices, gatilhos nem views na tabela produtos):
-- passo 1: desliga as chaves estrangeiras (fora da transação!)
PRAGMA foreign_keys = OFF;
-- passo 2: começa uma transação: ou vai tudo, ou nada
BEGIN;
-- passo 4: a tabela nova, com as regras
CREATE TABLE produtos_nova (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
categoria_id INTEGER NOT NULL REFERENCES categorias(id),
fornecedor_id INTEGER NOT NULL REFERENCES fornecedores(id),
preco REAL NOT NULL CHECK (preco > 0),
estoque INTEGER NOT NULL DEFAULT 0 CHECK (estoque >= 0),
descontinuado INTEGER NOT NULL DEFAULT 0
);
-- passo 5: a mudança
INSERT INTO produtos_nova
(id, nome, categoria_id, fornecedor_id, preco, estoque, descontinuado)
SELECT id, nome, categoria_id, fornecedor_id, preco, estoque, descontinuado
FROM produtos;
-- passo 6: demole a velha
DROP TABLE produtos;
-- passo 7: a placa
ALTER TABLE produtos_nova RENAME TO produtos;
-- passo 10: confere se ninguém ficou órfão (vazio = tudo certo)
PRAGMA foreign_key_check;
-- passo 11: confirma
COMMIT;
-- passo 12: liga as chaves de novo
PRAGMA foreign_keys = ON;
SELECT COUNT(*) AS produtos FROM produtos;
| Passo da documentação | O que faz | Na Loja Aurora |
|---|---|---|
1. PRAGMA foreign_keys = OFF | Senão o DROP TABLE esbarra nos itens de pedido. | Feito |
2. BEGIN | Se algo der errado no meio, nada muda (aula Transações). | Feito |
| 3. Anotar índices, gatilhos e views da tabela | Eles somem com o DROP TABLE e precisam ser recriados. Veja com SELECT type, sql FROM sqlite_schema WHERE tbl_name = 'produtos'; | Não tem nenhum |
4. CREATE TABLE da nova | A estrutura nova. | Feito |
5. INSERT INTO … SELECT | Copia os dados. | Feito: 45 produtos |
6. DROP TABLE da velha | Apaga a antiga. | Feito |
7. RENAME TO | A nova assume o nome. | Feito |
| 8 e 9. Recriar índices, gatilhos e views | Com o SQL anotado no passo 3. | Não tem nenhum |
10. PRAGMA foreign_key_check | Confere se alguma linha ficou apontando para o vazio. | Feito: vazio |
11. COMMIT | Confirma tudo. | Feito |
12. PRAGMA foreign_keys = ON | Volta a proteger o banco. | Feito |
Para ver que as regras novas estão valendo, acrescente no fim UPDATE produtos SET preco = -1 WHERE id = 1;: a resposta será CHECK constraint failed: preco > 0.
Dentro de uma transação, o PRAGMA foreign_keys não tem efeito nenhum (e não dá erro). Se você puser o PRAGMA foreign_keys = OFF depois do BEGIN, a regra continua ligada e o DROP TABLE produtos falha. A ordem do roteiro importa.
7. Qual caminho usar
| Mudança | Caminho | Cuidado |
|---|---|---|
| Nome da tabela | RENAME TO | Programas que usam o nome antigo param de funcionar. |
| Nome de uma coluna | RENAME COLUMN | Idem, para consultas que citam a coluna. |
| Coluna nova | ADD COLUMN | Com NOT NULL, precisa de DEFAULT. Nada de UNIQUE nem PRIMARY KEY. |
| Tirar uma coluna | DROP COLUMN | Não tem desfazer. Recusa se algo depende dela. |
| Tipo, regras, chave, ordem das colunas | Recriar a tabela (seção 6) | Use transação; com chaves estrangeiras, o roteiro completo. |
Quando dá errado
ALTER TABLE clientes ADD COLUMN pontos INTEGER NOT NULL;
A mensagem: Cannot add a NOT NULL column with default value NULL, "não é possível acrescentar uma coluna NOT NULL com valor padrão NULL". Os 30 clientes antigos ficariam sem valor, e a regra não deixa.
Como consertar: dê um valor padrão: ADD COLUMN pontos INTEGER NOT NULL DEFAULT 0.
ALTER TABLE clientes ADD COLUMN cpf TEXT UNIQUE;
A mensagem: Cannot add a UNIQUE column, "não é possível acrescentar uma coluna UNIQUE". Com PRIMARY KEY, a mensagem é Cannot add a PRIMARY KEY column.
Como consertar: acrescente a coluna sem UNIQUE e crie um índice único depois: CREATE UNIQUE INDEX idx_cpf ON clientes(cpf); (aula Índices). Para chave primária, só recriando a tabela.
ALTER TABLE clientes ADD COLUMN criado_em TEXT DEFAULT CURRENT_TIMESTAMP;
A mensagem: Cannot add a column with non-constant default, "não é possível acrescentar uma coluna com padrão não constante". O CURRENT_TIMESTAMP muda a cada segundo, e o SQLite não sabe que hora pôr nas linhas antigas. No CREATE TABLE ele é aceito; no ADD COLUMN, não.
Como consertar: acrescente sem DEFAULT e preencha as linhas antigas com um UPDATE, ou recrie a tabela.
ALTER TABLE clientes ADD COLUMN cidade TEXT;
A mensagem: duplicate column name: cidade, "nome de coluna repetido". Acontece muito ao rodar o mesmo roteiro de mudança duas vezes. O mesmo vale para renomear para um nome que já existe: RENAME COLUMN email TO nome dá error in table clientes after rename: duplicate column name: nome.
ALTER TABLE clientes RENAME COLUMN nascimento TO data_nascimento; SELECT nome, nascimento FROM clientes;
A mensagem: no such column: nascimento. O ALTER TABLE funcionou; quem errou foi a consulta, que ainda usa o nome antigo.
Já visto na seção 5: near "MODIFY": syntax error ou near "ALTER": syntax error. No SQLite, mudar o tipo é recriar a tabela.
MySQL, PostgreSQL e SQL Server mudam muito mais coisa com ALTER TABLE, inclusive o tipo de uma coluna e as regras, sem recriar a tabela. A escrita muda de um para outro (trechos ilustrativos, não rodam no SQLite):
-- MySQL / MariaDB ALTER TABLE produtos MODIFY COLUMN preco DECIMAL(10,2) NOT NULL; -- PostgreSQL ALTER TABLE produtos ALTER COLUMN preco TYPE numeric(10,2); -- SQL Server ALTER TABLE produtos ALTER COLUMN preco DECIMAL(10,2) NOT NULL; -- nos três: acrescentar uma regra numa tabela pronta ALTER TABLE produtos ADD CONSTRAINT preco_positivo CHECK (preco > 0);
Renomear também muda: no SQL Server, por exemplo, renomeia-se com o procedimento sp_rename, e não com ALTER TABLE … RENAME. Na dúvida, consulte a documentação do banco que você usa; o grupo "Os Dialetos" do Nível 3 compara os três.
Referência rápida
| Escrita | O que faz |
|---|---|
ALTER TABLE velha RENAME TO nova; | Renomeia a tabela (as chaves estrangeiras que apontam para ela são atualizadas). |
ALTER TABLE t RENAME COLUMN a TO b; | Renomeia uma coluna. |
ALTER TABLE t ADD COLUMN c TIPO …; | Acrescenta uma coluna no fim; as linhas antigas ficam com o DEFAULT (ou NULL). |
ALTER TABLE t DROP COLUMN c; | Apaga uma coluna e seus valores. |
Recriar: CREATE TABLE t_nova → INSERT INTO t_nova SELECT … FROM t → DROP TABLE t → ALTER TABLE t_nova RENAME TO t | Qualquer outra mudança. Com chaves estrangeiras: PRAGMA foreign_keys = OFF, BEGIN, …, PRAGMA foreign_key_check, COMMIT, PRAGMA foreign_keys = ON. |
| Mensagem | Quer dizer |
|---|---|
Cannot add a NOT NULL column with default value NULL | Coluna nova NOT NULL precisa de DEFAULT. |
Cannot add a UNIQUE column / Cannot add a PRIMARY KEY column | Essas regras não entram por ADD COLUMN. |
Cannot add a column with non-constant default | DEFAULT CURRENT_TIMESTAMP não vale no ADD COLUMN. |
duplicate column name: cidade | Já existe uma coluna com esse nome. |
cannot drop PRIMARY KEY column: "id" | A chave primária não pode ser apagada. |
error in index … after drop column: … | Um índice (ou regra) depende da coluna. |
near "MODIFY": syntax error | O SQLite não muda o tipo de coluna: recrie a tabela. |
Exercícios
1. Renomear a tabela
Complete para que a tabela categorias passe a se chamar secoes:
2. Coluna nova
Complete para acrescentar a coluna site aos fornecedores:
3. Depois de ALTER TABLE clientes ADD COLUMN telefone TEXT;, o que os 30 clientes que já existiam têm na coluna telefone?
4. Qual destes comandos o SQLite 3.50 aceita?
5. No roteiro de recriar a tabela produtos, por que o PRAGMA foreign_keys = OFF vem antes do BEGIN?
6. A Ana renomeou fornecedores para parceiros. O que aconteceu com a chave estrangeira produtos.fornecedor_id?
Missão final: o desconto que era texto
Missão: o desconto dos cupons foi criado como TEXT, e a ordem sai errada. Recrie a tabela com o desconto REAL, sem perder os três cupons: crie cupons_nova, copie as linhas convertendo com CAST(desconto AS REAL), apague a velha e dê à nova o nome cupons. O SELECT do fim deve mostrar PIAUI|5.0 em primeiro lugar.
CREATE TABLE cupons (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
desconto TEXT NOT NULL
);
INSERT INTO cupons (codigo, desconto)
VALUES ('NATAL', '15'), ('BEMVINDO', '10.5'), ('PIAUI', '5');
-- escreva aqui a mudança de casa
SELECT codigo, desconto, typeof(desconto)
FROM cupons
ORDER BY desconto;
CREATE TABLE cupons (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
desconto TEXT NOT NULL
);
INSERT INTO cupons (codigo, desconto)
VALUES ('NATAL', '15'), ('BEMVINDO', '10.5'), ('PIAUI', '5');
CREATE TABLE cupons_nova (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
desconto REAL NOT NULL
);
INSERT INTO cupons_nova (id, codigo, desconto)
SELECT id, codigo, CAST(desconto AS REAL)
FROM cupons;
DROP TABLE cupons;
ALTER TABLE cupons_nova RENAME TO cupons;
SELECT codigo, desconto, typeof(desconto)
FROM cupons
ORDER BY desconto;
Resumo
ALTER TABLEmuda a estrutura de uma tabela que já tem dados, sem apagar as linhas.- No SQLite ele faz quatro coisas:
RENAME TO(tabela),RENAME COLUMN … TO,ADD COLUMNeDROP COLUMN. - A coluna nova vai para o fim e as linhas antigas ganham o
DEFAULTou NULL.NOT NULLexigeDEFAULT;UNIQUE,PRIMARY KEYeDEFAULT CURRENT_TIMESTAMPnão entram porADD COLUMN. DROP COLUMNnão tem desfazer e é recusado quando a coluna é chave, única, está num índice ou numa regra.- Mudar o tipo ou acrescentar regras: recrie a tabela (nova → copiar → apagar a velha → renomear). Com chaves estrangeiras, desligue-as antes do
BEGIN, confira comPRAGMA foreign_key_checke ligue de novo depois doCOMMIT. - MySQL, PostgreSQL e SQL Server mudam tipos e regras direto com
ALTER TABLE, cada um com a sua escrita.