SQL Importar e Exportar CSV
A contadora da Loja Aurora pediu a lista de produtos com preço e estoque "numa planilha". No mesmo dia, um fornecedor mandou três produtos novos, também numa planilha. Nos dois casos, a ponte entre o banco e a planilha é um arquivo CSV. Nesta aula, a Ana vai exportar (tirar dados do banco para um arquivo) e importar (colocar os dados de um arquivo no banco) com o programa sqlite3, e vai ver o que acontece quando o arquivo vem com cabeçalho a mais, coluna a menos ou campo vazio.
- O que é um arquivo CSV e por que toda planilha entende esse formato.
- A exportar uma consulta para CSV com
.headers on,.mode csve.once. - A diferença entre
.once(só a próxima consulta) e.output(tudo, até você mandar parar). - A exportar direto da linha de comando, sem abrir o
sqlite3. - A importar um CSV para uma tabela nova e para uma tabela que já existe (
.import --csve--skip 1). - Por que os números importados podem virar texto, e por que um campo vazio não vira
NULL. - As mensagens reais do
sqlite3quando a importação dá errado.
Esta é uma aula de terminal. Importar e exportar mexe com arquivos do computador, e quem faz isso é o programa sqlite3, com os comandos de ponto que você viu em Comandos de Ponto. O editor do guia roda só SQL, num banco que vive na memória do navegador, então ele não lê nem grava arquivos. Todas as sessões abaixo saíram do terminal da Ana, com o banco ~/loja.db. O editor aparece onde o assunto é SQL: a consulta que você exporta e o conserto dos dados que você importa.
A ideia: uma planilha escrita em texto
Pense numa planilha simples, com quatro colunas. Agora imagine que você precisa ditá-la por telefone, linha por linha. Você diria algo como "um, vírgula, Caderno Pautado, vírgula, doze e trinta e cinco…". Um arquivo CSV é exatamente isso, escrito num arquivo de texto:
id,nome,preco,estoque 1,"Caderno Pautado 96 folhas",12.35,120 2,"Caneta Esferográfica Azul",2.79,500 3,"Lápis Grafite HB",1.15,430
| Na planilha | No CSV |
|---|---|
| Uma linha | Uma linha do arquivo |
| A troca de coluna | Uma vírgula |
| A primeira linha, com os nomes das colunas | O cabeçalho (opcional): id,nome,preco,estoque |
| Um texto com espaço, vírgula ou acento | Pode vir entre aspas duplas: "Lápis Grafite HB" |
CSV vem do inglês comma-separated values, "valores separados por vírgula". Como é só texto, qualquer programa entende: o LibreOffice Calc, o Excel, o Google Planilhas, o próprio cat do terminal e todos os bancos de dados. É o formato mais comum para levar dados de um lugar para outro.
1. Exportar: o primeiro CSV
A Ana abre o banco e, antes da consulta, faz três ajustes no sqlite3:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> .headers on sqlite> .mode csv sqlite> .once produtos.csv sqlite> SELECT id, nome, preco, estoque FROM produtos; sqlite> .quit
- .headers on
- Liga o cabeçalho: a primeira linha da saída terá os nomes das colunas. Sem ele, o arquivo começa direto nos dados. (
.header on, no singular, também funciona.) - .mode csv
- Muda o formato da saída para CSV: vírgula entre as colunas e aspas onde for preciso. O padrão do
sqlite3é a barra|. - .once produtos.csv
- Manda a saída da próxima consulta para o arquivo
produtos.csv, em vez da tela. Once quer dizer "uma vez". Se o arquivo já existir, ele é substituído. - SELECT …
- A consulta. Qualquer
SELECTserve: comWHERE,ORDER BY, junção… O que ela devolveria na tela vai para o arquivo. - .quit
- Sai do
sqlite3e volta para o terminal.
Repare que o SELECT não mostrou nada na tela: a saída foi toda para o arquivo. De volta ao terminal, a Ana confere:
$ ls -l produtos.csv -rw-rw-r-- 1 ana ana 1636 out 18 11:17 produtos.csv $ head -5 produtos.csv id,nome,preco,estoque 1,"Caderno Pautado 96 folhas",12.35,120 2,"Caneta Esferográfica Azul",2.79,500 3,"Lápis Grafite HB",1.15,430 4,"Marca-texto Amarelo",4.9,210 $ wc -l produtos.csv 46 produtos.csv « 1 linha de cabeçalho + 45 produtos
O head -5 mostra as cinco primeiras linhas, e o wc -l conta as linhas do arquivo: 46, o cabeçalho mais os 45 produtos da loja. Está pronto para mandar para a contadora. Para abrir no LibreOffice Calc, basta dar dois cliques no arquivo; o Calc mostra uma janela "Importar texto" e pergunta qual é o separador (marque "Vírgula").
O sqlite3 põe aspas duplas num texto quando ele tem algo que poderia confundir quem lê o arquivo: espaço, vírgula, aspas ou letra acentuada. Por isso "Eletrônicos" vem entre aspas e Papelaria não. Números nunca levam aspas. Para uma planilha, tanto faz: ela tira as aspas ao abrir.
2. Com e sem cabeçalho
O cabeçalho é desligado por padrão. Veja a diferença, numa sessão nova (cada vez que você abre o sqlite3, os ajustes voltam ao padrão):
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> .mode csv sqlite> .once sem-cabecalho.csv sqlite> SELECT id, nome FROM categorias; sqlite> .quit $ cat sem-cabecalho.csv 1,Papelaria 2,"Eletrônicos" 3,"Casa e Cozinha" 4,Livros 5,Brinquedos 6,"Esporte e Lazer" 7,Alimentos 8,Jardim
Sem o .headers on, quem abrir o arquivo não sabe o que é a primeira coluna. Para mandar a alguém, ligue sempre o cabeçalho. Sem cabeçalho só vale a pena quando o arquivo vai ser lido por outro programa que já sabe a ordem das colunas.
3. .once × .output
O .once vale para uma consulta. O .output (saída) manda tudo para o arquivo, consulta após consulta, até você escrever .output sozinho, que devolve a saída para a tela:
sqlite> .headers on sqlite> .output relatorio.csv sqlite> SELECT nome, cidade FROM clientes WHERE estado = 'PI' LIMIT 3; sqlite> SELECT nome, preco FROM produtos WHERE preco > 200; sqlite> .output sqlite> SELECT count(*) AS total FROM clientes; total 30
As duas primeiras consultas foram para o arquivo; depois do .output sozinho, a terceira voltou a aparecer na tela. O arquivo ficou assim:
$ cat relatorio.csv nome,cidade "José Ribamar Silva",Teresina "Maria Clara Mendes",Teresina "Conceição Prado",Teresina nome,preco "Jogo de Panelas 5 peças",249.9
Repare que as duas consultas ficaram uma embaixo da outra, cada uma com o seu cabeçalho. Isso é útil para um relatório em texto, mas confunde uma planilha, que espera uma tabela por arquivo. Regra prática: um CSV por consulta, com .once.
.output ligado?
Enquanto o .output arquivo estiver valendo, nada aparece na tela, e parece que o sqlite3 "parou de responder". Não parou: está escrevendo tudo no arquivo. Digite .output sozinho e a tela volta.
4. Vírgulas, aspas e NULL dentro do CSV
Se a vírgula separa as colunas, o que acontece com um texto que tem vírgula? E com um NULL? Com o .mode csv ligado e a saída na tela, a Ana testa três casos difíceis:
sqlite> SELECT id, nome, email FROM clientes WHERE email IS NULL; id,nome,email 7,"Luís Otávio Brito", 15,"Roberto Falcão", 23,"Henrique Sales", sqlite> SELECT nome, cidade || ', ' || estado AS local FROM clientes LIMIT 3; nome,local "José Ribamar Silva","Teresina, PI" "Maria Clara Mendes","Teresina, PI" "Ana Beatriz Sousa","São Paulo, SP" sqlite> SELECT id, nome FROM fornecedores WHERE id = 2; id,nome 2,"Loja D'Água"
- 7,"Luís Otávio Brito",
- O email é
NULL(veja O Valor NULL). No CSV,NULLvira um campo vazio: nada depois da última vírgula. O CSV não tem como escrever "nulo". - "Teresina, PI"
- O texto tem vírgula, então vai entre aspas. Quem lê o arquivo sabe que a vírgula de dentro das aspas faz parte do texto e não separa colunas.
- "Loja D'Água"
- O apóstrofo não atrapalha o CSV, que usa aspas duplas. (Se o texto tivesse aspas duplas, elas sairiam dobradas:
"".)
Ponto e vírgula no lugar da vírgula
Em português, a vírgula é a separadora de decimais (12,35), e algumas planilhas configuradas para o Brasil esperam o ponto e vírgula entre as colunas. O .separator troca o separador:
sqlite> .separator ; sqlite> SELECT id, nome, preco FROM produtos LIMIT 3; id;nome;preco 1;"Caderno Pautado 96 folhas";12.35 2;"Caneta Esferográfica Azul";2.79 3;"Lápis Grafite HB";1.15
Os preços continuam com ponto (12.35): o banco sempre escreve números assim. Na janela "Importar texto" do LibreOffice Calc, escolha o separador certo e, no campo de idioma, "Inglês (EUA)": assim ele entende o ponto como separador decimal.
5. Exportar sem abrir o sqlite3
Dá para fazer tudo numa linha só do terminal, com opções no lugar dos comandos de ponto e o > do shell para gravar o arquivo. É o jeito de automatizar um relatório (um script que gera o CSV todo dia, por exemplo):
$ sqlite3 -header -csv loja.db "SELECT nome, email FROM clientes WHERE vip = 1;" > vips.csv $ cat vips.csv nome,email "José Ribamar Silva",jose.ribamar@exemplo.com.br "Ana Beatriz Sousa",ana.beatriz@exemplo.com.br "Fernanda Carvalho",fernanda.carvalho@exemplo.com.br "Larissa Macêdo",larissa.macedo@exemplo.com.br "Mirela Pontes",mirela.pontes@exemplo.com.br
- -header
- O mesmo que
.headers on. - -csv
- O mesmo que
.mode csv. - loja.db
- O banco.
- "SELECT …;"
- A consulta, entre aspas duplas do shell (por isso o texto dentro do SQL usa aspas simples, como sempre).
- > vips.csv
- O shell, não o
sqlite3, pega o que sairia na tela e grava no arquivo (substituindo o que houver nele).
Em toda exportação, a parte que dá trabalho é a consulta. E essa você pode treinar no editor: o que ela mostrar na tabela é o que iria para o CSV.
Experimente: o setor de compras quer um CSV com os produtos que estão acabando: só nome e estoque, dos produtos não descontinuados (descontinuado = 0) com estoque abaixo de 20, do menor para o maior estoque. Monte a consulta que a Ana vai exportar.
SELECT nome, estoque FROM produtos;
SELECT nome, estoque FROM produtos WHERE estoque < 20 AND descontinuado = 0 ORDER BY estoque;
No terminal, bastaria colocar .headers on, .mode csv e .once reposicao.csv antes dessa consulta.
6. Importar para uma tabela nova
Agora o caminho inverso. O fornecedor mandou o arquivo novos_produtos.csv, que a Ana salvou na pasta Downloads:
$ cd Downloads $ cat novos_produtos.csv nome,preco,estoque Agenda 2027,34.9,60 Calculadora Científica,79.5,18 Régua 30 cm,3.25,250
Antes de misturar esses dados com os produtos da loja, ela quer olhar com calma. Então importa o arquivo para uma tabela nova, separada, chamada novos:
$ sqlite3 ~/loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> .import --csv novos_produtos.csv novos sqlite> .tables categorias fornecedores itens_pedido pedidos clientes funcionarios novos produtos sqlite> .schema novos CREATE TABLE IF NOT EXISTS "novos"( "nome" TEXT, "preco" TEXT, "estoque" TEXT);
- .import
- O comando de ponto que lê um arquivo e insere cada linha como uma linha da tabela.
- --csv
- Diz que o arquivo é CSV (vírgulas e aspas). Sem isso, o
.importusa o modo atual, que por padrão separa por|; veja em Quando dá errado o que acontece. - novos_produtos.csv
- O arquivo, procurado na pasta em que você abriu o
sqlite3(aqui,~/Downloads). O banco pode estar em outra pasta:~/loja.db. - novos
- A tabela de destino. Como ela não existia, o
sqlite3a criou, usando a primeira linha do arquivo como nomes das colunas.
O .import não mostra nada quando dá certo. O .tables mostra a tabela nova, e o .schema mostra como ela foi criada. Repare num detalhe importante: todas as colunas são TEXT. O sqlite3 não adivinha tipos; para ele, um CSV é só texto. Veja os dados:
sqlite> .mode box sqlite> SELECT nome, preco, typeof(preco) FROM novos; ┌────────────────────────┬───────┬───────────────┐ │ nome │ preco │ typeof(preco) │ ├────────────────────────┼───────┼───────────────┤ │ Agenda 2027 │ 34.9 │ text │ │ Calculadora Científica │ 79.5 │ text │ │ Régua 30 cm │ 3.25 │ text │ └────────────────────────┴───────┴───────────────┘ sqlite> SELECT nome, estoque FROM novos ORDER BY estoque; ┌────────────────────────┬─────────┐ │ nome │ estoque │ ├────────────────────────┼─────────┤ │ Calculadora Científica │ 18 │ │ Régua 30 cm │ 250 │ │ Agenda 2027 │ 60 │ └────────────────────────┴─────────┘
A função typeof() mostra o tipo de cada valor (veja Os Tipos no SQLite): text. E a ordenação saiu esquisita: 18, 250, 60. Texto é ordenado como no dicionário, letra por letra, e "2" vem antes de "6", então '250' fica antes de '60'. Para ordenar como número, converta com CAST ("converter"):
CREATE TABLE novos (nome TEXT, preco TEXT, estoque TEXT);
INSERT INTO novos VALUES
('Agenda 2027', '34.9', '60'),
('Calculadora Científica', '79.5', '18'),
('Régua 30 cm', '3.25', '250');
SELECT nome, estoque, typeof(estoque),
CAST(estoque AS INTEGER) AS numero,
typeof(CAST(estoque AS INTEGER)) AS tipo_numero
FROM novos;
O editor não lê arquivos, então aqui a tabela novos é criada à mão, igualzinha à que o .import criou: três colunas TEXT e os valores como texto. O CAST(estoque AS INTEGER) transforma o texto '60' no número 60.
Experimente: ordene os produtos importados pelo estoque como número, do menor para o maior: 18, 60, 250.
CREATE TABLE novos (nome TEXT, preco TEXT, estoque TEXT);
INSERT INTO novos VALUES
('Agenda 2027', '34.9', '60'),
('Calculadora Científica', '79.5', '18'),
('Régua 30 cm', '3.25', '250');
SELECT nome, estoque FROM novos ORDER BY estoque;
CREATE TABLE novos (nome TEXT, preco TEXT, estoque TEXT);
INSERT INTO novos VALUES
('Agenda 2027', '34.9', '60'),
('Calculadora Científica', '79.5', '18'),
('Régua 30 cm', '3.25', '250');
SELECT nome, estoque FROM novos ORDER BY CAST(estoque AS INTEGER);
7. Importar para uma tabela que já existe
A loja ganhou dois clientes num evento, e a lista veio num CSV com as mesmas colunas da tabela clientes:
$ cat clientes_novos.csv id,nome,email,cidade,estado,nascimento,vip 31,Paula Rocha Leal,paula.leal@exemplo.com.br,Parnaíba,PI,1994-03-12,0 32,Davi Monteiro,,Teresina,PI,,0
Desta vez a tabela já existe. Quando a tabela existe, o .import trata todas as linhas como dados, inclusive a primeira. Como a primeira linha é o cabeçalho, a Ana manda pular uma linha com --skip 1:
$ sqlite3 ~/loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> .import --csv --skip 1 clientes_novos.csv clientes sqlite> .mode box sqlite> SELECT id, nome, email, nascimento FROM clientes WHERE id > 30; ┌────┬──────────────────┬───────────────────────────┬────────────┐ │ id │ nome │ email │ nascimento │ ├────┼──────────────────┼───────────────────────────┼────────────┤ │ 31 │ Paula Rocha Leal │ paula.leal@exemplo.com.br │ 1994-03-12 │ │ 32 │ Davi Monteiro │ │ │ └────┴──────────────────┴───────────────────────────┴────────────┘
- --skip 1
- "Pule 1 linha": ignora a primeira linha do arquivo, o cabeçalho. Com
--skip 2, pularia duas, e assim por diante. - clientes
- A tabela que já existe. As colunas do arquivo entram na ordem das colunas da tabela: a primeira do CSV vai para
id, a segunda paranome… O.importnão lê os nomes do cabeçalho para casar as colunas; a ordem tem de ser a mesma.
Os dois clientes entraram. Mas o Davi não tem email nem data de nascimento: os campos vieram vazios no CSV. Será que viraram NULL?
sqlite> SELECT id, typeof(id), email IS NULL AS sem_email, typeof(email) FROM clientes WHERE id > 30; ┌────┬────────────┬───────────┬───────────────┐ │ id │ typeof(id) │ sem_email │ typeof(email) │ ├────┼────────────┼───────────┼───────────────┤ │ 31 │ integer │ 0 │ text │ │ 32 │ integer │ 0 │ text │ └────┴────────────┴───────────┴───────────────┘
Duas surpresas, uma boa e uma ruim:
- A boa: o
idéinteger. Como a colunaidda tabelaclientesfoi criada comoINTEGER, o SQLite converteu o texto'31'em número ao inserir. Numa tabela que já existe, com tipos, os números entram como números. - A ruim: o email do Davi não é
NULL(sem_emaildeu 0). É um texto vazio,''. O.importtransforma campo vazio em texto vazio, não emNULL. E aí uma consultaWHERE email IS NULL, que deveria achar quem não tem email, não acha o Davi.
O conserto é um UPDATE logo depois de importar, trocando o texto vazio por NULL (veja UPDATE):
sqlite> UPDATE clientes SET email = NULL WHERE email = ''; sqlite> UPDATE clientes SET nascimento = NULL WHERE nascimento = ''; sqlite> SELECT id, nome, email IS NULL AS sem_email, nascimento IS NULL AS sem_nascimento FROM clientes WHERE id > 30; ┌────┬──────────────────┬───────────┬────────────────┐ │ id │ nome │ sem_email │ sem_nascimento │ ├────┼──────────────────┼───────────┼────────────────┤ │ 31 │ Paula Rocha Leal │ 0 │ 0 │ │ 32 │ Davi Monteiro │ 1 │ 1 │ └────┴──────────────────┴───────────┴────────────────┘
Agora o Davi aparece como "sem email" e "sem nascimento", como os outros clientes sem esses dados. No editor, você pode ver a diferença entre '' e NULL com o mesmo cliente:
INSERT INTO clientes (id, nome, email, cidade, estado, nascimento, vip) VALUES (32, 'Davi Monteiro', '', 'Teresina', 'PI', '', 0); SELECT count(*) AS sem_email_antes FROM clientes WHERE email IS NULL; UPDATE clientes SET email = NULL WHERE email = ''; SELECT count(*) AS sem_email_depois FROM clientes WHERE email IS NULL;
Antes do UPDATE, o IS NULL acha só os 3 clientes que já não tinham email; depois, acha 4.
8. Formas diferentes de fazer
| Para | Forma | Quando usar |
|---|---|---|
| Exportar uma consulta | .headers on + .mode csv + .once arq.csv | O jeito do dia a dia, dentro do sqlite3 |
| Exportar várias consultas seguidas | .output arq.csv … .output | Relatórios em texto; para planilha, prefira um arquivo por consulta |
| Exportar de um script | sqlite3 -header -csv loja.db "SELECT …" > arq.csv | Automatizar, sem abrir o sqlite3 |
| Abrir direto na planilha | .excel antes da consulta | Uma olhada rápida: o sqlite3 grava um arquivo temporário e o abre no programa de planilhas do computador |
| Importar para tabela nova | .import --csv arq.csv tabela | Para olhar os dados antes; o cabeçalho vira nome de coluna e tudo vira TEXT |
| Importar para tabela existente | .import --csv --skip 1 arq.csv tabela | Quando as colunas do arquivo estão na mesma ordem da tabela |
| Importar no modo CSV | .mode csv e depois .import arq.csv tabela | O mesmo que --csv; é a forma antiga, que você vai ver em tutoriais |
| Passar da tabela nova para a de verdade | INSERT INTO … SELECT … FROM novos | Quando o CSV tem outras colunas ou precisa de conversão (veja INSERT INTO SELECT) |
A forma mais segura de importar dados de fora é a de duas etapas: primeiro para uma tabela separada (como a novos), depois, com SQL, para a tabela de verdade, convertendo e conferindo no caminho. É a missão final desta aula.
Quando dá errado
As mensagens abaixo saíram do sqlite3 da Ana, dentro da pasta ~/Downloads, depois da importação da seção 7. Repare num padrão: o .import aponta o arquivo e a linha com problema (clientes_novos.csv:2: = linha 2 do arquivo) e continua com as linhas seguintes.
sqlite> .import --csv --skip 1 clientes_novos.csv clientes clientes_novos.csv:2: INSERT failed: UNIQUE constraint failed: clientes.id clientes_novos.csv:3: INSERT failed: UNIQUE constraint failed: clientes.id
O que aconteceu: "linha 2: o INSERT falhou: a restrição UNIQUE falhou em clientes.id". Os clientes 31 e 32 já estavam no banco, e o id é a chave primária, que não aceita repetição (veja PRIMARY KEY). Nenhuma linha entrou duas vezes, e o banco continua certo.
Como evitar: antes de importar de novo, confira o que já está lá (SELECT … WHERE id > 30). Se a tabela não tivesse chave primária, as linhas teriam entrado repetidas, sem aviso nenhum.
--skip 1 numa tabela que já existe
Outro arquivo, clientes_mais.csv, com cabeçalho e os clientes 34 e 35:
sqlite> .import --csv clientes_mais.csv clientes clientes_mais.csv:1: INSERT failed: datatype mismatch sqlite> SELECT id, nome FROM clientes WHERE id > 33; 34|Otávio Lima 35|Rita Nogueira
O que aconteceu: sem o --skip 1, o sqlite3 tentou inserir o cabeçalho como se fosse um cliente: id = 'id'. A coluna id é a chave INTEGER PRIMARY KEY, que só aceita número inteiro, e daí veio "linha 1: o INSERT falhou: tipo de dado incompatível". As outras duas linhas entraram normalmente. Aqui o erro foi inofensivo; numa tabela com colunas só de texto, o cabeçalho teria entrado como um registro falso, um cliente chamado "nome" que mora em "cidade".
Como consertar: numa tabela que já existe, use --skip 1 sempre que o arquivo tiver cabeçalho.
$ cat incompleto.csv id,nome,email,cidade,estado,nascimento,vip 33,Sílvia Barros,Picos
sqlite> .import --csv --skip 1 incompleto.csv clientes incompleto.csv:2: expected 7 columns but found 3 - filling the rest with NULL incompleto.csv:2: INSERT failed: NOT NULL constraint failed: clientes.vip
O que aconteceu: duas mensagens para a mesma linha. A primeira é um aviso: "esperava 7 colunas mas achou 3; preenchendo o resto com NULL". Quem escreveu o arquivo pôs a cidade no lugar do email. A segunda é o erro: a coluna vip é NOT NULL (veja NOT NULL), e o NULL do preenchimento não pode entrar. A Sílvia ficou de fora. Se a tabela aceitasse NULL em todas as colunas, ela teria entrado com "Picos" no email, sem erro nenhum.
Como consertar: abra o arquivo e corrija a linha (cada linha precisa ter o mesmo número de vírgulas que o cabeçalho). Se o aviso expected … columns aparecer, desconfie do arquivo inteiro.
sqlite> .import --csv --skip 1 clientes_nvos.csv clientes Error: cannot open "clientes_nvos.csv"
O que aconteceu: "não consigo abrir o arquivo". Ou o nome está errado (aqui faltou um "o"), ou o arquivo está em outra pasta. O sqlite3 procura o arquivo na pasta em que você o abriu, não na pasta do banco.
Como consertar: confira o nome com ls antes de abrir o sqlite3, ou use o caminho inteiro: .import --csv --skip 1 ~/Downloads/clientes_novos.csv clientes.
--csv
sqlite> .import novos_produtos.csv teste sqlite> .schema teste CREATE TABLE IF NOT EXISTS "teste"( "nome,preco,estoque" TEXT); sqlite> SELECT * FROM teste; Agenda 2027,34.9,60 Calculadora Científica,79.5,18 Régua 30 cm,3.25,250
O que aconteceu: nenhuma mensagem, e mesmo assim deu errado. Sem --csv (e sem .mode csv), o .import usa o separador padrão do sqlite3, a barra |. Como o arquivo não tem nenhuma barra, cada linha inteira virou uma coluna só, chamada "nome,preco,estoque".
Como consertar: apague a tabela errada (DROP TABLE teste;) e importe de novo com --csv. Depois de todo .import, dê um .schema na tabela para conferir as colunas.
.import no editor do guia
.import --csv novos_produtos.csv novos
O que aconteceu: "perto de .: erro de sintaxe". Os comandos de ponto não são SQL: quem os entende é o programa sqlite3. O editor só roda SQL, e no navegador não existe a sua pasta Downloads. Import e export se fazem no terminal.
Nos outros bancos
Os comandos de ponto são só do sqlite3. Os outros bancos têm comandos SQL próprios para isso, cada um do seu jeito. Os trechos abaixo são ilustrativos (não rodam no SQLite) e seguem a documentação oficial de cada banco:
-- PostgreSQL: o arquivo é lido/gravado pelo SERVIDOR do banco COPY clientes FROM '/dados/clientes_novos.csv' WITH (FORMAT csv, HEADER); COPY (SELECT nome, email FROM clientes WHERE vip = 1) TO '/dados/vips.csv' WITH (FORMAT csv, HEADER); -- no programa psql, \copy faz o mesmo com um arquivo do SEU computador
-- MySQL: LOCAL = o arquivo está no computador do cliente LOAD DATA LOCAL INFILE 'clientes_novos.csv' INTO TABLE clientes FIELDS TERMINATED BY ',' ENCLOSED BY '"' IGNORE 1 LINES; -- exportar: SELECT ... INTO OUTFILE 'arquivo' FIELDS TERMINATED BY ','
-- SQL Server 2017 ou mais novo: caminho visto pelo servidor BULK INSERT clientes FROM 'C:\dados\clientes_novos.csv' WITH (FORMAT = 'CSV', FIRSTROW = 2);
Repare no HEADER, no IGNORE 1 LINES e no FIRSTROW = 2: os três fazem o papel do nosso --skip 1. E repare que, nos bancos com servidor, é preciso saber em qual computador está o arquivo: no seu ou no servidor. No SQLite essa dúvida não existe, porque o banco é um arquivo no seu próprio computador.
Referência rápida
| Comando | O que faz |
|---|---|
.headers on / .headers off | Liga/desliga a linha de cabeçalho com os nomes das colunas |
.mode csv | Saída em CSV (vírgula, aspas onde precisa) |
.separator ; | Troca o separador de colunas (aqui, ponto e vírgula) |
.once arquivo.csv | Manda só a próxima consulta para o arquivo |
.output arquivo.csv | Manda tudo para o arquivo, até um .output sozinho |
.output | Devolve a saída para a tela |
.excel | Abre o resultado da próxima consulta no programa de planilhas |
.import --csv arq tabela | Importa o CSV; cria a tabela (com o cabeçalho) se ela não existir |
.import --csv --skip 1 arq tabela | Importa pulando a primeira linha (para tabela que já existe) |
sqlite3 -header -csv banco "SQL" > arq | Exporta direto do terminal |
| Mensagem | Quer dizer |
|---|---|
arq:N: INSERT failed: UNIQUE constraint failed: tabela.coluna | A linha N repete uma chave que já existe |
arq:1: INSERT failed: datatype mismatch | O cabeçalho entrou como dado numa coluna de número: faltou --skip 1 |
arq:N: expected 7 columns but found 3 - filling the rest with NULL | A linha N tem menos colunas que a tabela |
arq:N: INSERT failed: NOT NULL constraint failed: tabela.coluna | Um campo obrigatório ficou sem valor |
Error: cannot open "arq" | Arquivo não encontrado (nome ou pasta errados) |
Exercícios
1. A saída em CSV
Complete os comandos para gerar o arquivo categorias.csv com cabeçalho:
.mode
. categorias.csv
SELECT * FROM categorias;
2. Pular o cabeçalho
A tabela clientes já existe e o arquivo tem cabeçalho. Complete:
3. A Ana importou um CSV para uma tabela que não existia. Que tipo têm as colunas criadas?
4. No CSV, o campo email de um cliente estava vazio (…,Davi Monteiro,,Teresina…). Depois do .import, o que fica no banco?
5. A Ana escreveu .output relatorio.csv, rodou uma consulta e agora nada aparece na tela. O que ela faz?
6. O sqlite3 respondeu novos.csv:2: expected 7 columns but found 3 - filling the rest with NULL. O que isso quer dizer?
Missão final: do CSV para a tabela de verdade
Missão: os três produtos do fornecedor estão na tabela novos, do jeito que o .import deixou (tudo texto). Copie-os para a tabela produtos com um INSERT INTO … SELECT: categoria 1 (Papelaria), fornecedor 1, e o preço e o estoque convertidos para número com CAST (REAL para o preço, INTEGER para o estoque). O SELECT do fim mostra o que entrou.
CREATE TABLE novos (nome TEXT, preco TEXT, estoque TEXT);
INSERT INTO novos VALUES
('Agenda 2027', '34.9', '60'),
('Calculadora Científica', '79.5', '18'),
('Régua 30 cm', '3.25', '250');
-- escreva aqui o INSERT INTO produtos ... SELECT ... FROM novos;
SELECT nome, preco, typeof(preco), estoque FROM produtos WHERE id > 45;
CREATE TABLE novos (nome TEXT, preco TEXT, estoque TEXT);
INSERT INTO novos VALUES
('Agenda 2027', '34.9', '60'),
('Calculadora Científica', '79.5', '18'),
('Régua 30 cm', '3.25', '250');
INSERT INTO produtos (nome, categoria_id, fornecedor_id, preco, estoque)
SELECT nome, 1, 1, CAST(preco AS REAL), CAST(estoque AS INTEGER)
FROM novos;
SELECT nome, preco, typeof(preco), estoque FROM produtos WHERE id > 45;
Os produtos ganham os ids 46, 47 e 48 sozinhos (veja AUTOINCREMENT), e a coluna descontinuado, que você não citou, fica com o valor padrão, 0. Um detalhe honesto: como a coluna preco de produtos é REAL, o SQLite converteria o texto sozinho, mesmo sem o CAST. Escreva o CAST assim mesmo: ele deixa claro o que você quer e funciona igual em todos os bancos, inclusive nos que não convertem nada sozinhos. No terminal, o último passo seria DROP TABLE novos;, para não deixar a tabela provisória no banco. Quer o caminho completo, de uma planilha de verdade até as consultas? Veja o Projeto: da Planilha ao Banco.
Resumo
- CSV é uma planilha em texto: uma linha por registro, vírgula entre as colunas, cabeçalho opcional. Toda planilha e todo banco entendem.
- Para exportar:
.headers on,.mode csv,.once arquivo.csve a consulta..outputmanda tudo para o arquivo até um.outputsozinho. De um script:sqlite3 -header -csv loja.db "SELECT …" > arquivo.csv. - No CSV,
NULLvira campo vazio, e textos com espaço, vírgula ou acento vêm entre aspas duplas. - Para importar:
.import --csv arquivo tabela. Tabela nova: o cabeçalho vira nome de coluna e tudo viraTEXT(converta comCAST). Tabela existente: use--skip 1e mantenha a ordem das colunas. - Campo vazio importado vira
'', nãoNULL: conserte comUPDATE … SET coluna = NULL WHERE coluna = ''. - O
.importavisa arquivo e linha de cada problema e segue em frente. Depois de importar, confira com.schemae umSELECT.