SQL Criar Tabela a partir de Consulta

Até agora, toda tabela nova começou vazia: um CREATE TABLE com as colunas e, depois, os INSERT. Mas às vezes a tabela que você quer já existe como resultado de uma consulta: "os clientes do Piauí", "os pedidos de 2024", "quanto cada produto vendeu". Com CREATE TABLE … AS SELECT ("crie a tabela como o resultado desta consulta"), o banco cria a tabela e a enche numa tacada só. Nesta aula você vai usar isso para fazer cópias de segurança, recortes e relatórios da Loja Aurora, e vai ver o que a cópia não leva junto.

Você vai aprender
  • Como criar uma tabela com o resultado de um SELECT.
  • Os três usos mais comuns: cópia de segurança, recorte e tabela de relatório.
  • O que é copiado (nomes, tipos, dados) e o que se perde (chave primária, NOT NULL, DEFAULT, chaves estrangeiras).
  • Por que dar nome às colunas calculadas com AS.
  • Que a tabela nova é uma fotografia: não acompanha as mudanças da original.
  • Os erros reais e o SELECT INTO do SQL Server.

Você vai precisar do que viu em CREATE TABLE e em SELECT. Os exemplos rodam de verdade no seu navegador, e cada Rodar começa do banco limpo.


A ideia: tirar uma xerox

A Ana tem o caderno de clientes da loja. O gerente de Teresina pediu "a lista dos clientes do Piauí". Ela pode copiar à mão, cliente por cliente, num caderno novo. Ou pode marcar as páginas certas e tirar uma xerox: sai um caderno novo, já com tudo escrito.

Duas coisas sobre a xerox valem também para o banco:

  • Ela copia o que está escrito, mas não as regras do caderno original. Se o original tinha "não pode deixar o nome em branco" impresso no topo, a xerox só tem o texto.
  • Ela é uma fotografia: se amanhã um cliente mudar de endereço no caderno original, a xerox continua com o endereço antigo.

1. A primeira tabela feita de consulta

CREATE TABLE clientes_pi AS
SELECT id, nome, cidade
FROM clientes
WHERE estado = 'PI';

SELECT * FROM clientes_pi;
CREATE TABLE clientes_pi
Cria uma tabela nova, chamada clientes_pi. Repare que não há lista de colunas entre parênteses.
AS
"Como": a tabela vai ser como o resultado da consulta que vem a seguir. Aqui o AS é obrigatório.
SELECT id, nome, cidade FROM clientes WHERE estado = 'PI'
Uma consulta comum. As colunas dela viram as colunas da tabela nova; as linhas dela viram as linhas.

A tabela nova tem 3 colunas e 14 linhas, e é uma tabela de verdade: dá para fazer SELECT, INSERT, UPDATE e DELETE nela, como em qualquer outra. Se você apagar um cliente de clientes_pi, a tabela clientes não muda.

No terminal, o .schema mostra como o SQLite montou a tabela:

$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> CREATE TABLE clientes_pi AS SELECT id, nome, cidade FROM clientes WHERE estado = 'PI';
sqlite> .schema clientes_pi
CREATE TABLE clientes_pi(id INT,nome TEXT,cidade TEXT);
sqlite> SELECT COUNT(*) FROM clientes_pi;
14
sqlite> .quit

Guarde esse id INT: ele vai ser importante na seção 3.

Experimente: o código só mostra os clientes do Ceará. Transforme a consulta numa tabela nova chamada clientes_ce, com o id, o nome e a cidade, e depois mostre a tabela nova.

SELECT id, nome, cidade
FROM clientes
WHERE estado = 'CE';
CREATE TABLE clientes_ce AS
SELECT id, nome, cidade
FROM clientes
WHERE estado = 'CE';

SELECT * FROM clientes_ce;

2. Três usos do dia a dia

Cópia de segurança antes de mexer

Antes de um UPDATE grande nos preços, a Ana guarda uma cópia da tabela inteira. O SELECT * leva todas as colunas e, sem WHERE, todas as linhas:

CREATE TABLE produtos_copia AS
SELECT * FROM produtos;

SELECT COUNT(*) AS produtos_na_copia FROM produtos_copia;

Se o reajuste der errado, os preços antigos estão na cópia, e a aula INSERT INTO SELECT mostra como trazê-los de volta. (Para o banco inteiro, a ferramenta certa é o backup: aula Backup.)

Um recorte

Os pedidos de 2024 vão para uma tabela própria, para o contador:

CREATE TABLE pedidos_2024 AS
SELECT *
FROM pedidos
WHERE data < '2025-01-01';

SELECT COUNT(*) AS pedidos,
       MIN(data) AS primeiro,
       MAX(data) AS ultimo
FROM pedidos_2024;

Uma tabela de relatório

O relatório "quanto cada produto vendeu" junta duas tabelas e soma (aulas INNER JOIN e GROUP BY). Calcular isso toda vez pode ser lento num banco grande. Guardado numa tabela, o relatório fica pronto para consultar:

CREATE TABLE vendas_produto AS
SELECT p.id                    AS produto_id,
       p.nome                  AS nome,
       SUM(i.quantidade)       AS unidades,
       ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS faturamento
FROM itens_pedido AS i
JOIN produtos AS p ON p.id = i.produto_id
GROUP BY p.id, p.nome;

SELECT *
FROM vendas_produto
ORDER BY unidades DESC, nome
LIMIT 5;

A tabela tem 42 linhas, uma por produto que já vendeu (os produtos 43, 44 e 45 nunca venderam, e o JOIN os deixa de fora).


3. O que a cópia leva, e o que perde

Peça ao SQLite o CREATE TABLE que ele montou para a cópia dos produtos, e compare com o original:

CREATE TABLE produtos_copia AS
SELECT * FROM produtos;

SELECT name, sql
FROM sqlite_schema
WHERE name IN ('produtos', 'produtos_copia');
No original (produtos)Na cópia (produtos_copia)
id INTEGER PRIMARY KEYid INT: não é mais chave, não numera sozinho, aceita repetido
nome TEXT NOT NULLnome TEXT: aceita vazio
categoria_id INTEGER NOT NULL REFERENCES categorias(id)categoria_id INT: sem chave estrangeira
estoque INTEGER NOT NULL DEFAULT 0estoque INT: sem valor padrão

Vão junto os nomes, os tipos (de um jeito resumido: INTEGER vira INT) e os dados. Ficam para trás todas as regras: PRIMARY KEY, NOT NULL, UNIQUE, DEFAULT, CHECK e as chaves estrangeiras. E o INT no lugar de INTEGER tem uma consequência que você viu na aula AUTOINCREMENT: a coluna deixa de numerar sozinha.

CREATE TABLE clientes_copia AS
SELECT * FROM clientes;

INSERT INTO clientes_copia (nome) VALUES ('Rita Moura');
INSERT INTO clientes_copia (id, nome) VALUES (1, NULL);

SELECT id, nome
FROM clientes_copia
WHERE id = 1 OR nome = 'Rita Moura' OR nome IS NULL;

A Rita entrou sem id (NULL), e entrou um segundo cliente 1, sem nome. Na tabela clientes original, nenhum dos dois INSERT passaria. Para uma cópia de segurança que só vai ser lida, isso não importa. Para uma tabela que vai ser usada e receber dados novos, crie a tabela com CREATE TABLE completo e encha com INSERT INTO SELECT.

Só a estrutura, sem os dados

Um truque comum: um WHERE que nunca é verdadeiro. A tabela nasce com as colunas e nenhuma linha:

CREATE TABLE produtos_vazia AS
SELECT * FROM produtos
WHERE 0;

SELECT COUNT(*) AS linhas FROM produtos_vazia;

O WHERE 0 quer dizer "onde falso": nenhuma linha passa. Mas lembre-se: as regras continuam ficando para trás.


4. Dê nome às colunas calculadas

No relatório da seção 2, todas as colunas ganharam nome com AS. Veja o que acontece sem os apelidos:

CREATE TABLE resumo AS
SELECT cliente_id, COUNT(*), SUM(frete)
FROM pedidos
GROUP BY cliente_id;

SELECT sql FROM sqlite_schema WHERE name = 'resumo';

As colunas se chamam "COUNT(*)" e "SUM(frete)", com parênteses e asterisco no nome. Para usar, só entre aspas duplas: SELECT "COUNT(*)" FROM resumo;. Ninguém quer isso. E repare que essas colunas nem têm tipo. Com apelidos (aula Apelidos (AS)), fica limpo:

CREATE TABLE resumo AS
SELECT cliente_id,
       COUNT(*)   AS qtd_pedidos,
       SUM(frete) AS total_frete
FROM pedidos
GROUP BY cliente_id;

SELECT * FROM resumo LIMIT 3;

O mesmo vale quando duas colunas têm o mesmo nome, como num JOIN: SELECT c.id, p.id … cria as colunas id e "id:1". Dê um apelido a cada uma: c.id AS cliente_id, p.id AS pedido_id.


5. É uma fotografia, não uma janela

A tabela nova é independente. Se o original mudar depois, ela não fica sabendo:

CREATE TABLE produtos_copia AS
SELECT * FROM produtos;

UPDATE produtos SET preco = 13.90 WHERE id = 1;

SELECT p.nome,
       p.preco AS preco_hoje,
       c.preco AS preco_na_copia
FROM produtos AS p
JOIN produtos_copia AS c ON c.id = p.id
WHERE p.id = 1;

O preço do produto 1 mudou na tabela produtos, e a cópia continua com o preço de quando foi tirada. Isso é ótimo para cópia de segurança e para "como estava no fim do mês". Mas, se você quer uma consulta com nome que mostre sempre os dados de agora, a ferramenta certa é uma view (aula Views): CREATE VIEW clientes_pi AS SELECT …. A view é uma janela; a tabela criada por consulta é uma fotografia.

CREATE TABLE … AS SELECTCREATE VIEW … AS SELECT
GuardaOs dados, copiadosSó a consulta
Quando o original mudaFica como estavaMostra o novo
Ocupa espaçoSim, como qualquer tabelaQuase nada
Aceita INSERT/UPDATESimNão (no SQLite)
Bom paraCópia de segurança, foto do fim do mês, relatório pesadoConsulta repetida que tem de estar sempre em dia

6. Formas de fazer

FormaEscritaQuando usar
Cópia inteiraCREATE TABLE t2 AS SELECT * FROM t;Cópia de segurança rápida.
RecorteCREATE TABLE t2 AS SELECT colunas FROM t WHERE …;Entregar uma parte dos dados.
Só a estruturaCREATE TABLE t2 AS SELECT * FROM t WHERE 0;Tabela de rascunho com as mesmas colunas (sem regras).
Só se não existirCREATE TABLE IF NOT EXISTS t2 AS SELECT …;Roteiros que rodam várias vezes.
TemporáriaCREATE TEMP TABLE t2 AS SELECT …;Contas intermediárias: a tabela some quando o banco é fechado.
Com as regrasCREATE TABLE t2 (… completo …); + INSERT INTO t2 SELECT … FROM t;Tabela que vai ser usada de verdade (aula INSERT INTO SELECT).
O IF NOT EXISTS não confere o conteúdo

CREATE TABLE IF NOT EXISTS clientes AS SELECT 1; responde "ok" e não faz nada, porque a tabela clientes já existe. Ele não compara colunas nem dados: se já existe uma tabela com esse nome, mesmo que seja outra coisa, o comando passa calado.


Quando dá errado

A tabela já existe
CREATE TABLE produtos_copia AS SELECT * FROM produtos;

CREATE TABLE produtos_copia AS SELECT * FROM produtos;

A mensagem: table produtos_copia already exists, "a tabela produtos_copia já existe". Acontece ao rodar o mesmo roteiro duas vezes.

Como consertar: escolha outro nome (ex.: produtos_copia_2026_10), ou apague a cópia velha antes com DROP TABLE IF EXISTS produtos_copia; (aula DROP TABLE).

Esqueceu o AS (a forma do MySQL)
CREATE TABLE produtos_copia
SELECT * FROM produtos;

A mensagem: near "SELECT": syntax error. O MySQL aceita sem o AS; o SQLite não.

Como consertar: CREATE TABLE produtos_copia AS SELECT * FROM produtos;.

SELECT INTO (a forma do SQL Server)
SELECT *
INTO produtos_copia
FROM produtos;

A mensagem: near "INTO": syntax error. O SQLite não tem SELECT … INTO.

Como consertar: mesma coisa: CREATE TABLE produtos_copia AS SELECT * FROM produtos;.

A cópia aceitou o que não devia

Não há mensagem: esse é o problema. A cópia não tem chave primária nem NOT NULL (seção 3), então aceita ids repetidos, ids vazios e nomes vazios.

Como consertar: se a tabela vai receber dados novos, crie-a com CREATE TABLE completo e copie com INSERT INTO … SELECT.

Coluna com nome estranho

Também sem mensagem: a tabela foi criada, mas uma coluna se chama "preco * 0.9" ou "COUNT(*)" (seção 4).

Como consertar: apague a tabela, dê um apelido a cada coluna calculada (preco * 0.9 AS preco_promocional) e crie de novo. Ou renomeie a coluna: ALTER TABLE x RENAME COLUMN "preco * 0.9" TO preco_promocional; (aula ALTER TABLE).


Nos outros bancos

A ideia existe nos quatro bancos, mas a escrita muda (trechos ilustrativos, não rodam no SQLite):

-- PostgreSQL e MySQL: igual ao SQLite
CREATE TABLE clientes_pi AS
SELECT id, nome, cidade FROM clientes WHERE estado = 'PI';

-- MySQL também aceita sem o AS
CREATE TABLE clientes_pi
SELECT id, nome, cidade FROM clientes WHERE estado = 'PI';

-- SQL Server: SELECT ... INTO
SELECT id, nome, cidade
INTO clientes_pi
FROM clientes
WHERE estado = 'PI';

Em todos, como no SQLite, a tabela nova leva os dados e não leva a chave primária nem as chaves estrangeiras. Para copiar só a estrutura com as regras, MySQL e PostgreSQL têm uma forma própria com LIKE: CREATE TABLE produtos_nova LIKE produtos; no MySQL e CREATE TABLE produtos_nova (LIKE produtos INCLUDING ALL); no PostgreSQL. O SQLite não tem; lá, copie o CREATE TABLE que o .schema mostra e troque o nome.


Referência rápida

EscritaO que faz
CREATE TABLE nova AS SELECT …;Cria a tabela com as colunas e as linhas do SELECT.
CREATE TABLE nova AS SELECT * FROM t;Cópia completa dos dados (sem as regras).
… WHERE 0;Só as colunas, nenhuma linha.
CREATE TABLE IF NOT EXISTS nova AS SELECT …;Não faz nada se já existir uma tabela com esse nome.
CREATE TEMP TABLE nova AS SELECT …;Tabela temporária, some ao fechar o banco.
expressão AS nome no SELECTDá o nome da coluna na tabela nova.
CopiadoNomes, tipos (resumidos: INTEGER → INT) e dados.
Não copiadoPRIMARY KEY, NOT NULL, UNIQUE, DEFAULT, CHECK, REFERENCES, índices.
MensagemQuer dizer
table produtos_copia already existsJá existe uma tabela com esse nome.
near "SELECT": syntax errorFaltou o AS depois do nome da tabela.
near "INTO": syntax errorSELECT … INTO é do SQL Server; no SQLite, use CREATE TABLE … AS.

Exercícios

1. A palavra que liga

Complete para criar a tabela fornecedores_copia com todos os fornecedores:

CREATE TABLE fornecedores_copia SELECT * FROM fornecedores;

2. Só a estrutura

Complete para criar pedidos_vazia com as colunas de pedidos e nenhuma linha:

CREATE TABLE pedidos_vazia AS SELECT * FROM pedidos WHERE ;

3. A Ana criou clientes_copia com CREATE TABLE clientes_copia AS SELECT * FROM clientes;. O que a cópia não tem?

4. Depois de criar produtos_copia, a Ana mudou o preço de um produto na tabela produtos. O que acontece na cópia?

5. Qual será o nome da segunda coluna em CREATE TABLE r AS SELECT estado, COUNT(*) FROM clientes GROUP BY estado;?

6. Qual é a forma do SQLite de fazer o que o SQL Server faz com SELECT * INTO copia FROM clientes;?

Missão final: a tabela dos pedidos pendentes

Missão: a equipe de entregas quer uma tabela própria só com os pedidos que ainda não foram entregues (entregue_em vazio). Crie a tabela pedidos_pendentes a partir da consulta, com três colunas chamadas exatamente pedido_id, cliente (o nome do cliente) e data. Depois mostre a tabela nova.

SELECT p.id, c.nome, p.data
FROM pedidos AS p
JOIN clientes AS c ON c.id = p.cliente_id
WHERE p.entregue_em IS NULL;
CREATE TABLE pedidos_pendentes AS
SELECT p.id   AS pedido_id,
       c.nome AS cliente,
       p.data AS data
FROM pedidos AS p
JOIN clientes AS c ON c.id = p.cliente_id
WHERE p.entregue_em IS NULL;

SELECT * FROM pedidos_pendentes;

Resumo

  • CREATE TABLE nova AS SELECT …; cria uma tabela com as colunas e as linhas do resultado de uma consulta.
  • Serve para cópias de segurança (SELECT *), recortes (WHERE) e tabelas de relatório (GROUP BY).
  • A tabela nova leva nomes, tipos e dados, mas nenhuma regra: sem chave primária, sem NOT NULL, sem DEFAULT, sem chave estrangeira. O id vira INT e para de numerar sozinho.
  • Dê nome com AS a toda coluna calculada, senão ela se chama "COUNT(*)".
  • É uma fotografia: não acompanha as mudanças do original. Para uma janela sempre atualizada, use uma view.
  • O MySQL aceita sem AS; o SQL Server usa SELECT … INTO. No SQLite, as duas formas dão erro de sintaxe.