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.

Você vai aprender
  • O que é um arquivo CSV e por que toda planilha entende esse formato.
  • A exportar uma consulta para CSV com .headers on, .mode csv e .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 --csv e --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 sqlite3 quando 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 planilhaNo CSV
Uma linhaUma linha do arquivo
A troca de colunaUma vírgula
A primeira linha, com os nomes das colunasO cabeçalho (opcional): id,nome,preco,estoque
Um texto com espaço, vírgula ou acentoPode 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 SELECT serve: com WHERE, ORDER BY, junção… O que ela devolveria na tela vai para o arquivo.
.quit
Sai do sqlite3 e 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").

Por que só alguns textos têm aspas?

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.

Esqueceu o .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, NULL vira 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 .import usa 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 sqlite3 a 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 para nome… O .import nã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 coluna id da tabela clientes foi criada como INTEGER, 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_email deu 0). É um texto vazio, ''. O .import transforma campo vazio em texto vazio, não em NULL. E aí uma consulta WHERE 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

ParaFormaQuando usar
Exportar uma consulta.headers on + .mode csv + .once arq.csvO jeito do dia a dia, dentro do sqlite3
Exportar várias consultas seguidas.output arq.csv … .outputRelatórios em texto; para planilha, prefira um arquivo por consulta
Exportar de um scriptsqlite3 -header -csv loja.db "SELECT …" > arq.csvAutomatizar, sem abrir o sqlite3
Abrir direto na planilha.excel antes da consultaUma 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 tabelaPara 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 tabelaQuando as colunas do arquivo estão na mesma ordem da tabela
Importar no modo CSV.mode csv e depois .import arq.csv tabelaO mesmo que --csv; é a forma antiga, que você vai ver em tutoriais
Passar da tabela nova para a de verdadeINSERT INTO … SELECT … FROM novosQuando 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.

Importou o mesmo arquivo duas vezes
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.

Esqueceu o --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.

Faltam colunas no arquivo
$ 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.

Nome do arquivo errado
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.

Esqueceu o --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.

Tentou usar .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

Import e export no MySQL, PostgreSQL e SQL Server

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

ComandoO que faz
.headers on / .headers offLiga/desliga a linha de cabeçalho com os nomes das colunas
.mode csvSaída em CSV (vírgula, aspas onde precisa)
.separator ;Troca o separador de colunas (aqui, ponto e vírgula)
.once arquivo.csvManda só a próxima consulta para o arquivo
.output arquivo.csvManda tudo para o arquivo, até um .output sozinho
.outputDevolve a saída para a tela
.excelAbre o resultado da próxima consulta no programa de planilhas
.import --csv arq tabelaImporta o CSV; cria a tabela (com o cabeçalho) se ela não existir
.import --csv --skip 1 arq tabelaImporta pulando a primeira linha (para tabela que já existe)
sqlite3 -header -csv banco "SQL" > arqExporta direto do terminal
MensagemQuer dizer
arq:N: INSERT failed: UNIQUE constraint failed: tabela.colunaA linha N repete uma chave que já existe
arq:1: INSERT failed: datatype mismatchO 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 NULLA linha N tem menos colunas que a tabela
arq:N: INSERT failed: NOT NULL constraint failed: tabela.colunaUm 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:

.headers
.mode
. categorias.csv
SELECT * FROM categorias;

2. Pular o cabeçalho

A tabela clientes já existe e o arquivo tem cabeçalho. Complete:

.import --csv -- clientes_novos.csv clientes

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.csv e a consulta. .output manda tudo para o arquivo até um .output sozinho. De um script: sqlite3 -header -csv loja.db "SELECT …" > arquivo.csv.
  • No CSV, NULL vira 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 vira TEXT (converta com CAST). Tabela existente: use --skip 1 e mantenha a ordem das colunas.
  • Campo vazio importado vira '', não NULL: conserte com UPDATE … SET coluna = NULL WHERE coluna = ''.
  • O .import avisa arquivo e linha de cada problema e segue em frente. Depois de importar, confira com .schema e um SELECT.