Saudações.
Vou ensinar como usar o banco de dados em arquivo chamado SQLITE3.
Pré-requisitos (constam em outros artigos aqui do blog):
- Instalação do Linux (Debian);
- Internet no servidor para instalar os programas;
Links úteis:
- Syntax diagrams: https://www.sqlite.org/syntaxdiagrams.html
- Site oficial: https://www.sqlite.org
- Documentação: https://www.sqlite.org/docs.html
- Download: https://www.sqlite.org/download.html
1 – Sobre o SQLITE
O SQLITE é, em uma definição simples, um banco de dados SQL de arquivo único embutido (embedded), auto-contido, sem servidor (serverless) e de configuração zero.
Diferente do PostgreSQL ou MySQL, o SQLite não requer um processo de servidor separado – ele lê e escreve diretamente em arquivos de disco.
Crie o arquivo “.db” ou “.sqlite” com pelo menos 1 tabela e você ja pode fazer uso imediato e armazenar dados.
Toda a política de banco de dados, tabelas, relações e execução é implementado pela biblioteca SQLITE executada pelo software cliente. Esse desafio impõe na libsqlite um peso grande, exigindo dela perfeição e algoritmos de última geração.
Ao alcançar estabilidade máxima ele se tornou o banco de dados preferido por milhões de aplicativos para smartphone (Whatsapp por exemplo) e por softwares modernos (padrão do N8N).
Principais características:
- Serverless: Não requer instalação ou configuração de servidor;
- Self-contained: Uma única biblioteca C sem dependências externas;
- Zero-configuration: Não precisa de setup ou administração;
- Cross-platform: Funciona em qualquer sistema operacional;
- Transacional: Suporte completo a ACID (Atomicidade, Consistência, Isolamento, Durabilidade);
- Compacto: Biblioteca com menos de 1MB.
- Segurança: ele NÃO possui sistema de usuários ou permissões interna, a segurança é gerenciada pelo sistema de arquivos do sistema operacional.
Instalando no Linux:
apt-get -y install sqlite3;
2 – Primeiro contato
Vamos criar um banco (arquivo):
# "Conectar" ao banco via terminal, se o arquivo não existir ele será criado:
sqlite3 /tmp/meubanco.db;
Ao executar o comando sqlite3, você estará no shell do SQLITE (linha de comando).
Use o comando “.quit” ou “.exit” para sair:
# Sair do shell
.quitO arquivo será criado vazio (zero bytes). Outra forma é entrar no comando sqlite3 sem especificar o arquivo do banco e carregá-lo pelo shell SQLITE:
# Entrar no shell sem informar o arquivo do banco:
sqlite3
-- Carregar arquivo, abrir/criar banco de dados
.open /tmp/meubanco.db
-- Exibir banco atual
.databases
-- main: /tmp/meubanco.db r/w
-- Verificar integridade do banco
PRAGMA integrity_check;
-- ok
-- Verificação rápida
PRAGMA quick_check;
-- ok
-- Salvar banco atual no mesmo arquivo
.save /tmp/meubanco.db
-- Salvar banco atual em arquivo versionado (backup manual)
.save /tmp/meubanco-v01.db
.exit3 – Comandos rápidos do SQLITE3
Quando estiver no shell do SQLITE, use o “.help” para pedir ajuda, guia completo de referência dos comandos de ponto:
-- COMANDOS DE PONTO (dot commands) - específicos do shell sqlite3
.help -- Ajuda geral dos comandos
.help COMMAND -- Ajuda para comando específico
.quit -- Sair do SQLite (ou .exit)
.exit -- Sair do SQLite (ou .quit)
.databases -- Listar bancos de dados conectados
.tables -- Listar tabelas do banco atual
.tables pattern -- Listar tabelas que correspondem ao padrão
.schema -- Mostrar estrutura de todas as tabelas
.schema tabela -- Mostrar estrutura de uma tabela específica
.fullschema -- Schema completo incluindo estatísticas
.indices -- Listar todos os índices
.indices tabela -- Listar índices de uma tabela específica
.headers on -- Mostrar cabeçalhos nas consultas
.headers off -- Ocultar cabeçalhos nas consultas
.mode column -- Modo de exibição em colunas alinhadas
.mode table -- Modo de exibição em tabela formatada
.mode box -- Modo de exibição em caixa (bordas)
.mode list -- Modo de exibição em lista (separado por |)
.mode csv -- Modo de exibição CSV
.mode json -- Modo de exibição JSON
.mode line -- Modo de exibição uma coluna por linha
.mode markdown -- Modo de exibição markdown
.mode html -- Modo de exibição HTML
.width N1 N2 ... -- Definir largura das colunas
.nullvalue 'NULL' -- Como exibir valores NULL
.separator ',' -- Definir separador para modo list/csv
.timer on -- Mostrar tempo de execução das queries
.timer off -- Ocultar tempo de execução
.open arquivo.db -- Abrir/criar banco de dados
.save arquivo.db -- Salvar banco atual em arquivo
.backup arquivo.db -- Fazer backup do banco atual
.restore arquivo.db -- Restaurar banco de backup
.read script.sql -- Executar comandos de um arquivo SQL
.output arquivo.txt -- Redirecionar saída para arquivo
.output stdout -- Voltar saída para tela
.dump -- Exportar banco inteiro como SQL
.dump tabela -- Exportar tabela específica como SQL
.import arquivo.csv tabela -- Importar CSV para tabela
.excel -- Abrir resultado no Excel (se disponível)
.show -- Mostrar configurações atuais
.stats on -- Mostrar estatísticas de execução
.stats off -- Ocultar estatísticas
.shell COMMAND -- Executar comando do sistema
.system COMMAND -- Executar comando do sistema (alias)
! COMMAND -- Executar comando do sistema (atalho)
.changes on -- Mostrar número de linhas alteradas
.changes off -- Ocultar número de linhas alteradas
.eqp on -- Mostrar plano de execução das queries
.eqp off -- Ocultar plano de execução
.explain on -- Modo de exibição para EXPLAIN
.explain off -- Voltar ao modo normal
.print texto -- Imprimir texto na saída
.once arquivo -- Próximo resultado vai para arquivo
.log arquivo -- Registrar todas as operações em arquivo
.log off -- Parar de registrar
-- PRAGMA - Comandos de configuração e informação
PRAGMA database_list; -- Listar bancos de dados anexados
PRAGMA table_info(tabela); -- Informações das colunas de uma tabela
PRAGMA table_xinfo(tabela); -- Informações estendidas das colunas
PRAGMA index_list(tabela); -- Lista de índices de uma tabela
PRAGMA index_info(indice); -- Informações de um índice
PRAGMA foreign_key_list(t); -- Lista de chaves estrangeiras
PRAGMA foreign_keys; -- Verificar se FK está habilitada
PRAGMA foreign_keys = ON; -- Habilitar verificação de FK
PRAGMA foreign_keys = OFF; -- Desabilitar verificação de FK
PRAGMA journal_mode; -- Ver modo de journal atual
PRAGMA journal_mode=WAL; -- Usar Write-Ahead Logging (recomendado)
PRAGMA journal_mode=DELETE; -- Modo padrão (delete journal após commit)
PRAGMA synchronous; -- Ver modo de sincronização
PRAGMA synchronous=NORMAL; -- Modo normal (equilíbrio)
PRAGMA synchronous=FULL; -- Modo seguro (mais lento)
PRAGMA synchronous=OFF; -- Modo rápido (risco de corrupção)
PRAGMA cache_size; -- Ver tamanho do cache
PRAGMA cache_size=10000; -- Definir cache (páginas, negativo=KB)
PRAGMA page_size; -- Ver tamanho da página
PRAGMA page_count; -- Número de páginas no banco
PRAGMA freelist_count; -- Páginas livres (não usadas)
PRAGMA integrity_check; -- Verificar integridade do banco
PRAGMA quick_check; -- Verificação rápida de integridade
PRAGMA optimize; -- Otimizar banco de dados
VACUUM; -- Reconstruir banco (compactar)
PRAGMA busy_timeout=5000; -- Timeout para banco bloqueado (ms)
PRAGMA encoding; -- Ver encoding do banco (UTF-8, etc.)
-- TRANSAÇÕES
BEGIN; -- Iniciar transação
BEGIN TRANSACTION; -- Iniciar transação (explícito)
BEGIN IMMEDIATE; -- Iniciar com lock imediato
BEGIN EXCLUSIVE; -- Iniciar com lock exclusivo
COMMIT; -- Confirmar transação
ROLLBACK; -- Desfazer transação
SAVEPOINT nome; -- Criar ponto de salvamento
RELEASE nome; -- Liberar savepoint
ROLLBACK TO nome; -- Voltar ao savepoint
-- ATTACH/DETACH - Múltiplos bancos
ATTACH 'outro.db' AS outro; -- Anexar outro banco de dados
DETACH outro; -- Desanexar banco de dados4 – Usando SQLITE em scripts shell/bash
Essa é a maneira mais usada para inicializar bancos SQLITE, observe:
# Criar diretório para bancos
mkdir -p /var/lib/sqlite;
# Criar banco de dados do sistema
sqlite3 /var/lib/sqlite/sistema.db << 'EOF'
.headers on
.mode column
CREATE TABLE IF NOT EXISTS config (
chave TEXT PRIMARY KEY,
valor TEXT
);
INSERT INTO config VALUES ('versao', '1.0.0');
INSERT INTO config VALUES ('nome', 'MeuSistema');
SELECT * FROM config;
EOF
# Ou no meu formato:
# - arquivo com esquema de tabelas
(
echo '.headers on';
echo '.mode column';
echo 'CREATE TABLE IF NOT EXISTS config (';
echo ' chave TEXT PRIMARY KEY,';
echo ' valor TEXT';
echo ');';
) > /var/lib/sqlite/001-schema.sql;
# - arquivo com dados iniciais
(
echo "INSERT INTO config VALUES ('versao', '1.0.0');";
echo "INSERT INTO config VALUES ('nome', 'MeuSistema');";
) > /var/lib/sqlite/010-startup-config.sql;
# Aplicar:
sqlite3 /var/lib/sqlite/sistema.db < /var/lib/sqlite/001-schema.sql;
sqlite3 /var/lib/sqlite/sistema.db < /var/lib/sqlite/010-startup-config.sql;
# Conferindo:
sqlite3 /var/lib/sqlite/sistema.db "SELECT * FROM config;";
# Verificar arquivo criado
ls -lah /var/lib/sqlite/sistema.db;
# -rw-r--r-- 1 root root 12K Dec 20 11:04 /var/lib/sqlite/sistema.db
# Conferir o esquema aplicado e as tabelas
sqlite3 /var/lib/sqlite/sistema.db ".schema";
# CREATE TABLE config (
# chave TEXT PRIMARY KEY,
# valor TEXT
# );
sqlite3 /var/lib/sqlite/sistema.db ".tables";
# config
Exemplos de execução em linha de comando:
# Criar banco e executar comando único
sqlite3 teste.db "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);";
# Inserir dados via comando
sqlite3 teste.db "INSERT INTO users (name) VALUES ('João');";
sqlite3 teste.db "INSERT INTO users (name) VALUES ('Maria');";
# Consultar dados
sqlite3 teste.db "SELECT * FROM users;";
# Executar com formatação
sqlite3 -header -column teste.db "SELECT * FROM users;";
# Saída em CSV
sqlite3 -header -csv teste.db "SELECT * FROM users;" > users.csv;
# Saída em JSON
sqlite3 -json teste.db "SELECT * FROM users;";
# Executar arquivo SQL
cat > script.sql << 'EOF'
.headers on
.mode box
SELECT * FROM users;
SELECT COUNT(*) AS total FROM users;
EOF
sqlite3 teste.db < script.sql;
# Executar múltiplos comandos
sqlite3 teste.db << 'EOF'
.headers on
.mode column
SELECT * FROM users;
INSERT INTO users (name) VALUES ('Pedro');
SELECT * FROM users;
EOF
# Dump do banco
sqlite3 teste.db .dump > backup.sql;
# Restore do dump
sqlite3 novo_banco.db < backup.sql;
# Verificar integridade
sqlite3 teste.db "PRAGMA integrity_check;";
# Ver estrutura
sqlite3 teste.db ".schema";
# Modo interativo com configurações
sqlite3 -header -column -nullvalue NULL teste.db;
# One-liner para estatísticas
sqlite3 teste.db "SELECT COUNT(*) as total, MAX(id) as ultimo_id FROM users;";
# Limpar arquivos de teste
rm -f teste.db novo_banco.db script.sql backup.sql users.csv;
x
x
5 – Configuração de comportamento
O SQLITE possui vários parâmetros para flexibilizar o uso, permitir acesso simultâneo de escrita e leitura (WAL):
# Pasta para salvar arquivos
mkdir -p /var/lib/sqlite;
# Entrar no sqlite3
sqlite3;
Dentro do sqlite3:
-- Configurar modo de exibição
.headers on
.mode box
-- Criar banco de aplicação
.open /var/lib/sqlite/certificados.db;
-- Verificar informações do banco
PRAGMA database_list;
PRAGMA page_size;
PRAGMA page_count;
PRAGMA encoding;
-- Otimizar para performance
PRAGMA journal_mode = WAL; -- Write-Ahead Logging
PRAGMA synchronous = NORMAL; -- Equilíbrio segurança/velocidade
PRAGMA cache_size = -64000; -- 64MB de cache
PRAGMA temp_store = MEMORY; -- Temporários em RAM
PRAGMA mmap_size = 268435456; -- 256MB memory-mapped I/O
-- Verificar configurações
PRAGMA journal_mode;
PRAGMA synchronous;
PRAGMA cache_size;
-- Salvar explicitamente
.save /var/lib/sqlite/certificados.db;
.quit5 – Tipos de colunas
Os tipos de colunas no SQLITE são:
| Afinidade | Tipos aceitos | Descrição |
|---|---|---|
| INTEGER | INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, INT2, INT8 | Números inteiros |
| REAL | REAL, DOUBLE, FLOAT, DOUBLE PRECISION | Ponto flutuante |
| TEXT | TEXT, VARCHAR, CHAR, CLOB, CHARACTER | Strings de texto |
| BLOB | BLOB, nenhum tipo especificado | Dados binários |
| NUMERIC | NUMERIC, DECIMAL, BOOLEAN, DATE, DATETIME | Numérico genérico |
| DATE/TIME | O SQLite NÃO possui tipo DATE/TIME nativo, datas são armazenadas como TEXT, INTEGER ou REAL | *Não* |
Praticando tipos:
-- Criar banco
.open /var/lib/sqlite/tipos.db;
-- Tabela para testes de tipos
-- ============================================
-- O SQLite aceita QUALQUER dado em QUALQUER coluna (exceto INTEGER PRIMARY KEY)
CREATE TABLE IF NOT EXISTS teste_de_tipos (
id INTEGER PRIMARY KEY,
coluna_int INTEGER,
coluna_text TEXT,
coluna_real REAL
);
-- Inserir dados de tipos errados - SQLite aceita:
INSERT INTO teste_de_tipos VALUES (1, 'texto', 123, 'abc');
INSERT INTO teste_de_tipos VALUES (2, 3.14, NULL, 999);
-- Ver dados
SELECT * FROM teste_de_tipos;
-- Ver tipos reais armazenados
SELECT
id,
typeof(coluna_int) AS tipo_int,
typeof(coluna_text) AS tipo_text,
typeof(coluna_real) AS tipo_real
FROM teste_de_tipos;
-- Tabela de teste de numeros
-- ============================================
CREATE TABLE teste_integer (
id INTEGER PRIMARY KEY,
pequeno INTEGER, -- Qualquer inteiro
contador INTEGER DEFAULT 0,
booleano INTEGER CHECK (booleano IN (0, 1))
);
-- Inserir valores
INSERT INTO teste_integer (pequeno, contador, booleano) VALUES (42, 100, 1);
INSERT INTO teste_integer (pequeno, contador, booleano) VALUES (-999, 0, 0);
INSERT INTO teste_integer (pequeno, contador, booleano) VALUES (9223372036854775807, 1, 1);
-- Visualizar
SELECT * FROM teste_integer;
-- Operações matemáticas
SELECT
id,
pequeno,
pequeno * 2 AS dobro,
pequeno + 100 AS mais_cem,
ABS(pequeno) AS absoluto
FROM teste_integer;
-- Tabela de teste de numeros reais
-- ============================================
CREATE TABLE teste_real (
id INTEGER PRIMARY KEY,
preco REAL,
taxa REAL DEFAULT 0.0,
coordenada_lat REAL,
coordenada_lng REAL
);
-- Inserir valores decimais
INSERT INTO teste_real (preco, taxa, coordenada_lat, coordenada_lng)
VALUES (99.99, 0.15, -23.5505199, -46.6333094);
INSERT INTO teste_real (preco, taxa, coordenada_lat, coordenada_lng)
VALUES (1234.56789, 0.0825, -22.9068467, -43.1728965);
-- Consultar com formatação
SELECT
id,
printf('%.2f', preco) AS preco,
printf('%.4f%%', taxa * 100) AS taxa_percentual,
printf('%.8f', coordenada_lat) AS latitude,
printf('%.8f', coordenada_lng) AS longitude
FROM teste_real;
-- Cálculos com REAL
SELECT
preco,
taxa,
printf('%.2f', preco * (1 + taxa)) AS preco_com_taxa,
ROUND(preco, 1) AS preco_arredondado
FROM teste_real;
-- Tabela de teste de texto
-- ============================================
CREATE TABLE teste_text (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
descricao TEXT,
email TEXT UNIQUE,
json_data TEXT, -- Armazenar JSON como texto
codigo TEXT CHECK (LENGTH(codigo) <= 10)
);
-- Inserir textos
INSERT INTO teste_text (nome, descricao, email, codigo)
VALUES ('Produto A', 'Descrição longa do produto...', 'a@email.com', 'ABC123');
INSERT INTO teste_text (nome, email, json_data)
VALUES ('Produto B', 'b@email.com', '{"cor": "azul", "tamanho": "M"}');
-- Consultas com funções de texto
SELECT
nome,
LENGTH(nome) AS tamanho_nome,
UPPER(nome) AS nome_maiusculo,
LOWER(nome) AS nome_minusculo,
SUBSTR(nome, 1, 5) AS primeiros_5
FROM teste_text;
-- Busca com LIKE
SELECT * FROM teste_text WHERE nome LIKE 'Produto%';
SELECT * FROM teste_text WHERE email LIKE '%@email.com';
-- Busca case-insensitive
SELECT * FROM teste_text WHERE nome LIKE '%produto%' COLLATE NOCASE;
-- Concatenação
SELECT nome || ' - ' || COALESCE(descricao, 'Sem descrição') AS nome_completo
FROM teste_text;
-- Extrair JSON (SQLite 3.38+)
SELECT
nome,
json_data,
json_extract(json_data, '$.cor') AS cor,
json_extract(json_data, '$.tamanho') AS tamanho
FROM teste_text
WHERE json_data IS NOT NULL;
-- Tabela de teste de blob/binario
-- ============================================
CREATE TABLE teste_blob (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
arquivo BLOB,
hash_md5 BLOB
);
-- Inserir dados binários (hexadecimal)
INSERT INTO teste_blob (nome, arquivo, hash_md5)
VALUES ('imagem.png', X'89504E470D0A1A0A', X'D41D8CD98F00B204E9800998ECF8427E');
-- Inserir via zeroblob (blob de zeros)
INSERT INTO teste_blob (nome, arquivo)
VALUES ('placeholder', zeroblob(1024)); -- 1KB de zeros
-- Consultar
SELECT
id,
nome,
LENGTH(arquivo) AS tamanho_bytes,
HEX(arquivo) AS arquivo_hex,
HEX(hash_md5) AS hash_hex
FROM teste_blob;
-- Verificar tipo
SELECT nome, typeof(arquivo) AS tipo FROM teste_blob;
DROP TABLE teste_blob;
-- Tabela basica de controle de data/hora
-- ============================================
CREATE TABLE teste_datetime (
id INTEGER PRIMARY KEY,
evento TEXT NOT NULL,
-- Diferentes formas de armazenar data/hora
data_texto TEXT, -- ISO8601: '2024-01-15 14:30:00'
data_unix INTEGER, -- Unix timestamp: 1705329000
data_julian REAL -- Dia Juliano: 2460325.10417
);
-- Inserir datas
INSERT INTO teste_datetime (evento, data_texto, data_unix, data_julian) VALUES
('Evento 1', datetime('now'), strftime('%s', 'now'), julianday('now'));
INSERT INTO teste_datetime (evento, data_texto, data_unix, data_julian) VALUES
('Evento 2', '2024-06-15 10:00:00', 1718445600, 2460476.91667);
INSERT INTO teste_datetime (evento, data_texto) VALUES
('Evento 3', datetime('now', '+7 days'));
-- Consultar e formatar datas
SELECT
evento,
data_texto,
date(data_texto) AS apenas_data,
time(data_texto) AS apenas_hora,
strftime('%d/%m/%Y', data_texto) AS data_br,
strftime('%H:%M', data_texto) AS hora_curta
FROM teste_datetime;
-- Converter Unix timestamp
SELECT
evento,
data_unix,
datetime(data_unix, 'unixepoch') AS data_legivel,
datetime(data_unix, 'unixepoch', 'localtime') AS data_local
FROM teste_datetime;
-- Funções de data úteis
SELECT
datetime('now') AS agora,
date('now') AS hoje,
time('now') AS hora_atual,
datetime('now', 'localtime') AS agora_local,
datetime('now', '+1 day') AS amanha,
datetime('now', '-1 month') AS mes_passado,
datetime('now', 'start of month') AS inicio_mes,
datetime('now', 'start of year') AS inicio_ano,
strftime('%W', 'now') AS semana_ano,
strftime('%j', 'now') AS dia_ano;
-- Calcular diferença entre datas
SELECT
evento,
data_texto,
CAST((julianday('now') - julianday(data_texto)) AS INTEGER) AS dias_passados,
CAST((julianday('now') - julianday(data_texto)) * 24 AS INTEGER) AS horas_passadas
FROM teste_datetime;
DROP TABLE teste_datetime;
-- Tabela basica de controle de usuarios
-- ============================================
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- Auto incremento
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
status INTEGER DEFAULT 1, -- Boolean como INTEGER
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
-- Ver estrutura
.schema users
PRAGMA table_info(users);
-- Inserir dados
INSERT INTO users (name, email) VALUES ('João', 'joao@email.com');
INSERT INTO users (name, email) VALUES ('Maria', 'maria@email.com');
INSERT INTO users (name, email, status) VALUES ('Pedro', 'pedro@email.com', 0);
-- Consultar
SELECT * FROM users;
-- Salvar explicitamente
.save /var/lib/sqlite/tipos.db;
.quit6 – Índices
Índices ajudam a localizar registros mais rápidos.
.open /var/lib/sqlite/indices.db
.headers on
.mode box
-- Mostrar tempo de execução
.timer on
-- Criar tabela de exemplo com muitos dados
-- ============================================
CREATE TABLE IF NOT EXISTS clientes (
id INTEGER PRIMARY KEY,
cpf TEXT UNIQUE NOT NULL,
nome TEXT NOT NULL,
email TEXT,
cidade TEXT,
estado TEXT,
ativo INTEGER DEFAULT 1,
criado_em TEXT DEFAULT CURRENT_TIMESTAMP
);
-- Inserir dados de teste (10.000 registros simulados)
WITH RECURSIVE cnt(x) AS (
SELECT 1
UNION ALL
SELECT x + 1 FROM cnt WHERE x < 10000
)
INSERT INTO clientes (cpf, nome, email, cidade, estado)
SELECT
printf('%011d', x) AS cpf,
'Cliente ' || x AS nome,
'cliente' || x || '@email.com' AS email,
CASE (x % 5)
WHEN 0 THEN 'São Paulo'
WHEN 1 THEN 'Rio de Janeiro'
WHEN 2 THEN 'Belo Horizonte'
WHEN 3 THEN 'Curitiba'
ELSE 'Porto Alegre'
END AS cidade,
CASE (x % 5)
WHEN 0 THEN 'SP'
WHEN 1 THEN 'RJ'
WHEN 2 THEN 'MG'
WHEN 3 THEN 'PR'
ELSE 'RS'
END AS estado
FROM cnt;
-- Verificar quantidade
SELECT COUNT(*) AS total FROM clientes;
-- ============================================
-- CONSULTA SEM ÍNDICE
-- ============================================
-- Ver plano de execução (sem índice em cidade)
EXPLAIN QUERY PLAN SELECT * FROM clientes WHERE cidade = 'São Paulo';
-- SCAN clientes (busca completa na tabela)
-- Executar consulta (note o tempo)
SELECT COUNT(*) FROM clientes WHERE cidade = 'São Paulo';
-- ============================================
-- CRIAR ÍNDICES
-- ============================================
-- Índice simples em uma coluna
CREATE INDEX idx_clientes_cidade ON clientes(cidade);
-- Ver plano de execução (com índice)
EXPLAIN QUERY PLAN SELECT * FROM clientes WHERE cidade = 'São Paulo';
-- SEARCH clientes USING INDEX idx_clientes_cidade (usa índice)
-- Executar mesma consulta (compare o tempo)
SELECT COUNT(*) FROM clientes WHERE cidade = 'São Paulo';
-- Índice composto (múltiplas colunas)
CREATE INDEX idx_clientes_estado_cidade ON clientes(estado, cidade);
-- Índice com ordenação
CREATE INDEX idx_clientes_nome ON clientes(nome COLLATE NOCASE);
-- Índice parcial (apenas registros ativos)
CREATE INDEX idx_clientes_ativos ON clientes(email) WHERE ativo = 1;
-- Índice único (já existe no cpf por UNIQUE)
-- CREATE UNIQUE INDEX idx_clientes_cpf ON clientes(cpf);
-- ============================================
-- GERENCIAR ÍNDICES
-- ============================================
-- Listar todos os índices
.indices clientes
-- Ver estrutura de índice específico
PRAGMA index_info(idx_clientes_cidade);
-- Ver lista de índices com mais detalhes
PRAGMA index_list(clientes);
-- Ver SQL de criação do índice
SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = 'clientes';
-- Analisar estatísticas (otimiza planos de execução)
ANALYZE clientes;
-- Ver estatísticas
SELECT * FROM sqlite_stat1 WHERE tbl = 'clientes';
-- ============================================
-- REMOVER ÍNDICES
-- ============================================
-- Remover índice
DROP INDEX IF EXISTS idx_clientes_cidade;
DROP INDEX IF EXISTS idx_clientes_estado_cidade;
DROP INDEX IF EXISTS idx_clientes_nome;
DROP INDEX IF EXISTS idx_clientes_ativos;
-- Reindexar tabela
REINDEX clientes;
-- Destruir tabela
DROP TABLE clientes;
-- Forcar salvamento
.save /var/lib/sqlite/indices.db
7 – Views
Views são tabelas virtuais construídas em tempo de execução para produzir registros em um formato especifico, extraindo dados de outras tabelas:
.open /var/lib/sqlite/views.db
.headers on
.mode box
-- Criar tabelas base
CREATE TABLE funcionarios (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
cargo TEXT,
departamento TEXT,
salario REAL,
data_admissao TEXT,
ativo INTEGER DEFAULT 1
);
CREATE TABLE departamentos (
id INTEGER PRIMARY KEY,
nome TEXT UNIQUE NOT NULL,
orcamento REAL
);
-- Inserir dados
INSERT INTO departamentos VALUES (1, 'TI', 500000);
INSERT INTO departamentos VALUES (2, 'RH', 200000);
INSERT INTO departamentos VALUES (3, 'Vendas', 300000);
INSERT INTO funcionarios VALUES (1, 'João Silva', 'Desenvolvedor', 'TI', 8000, '2020-01-15', 1);
INSERT INTO funcionarios VALUES (2, 'Maria Santos', 'Gerente', 'RH', 12000, '2018-06-01', 1);
INSERT INTO funcionarios VALUES (3, 'Pedro Costa', 'Vendedor', 'Vendas', 5000, '2021-03-10', 1);
INSERT INTO funcionarios VALUES (4, 'Ana Oliveira', 'Analista', 'TI', 6500, '2022-08-20', 1);
INSERT INTO funcionarios VALUES (5, 'Carlos Lima', 'Estagiário', 'TI', 2000, '2023-01-05', 0);
-- ============================================
-- CRIAR VIEWS
-- ============================================
-- View simples - funcionários ativos
CREATE VIEW vw_funcionarios_ativos AS
SELECT id, nome, cargo, departamento, salario
FROM funcionarios
WHERE ativo = 1;
-- Usar a view como tabela
SELECT * FROM vw_funcionarios_ativos;
-- View com cálculos
CREATE VIEW vw_folha_pagamento AS
SELECT
f.nome,
f.cargo,
f.departamento,
f.salario,
f.salario * 0.11 AS inss,
f.salario * 0.275 AS irrf_estimado,
f.salario - (f.salario * 0.11) AS salario_liquido_est
FROM funcionarios f
WHERE f.ativo = 1;
SELECT * FROM vw_folha_pagamento;
-- View com agregação - resumo por departamento
CREATE VIEW vw_resumo_departamento AS
SELECT
departamento,
COUNT(*) AS total_funcionarios,
printf('R$ %.2f', SUM(salario)) AS total_salarios,
printf('R$ %.2f', AVG(salario)) AS media_salarios,
printf('R$ %.2f', MIN(salario)) AS menor_salario,
printf('R$ %.2f', MAX(salario)) AS maior_salario
FROM funcionarios
WHERE ativo = 1
GROUP BY departamento;
SELECT * FROM vw_resumo_departamento;
-- View com JOIN
CREATE VIEW vw_funcionarios_completo AS
SELECT
f.id,
f.nome,
f.cargo,
f.departamento,
d.orcamento AS orcamento_depto,
printf('R$ %.2f', f.salario) AS salario,
date(f.data_admissao) AS admissao,
CAST((julianday('now') - julianday(f.data_admissao)) / 365 AS INTEGER) AS anos_empresa,
CASE f.ativo WHEN 1 THEN 'Ativo' ELSE 'Inativo' END AS status
FROM funcionarios f
LEFT JOIN departamentos d ON f.departamento = d.nome;
SELECT * FROM vw_funcionarios_completo;
-- ============================================
-- GERENCIAR VIEWS
-- ============================================
-- Listar views
SELECT name, sql FROM sqlite_master WHERE type = 'view';
-- Ver definição de view específica
SELECT sql FROM sqlite_master WHERE type = 'view' AND name = 'vw_funcionarios_ativos';
-- Recriar view (alterar)
DROP VIEW IF EXISTS vw_funcionarios_ativos;
CREATE VIEW vw_funcionarios_ativos AS
SELECT id, nome, cargo, departamento, printf('R$ %.2f', salario) AS salario
FROM funcionarios
WHERE ativo = 1
ORDER BY nome;
-- Remover view
DROP VIEW IF EXISTS vw_folha_pagamento;
-- ============================================
-- VIEWS TEMPORÁRIAS
-- ============================================
-- View temporária (existe apenas na sessão)
CREATE TEMP VIEW vw_temp_ti AS
SELECT * FROM funcionarios WHERE departamento = 'TI';
SELECT * FROM vw_temp_ti;
-- Limpar
DROP VIEW IF EXISTS vw_funcionarios_ativos;
DROP VIEW IF EXISTS vw_resumo_departamento;
DROP VIEW IF EXISTS vw_funcionarios_completo;
DROP TABLE funcionarios;
DROP TABLE departamentos;
7 – Triggers
Triggers executam código automaticamente em resposta a eventos (INSERT, UPDATE, DELETE):
.open /var/lib/sqlite/triggers.db
.headers on
.mode box
-- Criar tabelas
CREATE TABLE produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
preco REAL NOT NULL,
estoque INTEGER DEFAULT 0,
atualizado_em TEXT
);
CREATE TABLE produtos_log (
id INTEGER PRIMARY KEY,
produto_id INTEGER,
acao TEXT,
dados_antigos TEXT,
dados_novos TEXT,
usuario TEXT DEFAULT 'sistema',
data_hora TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE estoque_alertas (
id INTEGER PRIMARY KEY,
produto_id INTEGER,
produto_nome TEXT,
estoque_atual INTEGER,
mensagem TEXT,
data_hora TEXT DEFAULT CURRENT_TIMESTAMP
);
-- ============================================
-- TRIGGERS DE INSERT
-- ============================================
-- Trigger AFTER INSERT - registrar log
CREATE TRIGGER trg_produtos_insert_log
AFTER INSERT ON produtos
BEGIN
INSERT INTO produtos_log (produto_id, acao, dados_novos)
VALUES (
NEW.id,
'INSERT',
json_object('nome', NEW.nome, 'preco', NEW.preco, 'estoque', NEW.estoque)
);
END;
-- Trigger AFTER INSERT - alerta de estoque baixo
CREATE TRIGGER trg_produtos_estoque_baixo
AFTER INSERT ON produtos
WHEN NEW.estoque < 10
BEGIN
INSERT INTO estoque_alertas (produto_id, produto_nome, estoque_atual, mensagem)
VALUES (NEW.id, NEW.nome, NEW.estoque, 'Estoque baixo ao cadastrar produto!');
END;
-- Testar INSERT
INSERT INTO produtos (nome, preco, estoque) VALUES ('Notebook', 4500.00, 5);
INSERT INTO produtos (nome, preco, estoque) VALUES ('Mouse', 89.90, 100);
SELECT * FROM produtos;
SELECT * FROM produtos_log;
SELECT * FROM estoque_alertas;
-- ============================================
-- TRIGGERS DE UPDATE
-- ============================================
-- Trigger BEFORE UPDATE - atualizar timestamp
CREATE TRIGGER trg_produtos_update_timestamp
BEFORE UPDATE ON produtos
BEGIN
UPDATE produtos SET atualizado_em = datetime('now') WHERE id = NEW.id;
END;
-- Trigger AFTER UPDATE - registrar log
CREATE TRIGGER trg_produtos_update_log
AFTER UPDATE ON produtos
BEGIN
INSERT INTO produtos_log (produto_id, acao, dados_antigos, dados_novos)
VALUES (
NEW.id,
'UPDATE',
json_object('nome', OLD.nome, 'preco', OLD.preco, 'estoque', OLD.estoque),
json_object('nome', NEW.nome, 'preco', NEW.preco, 'estoque', NEW.estoque)
);
END;
-- Trigger AFTER UPDATE - alerta quando estoque diminui
CREATE TRIGGER trg_produtos_estoque_update
AFTER UPDATE OF estoque ON produtos
WHEN NEW.estoque < 10 AND OLD.estoque >= 10
BEGIN
INSERT INTO estoque_alertas (produto_id, produto_nome, estoque_atual, mensagem)
VALUES (NEW.id, NEW.nome, NEW.estoque, 'Estoque ficou abaixo do mínimo!');
END;
-- Testar UPDATE
UPDATE produtos SET preco = 4299.00 WHERE id = 1;
UPDATE produtos SET estoque = 8 WHERE id = 2;
SELECT * FROM produtos;
SELECT * FROM produtos_log;
SELECT * FROM estoque_alertas;
-- ============================================
-- TRIGGERS DE DELETE
-- ============================================
-- Trigger BEFORE DELETE - registrar log
CREATE TRIGGER trg_produtos_delete_log
BEFORE DELETE ON produtos
BEGIN
INSERT INTO produtos_log (produto_id, acao, dados_antigos)
VALUES (
OLD.id,
'DELETE',
json_object('nome', OLD.nome, 'preco', OLD.preco, 'estoque', OLD.estoque)
);
END;
-- Testar DELETE
DELETE FROM produtos WHERE id = 1;
SELECT * FROM produtos;
SELECT * FROM produtos_log;
-- ============================================
-- TRIGGER INSTEAD OF (para VIEWS)
-- ============================================
-- Criar view
CREATE VIEW vw_produtos_ativos AS
SELECT id, nome, preco, estoque FROM produtos WHERE estoque > 0;
-- Trigger para permitir INSERT via VIEW
CREATE TRIGGER trg_vw_produtos_insert
INSTEAD OF INSERT ON vw_produtos_ativos
BEGIN
INSERT INTO produtos (nome, preco, estoque)
VALUES (NEW.nome, NEW.preco, NEW.estoque);
END;
-- Inserir via view
INSERT INTO vw_produtos_ativos (nome, preco, estoque) VALUES ('Teclado', 199.90, 50);
SELECT * FROM produtos;
SELECT * FROM vw_produtos_ativos;
-- ============================================
-- GERENCIAR TRIGGERS
-- ============================================
-- Listar triggers
SELECT name, sql FROM sqlite_master WHERE type = 'trigger';
-- Ver trigger específico
SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = 'trg_produtos_insert_log';
-- Remover trigger
DROP TRIGGER IF EXISTS trg_produtos_insert_log;
DROP TRIGGER IF EXISTS trg_produtos_estoque_baixo;
-- Limpar tudo
DROP VIEW IF EXISTS vw_produtos_ativos;
DROP TABLE IF EXISTS produtos;
DROP TABLE IF EXISTS produtos_log;
DROP TABLE IF EXISTS estoque_alertas;8 – Transações
Transações garantem integridade dos dados (ACID):
.open /var/lib/sqlite/triggers.db
.headers on
.mode box
-- Criar tabelas
CREATE TABLE contas (
id INTEGER PRIMARY KEY,
titular TEXT NOT NULL,
saldo REAL DEFAULT 0 CHECK (saldo >= 0)
);
CREATE TABLE transferencias (
id INTEGER PRIMARY KEY,
conta_origem INTEGER,
conta_destino INTEGER,
valor REAL,
data_hora TEXT DEFAULT CURRENT_TIMESTAMP,
status TEXT
);
-- Dados iniciais
INSERT INTO contas (titular, saldo) VALUES ('João', 1000.00);
INSERT INTO contas (titular, saldo) VALUES ('Maria', 500.00);
INSERT INTO contas (titular, saldo) VALUES ('Pedro', 200.00);
SELECT * FROM contas;
-- ============================================
-- TRANSAÇÃO BÁSICA - COMMIT
-- ============================================
-- Iniciar transação
BEGIN TRANSACTION;
-- Transferir R$ 100 de João para Maria
UPDATE contas SET saldo = saldo - 100 WHERE id = 1;
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;
-- Registrar transferência
INSERT INTO transferencias (conta_origem, conta_destino, valor, status)
VALUES (1, 2, 100.00, 'concluida');
-- Verificar antes de confirmar
SELECT * FROM contas;
-- Confirmar transação
COMMIT;
SELECT * FROM contas;
SELECT * FROM transferencias;
-- ============================================
-- TRANSAÇÃO COM ROLLBACK
-- ============================================
-- Verificar saldo atual
SELECT * FROM contas;
-- Iniciar transação
BEGIN TRANSACTION;
-- Tentar transferir R$ 1000 de Pedro (só tem R$ 200)
UPDATE contas SET saldo = saldo - 1000 WHERE id = 3;
-- ERRO: CHECK constraint failed (saldo >= 0)
-- Se houve erro, desfazer
ROLLBACK;
-- Saldos permanecem inalterados
SELECT * FROM contas;
-- ============================================
-- TRANSAÇÃO COM SAVEPOINT
-- ============================================
BEGIN TRANSACTION;
-- Primeira operação
UPDATE contas SET saldo = saldo + 50 WHERE id = 1;
SAVEPOINT ponto1;
-- Segunda operação
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;
SAVEPOINT ponto2;
-- Terceira operação (queremos desfazer)
UPDATE contas SET saldo = saldo + 200 WHERE id = 3;
-- Ver estado atual
SELECT * FROM contas;
-- Voltar ao ponto2 (desfaz terceira operação)
ROLLBACK TO ponto2;
-- Ver estado após rollback parcial
SELECT * FROM contas;
-- Confirmar transação
COMMIT;
SELECT * FROM contas;
-- ============================================
-- MODOS DE TRANSAÇÃO
-- ============================================
-- BEGIN DEFERRED (padrão) - lock obtido quando necessário
BEGIN DEFERRED TRANSACTION;
SELECT * FROM contas;
COMMIT;
-- BEGIN IMMEDIATE - lock de escrita imediato
BEGIN IMMEDIATE TRANSACTION;
UPDATE contas SET saldo = saldo + 1 WHERE id = 1;
COMMIT;
-- BEGIN EXCLUSIVE - lock exclusivo (bloqueia outras conexões)
BEGIN EXCLUSIVE TRANSACTION;
UPDATE contas SET saldo = saldo + 1 WHERE id = 1;
COMMIT;
-- ============================================
-- TRANSAÇÃO AUTOMÁTICA (Autocommit)
-- ============================================
-- Por padrão, cada comando é uma transação automática
-- Estes comandos são equivalentes:
UPDATE contas SET saldo = 1000 WHERE id = 1;
-- É igual a:
BEGIN;
UPDATE contas SET saldo = 1000 WHERE id = 1;
COMMIT;
-- Limpar
DROP TABLE contas;
DROP TABLE transferencias;9 – Funções SQL Úteis
.open /var/lib/sqlite/samples01.db
.headers on
.mode box
-- ============================================
-- FUNÇÕES DE AGREGAÇÃO
-- ============================================
CREATE TEMP TABLE vendas (
id INTEGER PRIMARY KEY,
produto TEXT,
quantidade INTEGER,
valor REAL,
data TEXT
);
INSERT INTO vendas VALUES
(1, 'A', 10, 100.00, '2024-01-15'),
(2, 'B', 5, 50.00, '2024-01-16'),
(3, 'A', 8, 80.00, '2024-01-17'),
(4, 'C', 15, 150.00, '2024-01-18'),
(5, 'B', 3, 30.00, '2024-01-19');
SELECT
COUNT(*) AS total_vendas,
COUNT(DISTINCT produto) AS produtos_unicos,
SUM(quantidade) AS qtd_total,
SUM(valor) AS valor_total,
AVG(valor) AS media_valor,
MIN(valor) AS menor_venda,
MAX(valor) AS maior_venda,
GROUP_CONCAT(produto, ', ') AS produtos
FROM vendas;
-- Agregação por grupo
SELECT
produto,
COUNT(*) AS vendas,
SUM(quantidade) AS qtd,
printf('R$ %.2f', SUM(valor)) AS total
FROM vendas
GROUP BY produto
HAVING SUM(valor) > 50;
-- ============================================
-- FUNÇÕES DE TEXTO
-- ============================================
SELECT
'texto' AS original,
LENGTH('texto') AS tamanho,
UPPER('texto') AS maiusculo,
LOWER('TEXTO') AS minusculo,
SUBSTR('texto', 2, 3) AS substring,
REPLACE('texto', 'e', 'a') AS substituido,
TRIM(' texto ') AS sem_espacos,
LTRIM(' texto') AS sem_espacos_esq,
RTRIM('texto ') AS sem_espacos_dir,
INSTR('texto', 'x') AS posicao_x,
'pre' || 'fixo' AS concatenado,
PRINTF('%05d', 42) AS formatado,
QUOTE('texto com ''aspas''') AS com_escape;
-- ============================================
-- FUNÇÕES NUMÉRICAS
-- ============================================
SELECT
ABS(-10) AS absoluto,
ROUND(3.14159, 2) AS arredondado,
ROUND(3.5) AS arred_inteiro,
MAX(1, 2, 3) AS maximo,
MIN(1, 2, 3) AS minimo,
RANDOM() AS aleatorio,
ABS(RANDOM() % 100) AS aleatorio_0_99,
CAST(3.7 AS INTEGER) AS para_inteiro,
CAST(42 AS REAL) AS para_real,
TYPEOF(42) AS tipo_int,
TYPEOF(3.14) AS tipo_real,
TYPEOF('abc') AS tipo_text;
-- ============================================
-- FUNÇÕES DE DATA/HORA
-- ============================================
SELECT
DATE('now') AS hoje,
TIME('now') AS hora,
DATETIME('now') AS data_hora,
DATETIME('now', 'localtime') AS data_hora_local,
DATE('now', '+7 days') AS daqui_7_dias,
DATE('now', '-1 month') AS mes_passado,
DATE('now', 'start of month') AS inicio_mes,
DATE('now', 'start of year') AS inicio_ano,
DATE('now', '+1 month', 'start of month', '-1 day') AS fim_mes,
STRFTIME('%d/%m/%Y', 'now') AS formato_br,
STRFTIME('%H:%M:%S', 'now') AS formato_hora,
STRFTIME('%Y', 'now') AS ano,
STRFTIME('%m', 'now') AS mes,
STRFTIME('%d', 'now') AS dia,
STRFTIME('%W', 'now') AS semana_ano,
STRFTIME('%w', 'now') AS dia_semana,
STRFTIME('%s', 'now') AS unix_timestamp,
JULIANDAY('now') AS dia_juliano;
-- Diferença entre datas
SELECT
CAST(JULIANDAY('2024-12-31') - JULIANDAY('2024-01-01') AS INTEGER) AS dias_no_ano,
CAST((JULIANDAY('now') - JULIANDAY('2024-01-01')) AS INTEGER) AS dias_desde_jan;
-- ============================================
-- FUNÇÕES CONDICIONAIS
-- ============================================
SELECT
COALESCE(NULL, 'valor_padrao') AS coalesce_ex,
IFNULL(NULL, 'se_null') AS ifnull_ex,
NULLIF(1, 1) AS nullif_igual,
NULLIF(1, 2) AS nullif_diferente,
IIF(1 > 0, 'verdadeiro', 'falso') AS iif_ex,
CASE
WHEN 10 > 5 THEN 'maior'
WHEN 10 < 5 THEN 'menor'
ELSE 'igual'
END AS case_ex;
-- CASE com valores
SELECT
produto,
valor,
CASE
WHEN valor >= 100 THEN 'Alto'
WHEN valor >= 50 THEN 'Médio'
ELSE 'Baixo'
END AS categoria
FROM vendas;
-- ============================================
-- FUNÇÕES JSON (SQLite 3.38+)
-- ============================================
SELECT
JSON('{"nome":"João","idade":30}') AS json_valido,
JSON_EXTRACT('{"nome":"João","idade":30}', '$.nome') AS nome,
JSON_EXTRACT('{"nome":"João","idade":30}', '$.idade') AS idade,
JSON_TYPE('{"nome":"João"}', '$.nome') AS tipo_nome,
JSON_ARRAY(1, 2, 3) AS array_json,
JSON_OBJECT('a', 1, 'b', 2) AS objeto_json,
JSON_INSERT('{"a":1}', '$.b', 2) AS json_inserido,
JSON_REPLACE('{"a":1}', '$.a', 99) AS json_substituido,
JSON_SET('{"a":1}', '$.b', 2) AS json_setado,
JSON_REMOVE('{"a":1,"b":2}', '$.b') AS json_removido;
-- JSON com arrays
SELECT
JSON_ARRAY_LENGTH('[1,2,3,4,5]') AS tamanho,
JSON_EXTRACT('[1,2,3,4,5]', '$[0]') AS primeiro,
JSON_EXTRACT('[1,2,3,4,5]', '$[#-1]') AS ultimo;
DROP TABLE vendas;
10 – Consultas avançadas
Mais exemplos:
.open /var/lib/sqlite/consultas.db
.headers on
.mode box
-- Criar tabelas de exemplo
CREATE TABLE clientes (
id INTEGER PRIMARY KEY,
nome TEXT,
cidade TEXT
);
CREATE TABLE pedidos (
id INTEGER PRIMARY KEY,
cliente_id INTEGER,
valor REAL,
status TEXT,
data TEXT
);
-- Inserir dados
INSERT INTO clientes VALUES
(1, 'João', 'São Paulo'),
(2, 'Maria', 'Rio de Janeiro'),
(3, 'Pedro', 'São Paulo'),
(4, 'Ana', 'Curitiba');
INSERT INTO pedidos VALUES
(1, 1, 100.00, 'entregue', '2024-01-10'),
(2, 1, 200.00, 'entregue', '2024-01-15'),
(3, 2, 150.00, 'pendente', '2024-01-20'),
(4, 2, 300.00, 'entregue', '2024-02-01'),
(5, 3, 50.00, 'cancelado', '2024-02-10'),
(6, 1, 175.00, 'pendente', '2024-02-15');
-- ============================================
-- JOINS
-- ============================================
-- INNER JOIN
SELECT c.nome, p.valor, p.status
FROM clientes c
INNER JOIN pedidos p ON c.id = p.cliente_id;
-- LEFT JOIN (todos os clientes, mesmo sem pedidos)
SELECT c.nome, COALESCE(SUM(p.valor), 0) AS total_compras
FROM clientes c
LEFT JOIN pedidos p ON c.id = p.cliente_id
GROUP BY c.id;
-- ============================================
-- SUBQUERIES
-- ============================================
-- Subquery no WHERE
SELECT * FROM clientes
WHERE id IN (SELECT DISTINCT cliente_id FROM pedidos WHERE status = 'entregue');
-- Subquery escalar
SELECT
nome,
(SELECT COUNT(*) FROM pedidos WHERE cliente_id = clientes.id) AS qtd_pedidos
FROM clientes;
-- Subquery no FROM
SELECT cidade, total_clientes
FROM (
SELECT cidade, COUNT(*) AS total_clientes
FROM clientes
GROUP BY cidade
) AS resumo
WHERE total_clientes >= 1;
-- ============================================
-- CTE (Common Table Expressions)
-- ============================================
-- CTE simples
WITH pedidos_entregues AS (
SELECT * FROM pedidos WHERE status = 'entregue'
)
SELECT c.nome, SUM(pe.valor) AS total_entregue
FROM clientes c
JOIN pedidos_entregues pe ON c.id = pe.cliente_id
GROUP BY c.id;
-- CTE múltiplas
WITH
resumo_cliente AS (
SELECT
cliente_id,
COUNT(*) AS qtd_pedidos,
SUM(valor) AS total
FROM pedidos
GROUP BY cliente_id
),
media_geral AS (
SELECT AVG(total) AS media FROM resumo_cliente
)
SELECT
c.nome,
rc.qtd_pedidos,
printf('R$ %.2f', rc.total) AS total,
CASE WHEN rc.total > mg.media THEN 'Acima' ELSE 'Abaixo' END AS comparativo
FROM clientes c
JOIN resumo_cliente rc ON c.id = rc.cliente_id
CROSS JOIN media_geral mg;
-- CTE recursiva (hierarquia)
CREATE TABLE categorias (
id INTEGER PRIMARY KEY,
nome TEXT,
pai_id INTEGER
);
INSERT INTO categorias VALUES
(1, 'Eletrônicos', NULL),
(2, 'Computadores', 1),
(3, 'Notebooks', 2),
(4, 'Desktops', 2),
(5, 'Celulares', 1),
(6, 'Smartphones', 5);
WITH RECURSIVE arvore AS (
-- Base: categorias raiz
SELECT id, nome, pai_id, 0 AS nivel, nome AS caminho
FROM categorias
WHERE pai_id IS NULL
UNION ALL
-- Recursivo: filhos
SELECT c.id, c.nome, c.pai_id, a.nivel + 1, a.caminho || ' > ' || c.nome
FROM categorias c
JOIN arvore a ON c.pai_id = a.id
)
SELECT
SUBSTR(' ', 1, nivel * 2) || nome AS categoria,
caminho
FROM arvore
ORDER BY caminho;
-- ============================================
-- WINDOW FUNCTIONS (SQLite 3.25+)
-- ============================================
-- ROW_NUMBER, RANK, DENSE_RANK
SELECT
cliente_id,
valor,
ROW_NUMBER() OVER (ORDER BY valor DESC) AS posicao,
RANK() OVER (ORDER BY valor DESC) AS ranking,
DENSE_RANK() OVER (ORDER BY valor DESC) AS ranking_denso
FROM pedidos;
-- Particionado por cliente
SELECT
cliente_id,
valor,
ROW_NUMBER() OVER (PARTITION BY cliente_id ORDER BY valor DESC) AS pos_cliente,
SUM(valor) OVER (PARTITION BY cliente_id) AS total_cliente
FROM pedidos;
-- Running total (soma acumulada)
SELECT
data,
valor,
SUM(valor) OVER (ORDER BY data) AS acumulado,
AVG(valor) OVER (ORDER BY data ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS media_movel_3
FROM pedidos
ORDER BY data;
-- LEAD e LAG (próximo e anterior)
SELECT
data,
valor,
LAG(valor, 1) OVER (ORDER BY data) AS valor_anterior,
LEAD(valor, 1) OVER (ORDER BY data) AS valor_proximo,
valor - LAG(valor, 1) OVER (ORDER BY data) AS diferenca
FROM pedidos
ORDER BY data;
-- FIRST_VALUE e LAST_VALUE
SELECT
cliente_id,
data,
valor,
FIRST_VALUE(valor) OVER (
PARTITION BY cliente_id ORDER BY data
) AS primeiro_valor,
LAST_VALUE(valor) OVER (
PARTITION BY cliente_id ORDER BY data
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ultimo_valor
FROM pedidos;
-- ============================================
-- UPSERT (INSERT OR REPLACE)
-- ============================================
CREATE TABLE config (
chave TEXT PRIMARY KEY,
valor TEXT,
atualizado TEXT
);
-- Insert or Replace (substitui se existir)
INSERT OR REPLACE INTO config VALUES ('tema', 'escuro', datetime('now'));
INSERT OR REPLACE INTO config VALUES ('idioma', 'pt-BR', datetime('now'));
INSERT OR REPLACE INTO config VALUES ('tema', 'claro', datetime('now'));
SELECT * FROM config;
-- Insert or Ignore (ignora se existir)
INSERT OR IGNORE INTO config VALUES ('tema', 'azul', datetime('now'));
SELECT * FROM config;
-- UPSERT com ON CONFLICT (SQLite 3.24+)
INSERT INTO config (chave, valor, atualizado) VALUES ('versao', '1.0', datetime('now'))
ON CONFLICT(chave) DO UPDATE SET
valor = excluded.valor,
atualizado = excluded.atualizado;
SELECT * FROM config;
-- Limpar
DROP TABLE clientes;
DROP TABLE pedidos;
DROP TABLE categorias;
DROP TABLE config;11 – Importação e Exportação de Dados
Manipulando dados para dentro e para fora do SQLITE:
.open /var/lib/sqlite/importacao.db
-- ============================================
-- EXPORTAR DADOS
-- ============================================
-- Criar tabela de exemplo
CREATE TABLE usuarios (
id INTEGER PRIMARY KEY,
nome TEXT,
email TEXT,
cidade TEXT
);
INSERT INTO usuarios VALUES
(1, 'João Silva', 'joao@email.com', 'São Paulo'),
(2, 'Maria Santos', 'maria@email.com', 'Rio de Janeiro'),
(3, 'Pedro Costa', 'pedro@email.com', 'Belo Horizonte');
-- Exportar para CSV
.headers on
.mode csv
.output usuarios.csv
SELECT * FROM usuarios;
.output stdout
-- Exportar para JSON (linha por linha)
.mode json
.output usuarios.json
SELECT * FROM usuarios;
.output stdout
-- Exportar como SQL (dump)
.output usuarios.sql
.dump usuarios
.output stdout
-- Exportar apenas dados (INSERT)
.mode insert usuarios
.output usuarios_insert.sql
SELECT * FROM usuarios;
.output stdout
-- Ver arquivos criados
.shell ls -la *.csv *.json *.sql
-- ============================================
-- IMPORTAR DADOS
-- ============================================
-- Criar tabela para importação
CREATE TABLE usuarios_import (
id INTEGER PRIMARY KEY,
nome TEXT,
email TEXT,
cidade TEXT
);
-- Importar CSV
.mode csv
.import usuarios.csv usuarios_import
-- Verificar importação
.mode box
.headers on
SELECT * FROM usuarios_import;
-- Se CSV tiver header, pular primeira linha
DELETE FROM usuarios_import;
.import --skip 1 usuarios.csv usuarios_import
SELECT * FROM usuarios_import;
-- ============================================
-- BACKUP E RESTORE
-- ============================================
-- Backup do banco inteiro
.backup backup_completo.db
-- Dump SQL completo
.output dump_completo.sql
.dump
.output stdout
-- Restore de dump
.read dump_completo.sql
-- ============================================
-- ATTACH E COPIAR ENTRE BANCOS
-- ============================================
-- Criar segundo banco
ATTACH DATABASE 'outro_banco.db' AS outro;
-- Copiar tabela para outro banco
CREATE TABLE outro.usuarios AS SELECT * FROM usuarios;
-- Verificar
SELECT * FROM outro.usuarios;
-- Desanexar
DETACH DATABASE outro;
-- Limpar arquivos
.shell rm -f usuarios.csv usuarios.json usuarios.sql usuarios_insert.sql
.shell rm -f backup_completo.db dump_completo.sql outro_banco.db
DROP TABLE usuarios;
DROP TABLE usuarios_import;
12 – Performance e Otimização
.open /var/lib/sqlite/performance.db
.headers on
.mode box
-- ============================================
-- CONFIGURAÇÕES DE PERFORMANCE
-- ============================================
-- Modo WAL (Write-Ahead Logging) - recomendado para concorrência
PRAGMA journal_mode = WAL;
-- Cache em memória (páginas, negativo = KB)
PRAGMA cache_size = -64000; -- 64MB
-- Armazenar temporários em memória
PRAGMA temp_store = MEMORY;
-- Memory-mapped I/O
PRAGMA mmap_size = 268435456; -- 256MB
-- Modo de sincronização
PRAGMA synchronous = NORMAL; -- Equilíbrio entre segurança e velocidade
-- Verificar configurações
PRAGMA journal_mode;
PRAGMA cache_size;
PRAGMA temp_store;
PRAGMA mmap_size;
PRAGMA synchronous;
-- ============================================
-- ANALISAR QUERIES
-- ============================================
-- Criar tabela grande para testes
CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
nivel TEXT,
mensagem TEXT,
data_hora TEXT,
origem TEXT
);
-- Inserir 100.000 registros
WITH RECURSIVE cnt(x) AS (
SELECT 1
UNION ALL
SELECT x + 1 FROM cnt WHERE x < 100000
)
INSERT INTO logs (nivel, mensagem, data_hora, origem)
SELECT
CASE (x % 4) WHEN 0 THEN 'INFO' WHEN 1 THEN 'WARN' WHEN 2 THEN 'ERROR' ELSE 'DEBUG' END,
'Mensagem de log número ' || x,
datetime('2024-01-01', '+' || (x % 365) || ' days', '+' || (x % 24) || ' hours'),
CASE (x % 3) WHEN 0 THEN 'api' WHEN 1 THEN 'web' ELSE 'batch' END
FROM cnt;
SELECT COUNT(*) AS total FROM logs;
-- Ver plano de execução
.timer on
EXPLAIN QUERY PLAN SELECT * FROM logs WHERE nivel = 'ERROR';
-- Resultado: SCAN logs (varredura completa)
-- Criar índice
CREATE INDEX idx_logs_nivel ON logs(nivel);
-- Ver plano novamente
EXPLAIN QUERY PLAN SELECT * FROM logs WHERE nivel = 'ERROR';
-- Resultado: SEARCH logs USING INDEX (usa índice)
-- Comparar tempo de execução
SELECT COUNT(*) FROM logs WHERE nivel = 'ERROR';
-- Analisar estatísticas
ANALYZE logs;
-- Ver estatísticas
SELECT * FROM sqlite_stat1 WHERE tbl = 'logs';
-- ============================================
-- EXPLAIN DETALHADO
-- ============================================
-- EXPLAIN mostra bytecode VM
EXPLAIN SELECT * FROM logs WHERE nivel = 'ERROR' LIMIT 5;
-- EXPLAIN QUERY PLAN é mais legível
EXPLAIN QUERY PLAN
SELECT l1.nivel, COUNT(*)
FROM logs l1
JOIN logs l2 ON l1.data_hora = l2.data_hora
GROUP BY l1.nivel;
-- ============================================
-- VACUUM - OTIMIZAÇÃO DE ESPAÇO
-- ============================================
-- Ver tamanho do banco
SELECT page_count * page_size AS tamanho_bytes FROM pragma_page_count(), pragma_page_size();
-- Deletar metade dos registros
DELETE FROM logs WHERE id % 2 = 0;
-- Ver páginas livres
PRAGMA freelist_count;
-- Compactar banco (recupera espaço)
VACUUM;
-- Verificar novamente
PRAGMA freelist_count;
SELECT page_count * page_size AS tamanho_bytes FROM pragma_page_count(), pragma_page_size();
-- ============================================
-- OTIMIZAÇÃO AUTOMÁTICA
-- ============================================
-- Rodar otimização (analisa e reorganiza)
PRAGMA optimize;
-- Verificar integridade
PRAGMA integrity_check;
-- Limpar
DROP TABLE logs;
.timer off13 – Extensões e Funcionalidades Especiais
.open /var/lib/sqlite/extensoes.db
.headers on
.mode box
-- ============================================
-- FULL-TEXT SEARCH (FTS5)
-- ============================================
-- Criar tabela FTS5
CREATE VIRTUAL TABLE artigos USING fts5(
titulo,
conteudo,
autor,
content='artigos_base',
content_rowid='id'
);
-- Tabela base
CREATE TABLE artigos_base (
id INTEGER PRIMARY KEY,
titulo TEXT,
conteudo TEXT,
autor TEXT
);
-- Triggers para sincronizar
CREATE TRIGGER artigos_ai AFTER INSERT ON artigos_base BEGIN
INSERT INTO artigos(rowid, titulo, conteudo, autor)
VALUES (new.id, new.titulo, new.conteudo, new.autor);
END;
CREATE TRIGGER artigos_ad AFTER DELETE ON artigos_base BEGIN
INSERT INTO artigos(artigos, rowid, titulo, conteudo, autor)
VALUES ('delete', old.id, old.titulo, old.conteudo, old.autor);
END;
CREATE TRIGGER artigos_au AFTER UPDATE ON artigos_base BEGIN
INSERT INTO artigos(artigos, rowid, titulo, conteudo, autor)
VALUES ('delete', old.id, old.titulo, old.conteudo, old.autor);
INSERT INTO artigos(rowid, titulo, conteudo, autor)
VALUES (new.id, new.titulo, new.conteudo, new.autor);
END;
-- Inserir dados
INSERT INTO artigos_base (titulo, conteudo, autor) VALUES
('Introdução ao SQLite', 'SQLite é um banco de dados leve e eficiente para aplicações embarcadas.', 'João'),
('Python e Banco de Dados', 'Python possui excelente suporte para SQLite através do módulo sqlite3.', 'Maria'),
('Performance em SQLite', 'Dicas para otimizar consultas e índices no SQLite.', 'Pedro'),
('SQLite vs PostgreSQL', 'Comparação entre SQLite e PostgreSQL para diferentes casos de uso.', 'Ana');
-- Busca full-text
SELECT * FROM artigos WHERE artigos MATCH 'SQLite';
-- Busca com ranking
SELECT *, rank FROM artigos WHERE artigos MATCH 'SQLite' ORDER BY rank;
-- Busca em coluna específica
SELECT * FROM artigos WHERE artigos MATCH 'titulo:SQLite';
-- Busca com operadores
SELECT * FROM artigos WHERE artigos MATCH 'SQLite AND Python';
SELECT * FROM artigos WHERE artigos MATCH 'SQLite OR PostgreSQL';
SELECT * FROM artigos WHERE artigos MATCH 'SQLite NOT PostgreSQL';
-- Busca por prefixo
SELECT * FROM artigos WHERE artigos MATCH 'SQL*';
-- Highlight dos termos encontrados
SELECT highlight(artigos, 0, '<b>', '</b>') AS titulo_destacado
FROM artigos
WHERE artigos MATCH 'SQLite';
-- Snippet do conteúdo
SELECT snippet(artigos, 1, '<b>', '</b>', '...', 10) AS trecho
FROM artigos
WHERE artigos MATCH 'banco dados';
-- ============================================
-- R-TREE (Índice Espacial)
-- ============================================
-- Criar índice R-Tree para dados geoespaciais
CREATE VIRTUAL TABLE locais USING rtree(
id,
min_lat, max_lat,
min_lng, max_lng
);
-- Inserir pontos (como retângulos de tamanho zero)
INSERT INTO locais VALUES (1, -23.5505, -23.5505, -46.6333, -46.6333); -- São Paulo
INSERT INTO locais VALUES (2, -22.9068, -22.9068, -43.1729, -43.1729); -- Rio de Janeiro
INSERT INTO locais VALUES (3, -19.9191, -19.9191, -43.9386, -43.9386); -- Belo Horizonte
INSERT INTO locais VALUES (4, -25.4284, -25.4284, -49.2733, -49.2733); -- Curitiba
-- Buscar pontos em uma região (bounding box)
SELECT id FROM locais
WHERE min_lat >= -25 AND max_lat <= -20
AND min_lng >= -50 AND max_lng <= -40;
-- ============================================
-- GENERATED COLUMNS (SQLite 3.31+)
-- ============================================
CREATE TABLE produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
preco REAL NOT NULL,
quantidade INTEGER NOT NULL,
-- Coluna gerada (calculada automaticamente)
valor_total REAL GENERATED ALWAYS AS (preco * quantidade) STORED,
-- Coluna virtual (calculada sob demanda)
em_estoque TEXT GENERATED ALWAYS AS (
CASE WHEN quantidade > 0 THEN 'Sim' ELSE 'Não' END
) VIRTUAL
);
INSERT INTO produtos (nome, preco, quantidade) VALUES ('Notebook', 3500.00, 10);
INSERT INTO produtos (nome, preco, quantidade) VALUES ('Mouse', 89.90, 50);
INSERT INTO produtos (nome, preco, quantidade) VALUES ('Monitor', 1200.00, 0);
SELECT * FROM produtos;
-- ============================================
-- STRICT TABLES (SQLite 3.37+)
-- ============================================
-- Tabela com tipos estritos (como outros bancos)
CREATE TABLE usuarios_strict (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
idade INTEGER,
saldo REAL
) STRICT;
-- Isso funciona
INSERT INTO usuarios_strict VALUES (1, 'João', 30, 1000.50);
-- Isso FALHA (texto em coluna INTEGER)
-- INSERT INTO usuarios_strict VALUES (2, 'Maria', 'trinta', 500.00);
-- Error: cannot store TEXT value in INTEGER column usuarios_strict.idade
-- ============================================
-- JSON (Funções nativas)
-- ============================================
CREATE TABLE configuracoes (
id INTEGER PRIMARY KEY,
nome TEXT,
dados TEXT -- JSON armazenado como TEXT
);
INSERT INTO configuracoes VALUES
(1, 'app', '{"tema": "escuro", "idioma": "pt-BR", "notificacoes": true}'),
(2, 'usuario', '{"nome": "João", "preferencias": {"fonte": 14, "cores": ["azul", "verde"]}}');
-- Extrair valores JSON
SELECT
nome,
json_extract(dados, '$.tema') AS tema,
json_extract(dados, '$.idioma') AS idioma
FROM configuracoes
WHERE nome = 'app';
-- Extrair de estruturas aninhadas
SELECT
json_extract(dados, '$.nome') AS nome_usuario,
json_extract(dados, '$.preferencias.fonte') AS tamanho_fonte,
json_extract(dados, '$.preferencias.cores[0]') AS cor_primaria
FROM configuracoes
WHERE nome = 'usuario';
-- Modificar JSON
UPDATE configuracoes
SET dados = json_set(dados, '$.tema', 'claro')
WHERE nome = 'app';
SELECT dados FROM configuracoes WHERE nome = 'app';
-- Limpar
DROP TABLE artigos;
DROP TABLE artigos_base;
DROP TABLE locais;
DROP TABLE produtos;
DROP TABLE usuarios_strict;
DROP TABLE configuracoes;14 – Dicas e Boas Práticas
-- ============================================
-- BOAS PRÁTICAS
-- ============================================
-- 1. SEMPRE usar transações para múltiplas operações
BEGIN;
INSERT INTO tabela VALUES (1, 'a');
INSERT INTO tabela VALUES (2, 'b');
INSERT INTO tabela VALUES (3, 'c');
COMMIT;
-- 2. Habilitar chaves estrangeiras (desabilitadas por padrão!)
PRAGMA foreign_keys = ON;
-- 3. Usar WAL mode para melhor concorrência
PRAGMA journal_mode = WAL;
-- 4. Criar índices para colunas usadas em WHERE, JOIN, ORDER BY
CREATE INDEX idx_tabela_coluna ON tabela(coluna);
-- 5. Usar EXPLAIN QUERY PLAN para otimizar queries
EXPLAIN QUERY PLAN SELECT * FROM tabela WHERE coluna = 'valor';
-- 6. Executar ANALYZE após grandes alterações
ANALYZE;
-- 7. Usar VACUUM periodicamente para compactar
VACUUM;
-- 8. Usar prepared statements (via código) para evitar SQL injection
-- 9. Fazer backup regular
-- .backup arquivo_backup.db
-- 10. Usar tipos apropriados (mesmo com type affinity flexível)
CREATE TABLE exemplo (
id INTEGER PRIMARY KEY, -- Sempre INTEGER para PK
nome TEXT NOT NULL, -- TEXT para strings
valor REAL, -- REAL para decimais
ativo INTEGER DEFAULT 1, -- INTEGER para boolean (0/1)
dados BLOB, -- BLOB para binários
criado TEXT DEFAULT CURRENT_TIMESTAMP -- TEXT para datas
);
-- ============================================
-- ERROS COMUNS A EVITAR
-- ============================================
-- ❌ NÃO: Esquecer de habilitar FK
-- ✅ SIM: PRAGMA foreign_keys = ON;
-- ❌ NÃO: Múltiplos INSERTs sem transação (muito lento)
-- ✅ SIM: BEGIN; ... múltiplos INSERTs ... COMMIT;
-- ❌ NÃO: SELECT * em produção
-- ✅ SIM: SELECT coluna1, coluna2 FROM tabela;
-- ❌ NÃO: Confiar no autocommit para operações críticas
-- ✅ SIM: Usar transações explícitas
-- ❌ NÃO: Guardar arquivos grandes no banco
-- ✅ SIM: Guardar path do arquivo e manter arquivo no filesystem
-- ❌ NÃO: Usar SQLite para alta concorrência de escrita
-- ✅ SIM: Usar PostgreSQL/MySQL para esses casos
-- ============================================
-- QUANDO USAR SQLITE
-- ============================================
-- ✅ Aplicações desktop/mobile
-- ✅ Sites com baixo/médio tráfego
-- ✅ Dispositivos embarcados/IoT
-- ✅ Testes e desenvolvimento
-- ✅ Cache local
-- ✅ Armazenamento de configurações
-- ✅ Prototipagem rápida
-- ✅ Análise de dados (datasets pequenos/médios)
-- ❌ Alta concorrência de escrita
-- ❌ Grandes volumes de dados (>100GB)
-- ❌ Replicação necessária
-- ❌ Múltiplos servidores acessando mesmo banco
“A simplicidade é o mais
alto grau de sofisticação“
Autor: Leonardo da Vinci
Terminamos por hoje!
Patrick Brandão, patrickbrandao@gmail.com
