SQLite com Python: Banco de Dados com o Módulo sqlite3

Cansado de programar?

Conheça a melhor e mais completa formação de Python e Django e sinta-se um programador verdadeiramente competente. Além de Python e Django, você também vai aprender Banco de Dados, SQL, HTML, CSS, Javascript, Bootstrap e muito mais!

Quero aprender Python e Django de Verdade! Quero aprender!
Suporte

Tire suas dúvidas diretamente com o professor

Projetos práticos

Projetos práticos voltados para o mercado de trabalho

Prática profissional

Formação moderna com foco na prática profissional

Resposta rápida

O Python já traz o módulo sqlite3, que cria e acessa bancos SQLite (um arquivo .db) sem instalar nada. Conecte com sqlite3.connect("banco.db"), rode SQL com execute() usando placeholders ? para os valores e grave com commit().

1
2
3
4
5
6
import sqlite3

conexao = sqlite3.connect(":memory:")
conexao.execute("CREATE TABLE alunos (nome TEXT, nota REAL)")
conexao.execute("INSERT INTO alunos VALUES (?, ?)", ("Ana", 9.5))
print(conexao.execute("SELECT * FROM alunos").fetchall())  # [('Ana', 9.5)]

Resumo em 30 segundos:

  • sqlite3 é da biblioteca padrão: basta import sqlite3.
  • Use sempre ? (ou :nome) para valores. Nunca f-string: isso abre brecha para SQL injection.
  • INSERT, UPDATE e DELETE só ficam gravados após commit().
  • with conexao: faz commit ou rollback, mas não fecha a conexão.
  • conexao.row_factory = sqlite3.Row permite linha["nome"] e dict(linha).

Salve salve Pythonista!

Chega uma hora em que guardar dados em listas, dicionários ou arquivos de texto não basta: você precisa buscar, filtrar, atualizar e garantir que nada se perca no meio do caminho. Para isso existe o banco de dados, e o jeito mais simples de começar em Python é o SQLite.

Neste guia você vai aprender a conectar, criar tabelas, inserir dados com segurança (e ver na prática o estrago que uma f-string pode causar), consultar com fetchone() e fetchall(), receber linhas como dicionário, atualizar, apagar e controlar transações com commit(), rollback() e with. No final há erros comuns com a mensagem real e exercícios resolvidos.

Todos os exemplos foram executados no Python 3.13 (com a biblioteca SQLite 3.50.4 embutida). Onde o comportamento muda entre versões, o texto avisa.

Então… Bora pro post! :rocket:

Vá Direto ao Assunto…

O que é o SQLite e o módulo sqlite3

O que é o SQLite? SQLite é um banco de dados relacional que guarda tudo em um único arquivo no disco, sem servidor, usuário ou senha. O Python acessa esse banco pelo módulo sqlite3, que faz parte da biblioteca padrão. Ele entende SQL comum (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) e é ideal para scripts, aplicações desktop, protótipos, testes e sites com pouco tráfego de escrita.

O SQLite está em todo lugar: navegadores, celulares e aplicativos usam para guardar dados locais. O Django, inclusive, cria um arquivo db.sqlite3 como banco padrão de todo projeto novo, e é nele que o comando migrate do Django cria as tabelas.

Como o módulo já vem com o Python, não há nada a instalar. Para ver a versão da biblioteca SQLite embutida no seu Python:

1
2
3
import sqlite3

print(sqlite3.sqlite_version)
1
3.50.4

A referência completa está na documentação oficial do sqlite3.

Conectar ao banco e criar uma tabela

O fluxo de trabalho tem sempre os mesmos passos: conectar, obter um cursor, executar SQL, confirmar (commit) e fechar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sqlite3

conexao = sqlite3.connect("loja.db")
cursor = conexao.cursor()

cursor.execute("""
    CREATE TABLE IF NOT EXISTS produtos (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        nome TEXT NOT NULL UNIQUE,
        preco REAL NOT NULL,
        estoque INTEGER DEFAULT 0
    )
""")
conexao.commit()

O que acontece aqui:

  • sqlite3.connect("loja.db") abre o arquivo loja.db, criando-o se ele não existir. O caminho segue as mesmas regras de qualquer arquivo em Python (relativo à pasta onde o script roda), como vimos em como manipular arquivos utilizando Python.
  • O cursor é o objeto que executa comandos e percorre resultados. A conexão também tem um atalho conexao.execute(), que cria o cursor para você.
  • IF NOT EXISTS evita erro ao rodar o script de novo.
  • INTEGER PRIMARY KEY gera o id automaticamente; UNIQUE impede dois produtos com o mesmo nome.

Os tipos do SQLite são poucos: INTEGER, REAL (número com casas decimais), TEXT, BLOB (bytes) e NULL. Eles viram int, float, str, bytes e None no Python.

Inserir dados com placeholders (?)

Para inserir valores, escreva ? no lugar de cada valor e passe os dados em uma tupla no segundo argumento do execute():

1
2
3
4
5
6
cursor.execute(
    "INSERT INTO produtos (nome, preco, estoque) VALUES (?, ?, ?)",
    ("Teclado", 199.9, 15),
)
conexao.commit()
print(cursor.lastrowid, cursor.rowcount)
1
1 1

cursor.lastrowid traz o id gerado para a linha inserida, e cursor.rowcount diz quantas linhas o comando afetou.

Também existe o estilo nomeado, com :nome no SQL e um dicionário nos dados. É ótimo quando os valores já estão em um dict:

1
2
3
4
5
6
7
produto = {"nome": "Mouse", "preco": 89.9, "estoque": 40}
cursor.execute(
    "INSERT INTO produtos (nome, preco, estoque) VALUES (:nome, :preco, :estoque)",
    produto,
)
conexao.commit()
print(cursor.lastrowid)
1
2

Não misture os estilos: a partir do Python 3.14, passar uma tupla para placeholders nomeados gera sqlite3.ProgrammingError (no 3.12 e 3.13 era só um aviso de depreciação).

SQL injection: o perigo de montar SQL com f-string

É tentador escrever f"SELECT ... WHERE nome = '{nome}'". Não faça isso com valores que vêm de fora (formulário, input(), API, arquivo). Veja um login montado com f-string, em um banco de teste separado (teste) para não misturar com a loja:

1
2
3
4
5
6
7
8
9
10
11
12
import sqlite3

teste = sqlite3.connect(":memory:")
teste.execute("CREATE TABLE usuarios (id INTEGER PRIMARY KEY, nome TEXT, senha TEXT)")
teste.execute("INSERT INTO usuarios (nome, senha) VALUES ('admin', 'Adm!n2026')")

nome = "admin' --"  # digitado pelo "usuário"
senha = "qualquer"

sql = f"SELECT nome FROM usuarios WHERE nome = '{nome}' AND senha = '{senha}'"
print(sql)
print(teste.execute(sql).fetchall())
1
2
SELECT nome FROM usuarios WHERE nome = 'admin' --' AND senha = 'qualquer'
[('admin',)]

O apóstrofo digitado fechou a string e o -- transformou o resto da consulta em comentário SQL. A verificação de senha simplesmente sumiu, e o atacante entrou como admin sem saber a senha. Isso é SQL injection, uma das falhas de segurança mais antigas e ainda mais exploradas da web.

Agora a mesma consulta com placeholders:

1
2
sql = "SELECT nome FROM usuarios WHERE nome = ? AND senha = ?"
print(teste.execute(sql, (nome, senha)).fetchall())
1
[]

Com ?, o valor é enviado separado do comando e tratado sempre como dado: o banco procurou um usuário chamado literalmente admin' --, que não existe. A documentação do sqlite3 é explícita: não use operações de string do Python para montar consultas.

Um detalhe: o execute() recusa mais de um comando por chamada (sqlite3.ProgrammingError: You can only execute one statement at a time.), o que barra o clássico '; DROP TABLE usuarios; --. Mas, como o exemplo mostrou, não é preciso um segundo comando para causar estrago. A única defesa confiável são os placeholders.

Placeholders servem para valores. Nomes de tabela e de coluna não podem ser ? (SELECT * FROM ? dá erro de sintaxe). Se precisar de um nome dinâmico, valide-o contra uma lista fixa de nomes permitidos antes de montar o SQL.

Inserir várias linhas com executemany

Para inserir uma lista de registros, use executemany(): ele executa o mesmo comando uma vez para cada tupla da lista.

1
2
3
4
5
6
7
8
9
novos = [
    ("Monitor", 1299.0, 5),
    ("Headset", 349.9, 12),
    ("Webcam", 259.0, 0),
    ("Cabo HDMI", 39.9, 100),
]
cursor.executemany("INSERT INTO produtos (nome, preco, estoque) VALUES (?, ?, ?)", novos)
conexao.commit()
print(cursor.rowcount)
1
4

Além de mais curto que um for com execute(), é mais rápido, porque o comando é preparado uma vez só. Se a lista vier de uma lista de dicionários, use placeholders nomeados e passe a lista de dicts direto.

Está curtindo esse conteúdo? :thumbsup:

Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?

Sua assinatura não pôde ser validada.
Você fez sua assinatura com sucesso.

Assine as PyDicas e receba 30 dias do melhor conteúdo Python na sua Caixa de Entrada: direto e sem enrolação!

Consultar dados: fetchone, fetchall e fetchmany

Depois de um SELECT, o cursor guarda o resultado e você escolhe como ler:

1
2
3
cursor.execute("SELECT id, nome, preco FROM produtos WHERE preco > ? ORDER BY preco", (100,))
for linha in cursor.fetchall():
    print(linha)
1
2
3
4
(1, 'Teclado', 199.9)
(5, 'Webcam', 259.0)
(4, 'Headset', 349.9)
(3, 'Monitor', 1299.0)

Repare no (100,): mesmo com um único valor, os parâmetros precisam ser uma tupla, e tupla de um elemento exige a vírgula.

Para buscar um registro só, use fetchone(). Ele devolve uma tupla, ou None se nada for encontrado:

1
2
3
4
5
6
7
8
9
cursor.execute("SELECT nome, preco FROM produtos WHERE id = ?", (1,))
print(cursor.fetchone())

cursor.execute("SELECT nome, preco FROM produtos WHERE id = ?", (999,))
print(cursor.fetchone())

cursor.execute("SELECT COUNT(*), MAX(preco) FROM produtos")
total, mais_caro = cursor.fetchone()
print(total, mais_caro)
1
2
3
('Teclado', 199.9)
None
6 1299.0

Para resultados grandes, fetchmany(n) lê em lotes, e iterar o cursor direto lê uma linha por vez sem montar a lista inteira na memória:

1
2
3
4
5
6
cursor.execute("SELECT nome FROM produtos ORDER BY id")
print(cursor.fetchmany(2))
print(cursor.fetchmany(2))

for (nome,) in conexao.execute("SELECT nome FROM produtos WHERE estoque >= ?", (40,)):
    print(nome)
1
2
3
4
[('Teclado',), ('Mouse',)]
[('Monitor',), ('Headset',)]
Mouse
Cabo HDMI
Método Devolve Quando usar
fetchone() Uma tupla ou None Buscar por id, contar, MAX, SUM, checar se existe
fetchall() Lista com todas as linhas Resultados pequenos que você vai usar inteiros
fetchmany(n) Lista com até n linhas Processar resultados grandes em lotes
for linha in cursor: Uma linha por volta Percorrer resultados grandes sem carregar tudo

Linhas como dicionário com row_factory = sqlite3.Row

Acessar linha[2] funciona, mas fica ilegível: qual coluna era a 2? Defina row_factory = sqlite3.Row na conexão e as linhas passam a aceitar acesso por nome:

1
2
3
4
5
6
7
8
9
10
11
12
conexao.row_factory = sqlite3.Row
cursor = conexao.cursor()

cursor.execute("SELECT * FROM produtos WHERE nome = ?", ("Mouse",))
produto = cursor.fetchone()

print(produto["nome"], produto["preco"], produto[0])
print(produto.keys())
print(dict(produto))

sem_estoque = [dict(p) for p in conexao.execute("SELECT nome, estoque FROM produtos WHERE estoque = 0")]
print(sem_estoque)
1
2
3
4
Mouse 89.9 2
['id', 'nome', 'preco', 'estoque']
{'id': 2, 'nome': 'Mouse', 'preco': 89.9, 'estoque': 40}
[{'nome': 'Webcam', 'estoque': 0}]

O row_factory vale para os cursores criados depois da atribuição, por isso criamos um cursor novo na linha 2. Um sqlite3.Row aceita índice e nome (sem diferenciar maiúsculas), e dict(linha) o converte em um dicionário comum, pronto para virar JSON em uma API.

UPDATE e DELETE

Atualizar e remover seguem o mesmo padrão, sempre com WHERE e placeholders. O rowcount informa quantas linhas foram afetadas:

1
2
3
4
5
6
7
8
9
10
cursor.execute("UPDATE produtos SET preco = ROUND(preco * 0.9, 2) WHERE preco > ?", (1000,))
print("Atualizados:", cursor.rowcount)

cursor.execute("DELETE FROM produtos WHERE estoque = ?", (0,))
print("Removidos:", cursor.rowcount)
conexao.commit()

for p in conexao.execute("SELECT nome, preco FROM produtos ORDER BY id"):
    print(tuple(p))
conexao.close()
1
2
3
4
5
6
7
Atualizados: 1
Removidos: 1
('Teclado', 199.9)
('Mouse', 89.9)
('Monitor', 1169.1)
('Headset', 349.9)
('Cabo HDMI', 39.9)

O Monitor ganhou 10% de desconto e a Webcam (estoque zero) foi removida. Duas dicas: um UPDATE ou DELETE sem WHERE afeta a tabela inteira, e um rowcount igual a 0 significa que nenhuma linha casou com o filtro (não é erro, então confira se precisar).

commit, rollback e o context manager da conexão

Uma transação agrupa comandos que precisam acontecer juntos: ou todos são gravados, ou nenhum. No modo padrão, o sqlite3 abre uma transação automaticamente antes de INSERT, UPDATE, DELETE e REPLACE, e ela só é gravada com commit(). Veja o que acontece quando o commit é esquecido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sqlite3

conexao = sqlite3.connect("banco.db")
conexao.execute("""
    CREATE TABLE IF NOT EXISTS contas (
        titular TEXT PRIMARY KEY,
        saldo REAL NOT NULL CHECK (saldo >= 0)
    )
""")
conexao.executemany("INSERT INTO contas VALUES (?, ?)", [("Ana", 500.0), ("Bruno", 100.0)])
conexao.commit()

conexao.execute("INSERT INTO contas VALUES (?, ?)", ("Carla", 50.0))  # sem commit
conexao.close()

conexao = sqlite3.connect("banco.db")
print(conexao.execute("SELECT titular FROM contas").fetchall())
conexao.close()
1
[('Ana',), ('Bruno',)]

A Carla sumiu: close() não faz commit, e a alteração pendente foi descartada.

O rollback() faz o contrário do commit: desfaz tudo o que foi feito desde o início da transação. O exemplo clássico é a transferência bancária. A regra CHECK (saldo >= 0) impede saldo negativo, então tirar R$ 600 da Ana (que tem 500) falha. Sem rollback, o Bruno ficaria com o dinheiro que nunca saiu da conta da Ana:

1
2
3
4
5
6
7
8
9
10
conexao = sqlite3.connect("banco.db")
try:
    conexao.execute("UPDATE contas SET saldo = saldo + ? WHERE titular = ?", (600, "Bruno"))
    conexao.execute("UPDATE contas SET saldo = saldo - ? WHERE titular = ?", (600, "Ana"))
    conexao.commit()
except sqlite3.IntegrityError as erro:
    conexao.rollback()
    print("Transferência cancelada:", erro)

print(conexao.execute("SELECT * FROM contas").fetchall())
1
2
Transferência cancelada: CHECK constraint failed: saldo >= 0
[('Ana', 500.0), ('Bruno', 100.0)]

O crédito do Bruno (primeiro UPDATE) foi desfeito junto. Para revisar try/except, veja o nosso guia de tratamento de erros e exceções no Python.

with conexao: commit e rollback automáticos

Escrever try/commit/rollback toda vez cansa. A conexão funciona como context manager: se o bloco with terminar sem erro, ela faz commit; se sair com exceção, faz rollback (e a exceção continua subindo).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def transferir(conexao, origem, destino, valor):
    with conexao:  # commit se der certo, rollback se der erro
        conexao.execute("UPDATE contas SET saldo = saldo + ? WHERE titular = ?", (valor, destino))
        conexao.execute("UPDATE contas SET saldo = saldo - ? WHERE titular = ?", (valor, origem))

transferir(conexao, "Ana", "Bruno", 200)
print(conexao.execute("SELECT * FROM contas").fetchall())

try:
    transferir(conexao, "Ana", "Bruno", 1000)
except sqlite3.IntegrityError as erro:
    print("Erro:", erro)
print(conexao.execute("SELECT * FROM contas").fetchall())
conexao.close()
1
2
3
[('Ana', 300.0), ('Bruno', 300.0)]
Erro: CHECK constraint failed: saldo >= 0
[('Ana', 300.0), ('Bruno', 300.0)]

Atenção à pegadinha: o with conexao: não fecha a conexão, diferente do with open() de arquivos. Por isso ainda conseguimos consultar depois dos blocos. Para fechar automaticamente, combine com contextlib.closing():

1
2
3
4
5
6
from contextlib import closing

with closing(sqlite3.connect("banco.db")) as conexao:
    with conexao:
        conexao.execute("UPDATE contas SET saldo = saldo + 1 WHERE titular = ?", ("Ana",))
    print(conexao.execute("SELECT saldo FROM contas WHERE titular = ?", ("Ana",)).fetchone())
1
(301.0,)

O with de fora fecha a conexão; o de dentro controla a transação. Fechar importa: desde o Python 3.13, uma conexão que nunca é fechada emite ResourceWarning.

Banco de dados em memória (:memory:)

Passando ":memory:" no lugar do nome do arquivo, o banco é criado na RAM. Ele é muito rápido e desaparece quando a conexão fecha:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sqlite3

memoria = sqlite3.connect(":memory:")
memoria.execute("CREATE TABLE cache (chave TEXT PRIMARY KEY, valor TEXT)")
memoria.execute("INSERT INTO cache VALUES (?, ?)", ("usuario:1", "Ana"))
print(memoria.execute("SELECT valor FROM cache WHERE chave = ?", ("usuario:1",)).fetchone())
memoria.close()

memoria = sqlite3.connect(":memory:")
try:
    memoria.execute("SELECT * FROM cache")
except sqlite3.OperationalError as erro:
    print(erro)
memoria.close()
1
2
('Ana',)
no such table: cache

A segunda conexão abriu um banco novo e vazio. Use :memory: em testes automatizados (cada teste começa com um banco limpo), para experimentar SQL e para processar dados temporários.

Saber escolher entre SQLite, um arquivo simples e um banco mais robusto vem de já ter apanhado num projeto real. Na Jornada Python esse aprendizado acontece na prática, com PostgreSQL incluso na grade:

Quando usar SQLite, arquivo ou outro banco

Situação Melhor escolha Por quê
Script, app desktop, protótipo, dados locais SQLite (sqlite3) Zero instalação, um único arquivo
Testes automatizados SQLite com :memory: Banco limpo e rápido a cada teste
Guardar configuração ou lista pequena Arquivo JSON ou CSV Não precisa de consultas nem transações
Site com muitos usuários escrevendo ao mesmo tempo PostgreSQL ou MySQL O SQLite permite um escritor por vez
Projeto Django SQLite no desenvolvimento, PostgreSQL em produção É o padrão do startproject; em produção, escala melhor

Se o seu projeto crescer, veja como conectar o Django ao PostgreSQL.

Erros comuns

Estes são os erros que mais aparecem com quem começa a usar o sqlite3, com a última linha real do traceback.

Passar uma string em vez de tupla (ProgrammingError)

1
cursor.execute("INSERT INTO clientes (nome) VALUES (?)", ("Maria"))
1
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 5 supplied.

("Maria") é só a string "Maria" entre parênteses, e cada uma das 5 letras virou um parâmetro. Correção: tupla de um elemento leva vírgula, ("Maria",).

Consultar uma tabela que não existe (OperationalError)

1
2
conexao = sqlite3.connect("loja.db")
conexao.execute("SELECT * FROM produto")
1
sqlite3.OperationalError: no such table: produto

Causas típicas: nome errado (produto em vez de produtos), tabela ainda não criada, ou o script rodando em outra pasta, o que faz o connect() criar um loja.db novo e vazio. Correção: confira o nome, use CREATE TABLE IF NOT EXISTS e, se preciso, um caminho absoluto para o arquivo.

Inserir um valor repetido em coluna UNIQUE (IntegrityError)

Rodar de novo o script que insere ("Teclado", 199.9, 15) na tabela produtos (coluna nome é UNIQUE):

1
sqlite3.IntegrityError: UNIQUE constraint failed: produtos.nome

Correção: trate sqlite3.IntegrityError com try/except, verifique antes com um SELECT, ou use INSERT OR IGNORE quando repetir não for problema.

Número de valores diferente do número de ? (ProgrammingError)

1
2
conexao.execute("CREATE TABLE t (a TEXT, b TEXT)")
conexao.execute("INSERT INTO t VALUES (?, ?)", ("x",))
1
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 2, and there are 1 supplied.

Correção: a tupla precisa ter exatamente um valor para cada ?.

Usar a conexão depois de fechar (ProgrammingError)

1
2
3
conexao = sqlite3.connect("loja.db")
conexao.close()
conexao.execute("SELECT * FROM produtos")
1
sqlite3.ProgrammingError: Cannot operate on a closed database.

Correção: faça todas as operações antes do close(). Com contextlib.closing(), mantenha o código que usa a conexão dentro do bloco with.

Exercícios resolvidos

Tente resolver cada exercício antes de abrir a solução. Todos os códigos foram executados no Python 3.13 e as saídas conferidas. Para mais prática, veja a nossa página de exercícios de Python resolvidos.

Exercício 1. Crie um banco em memória com a tabela livros (titulo TEXT, autor TEXT, ano INTEGER), insira os 4 livros abaixo com uma única chamada e mostre quantos registros existem.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
import sqlite3

conexao = sqlite3.connect(":memory:")
conexao.execute("CREATE TABLE livros (titulo TEXT, autor TEXT, ano INTEGER)")
livros = [
    ("Dom Casmurro", "Machado de Assis", 1899),
    ("Aprendendo Python", "Ana Souza", 2015),
    ("O Alquimista", "Paulo Coelho", 1988),
    ("Python Avançado 2a ed.", "Ana Souza", 2022),
]
conexao.executemany("INSERT INTO livros VALUES (?, ?, ?)", livros)
conexao.commit()
print(conexao.execute("SELECT COUNT(*) FROM livros").fetchone()[0])

Saída: 4

O executemany() roda o INSERT uma vez por tupla. O fetchone() devolve a tupla (4,), e o [0] pega o número.

Exercício 2. Usando a tabela do Exercício 1, mostre os livros publicados depois de 2000, do mais antigo para o mais novo, no formato ano - título. O ano mínimo deve vir de uma variável.

Ver solução
1
2
3
4
5
6
ano_minimo = 2000
cursor = conexao.execute(
    "SELECT titulo, ano FROM livros WHERE ano > ? ORDER BY ano", (ano_minimo,)
)
for titulo, ano in cursor:
    print(f"{ano} - {titulo}")

Saída:

1
2
2015 - Aprendendo Python
2022 - Python Avançado 2a ed.

A variável entra pelo placeholder ?, e o for desempacota cada tupla em titulo e ano.

Exercício 3. Ainda na tabela livros, devolva os livros da autora “Ana Souza” como uma lista de dicionários com as chaves titulo e autor.

Ver solução
1
2
3
4
5
6
conexao.row_factory = sqlite3.Row
linhas = conexao.execute(
    "SELECT titulo, autor FROM livros WHERE autor = ?", ("Ana Souza",)
).fetchall()
resultado = [dict(linha) for linha in linhas]
print(resultado)

Saída:

1
[{'titulo': 'Aprendendo Python', 'autor': 'Ana Souza'}, {'titulo': 'Python Avançado 2a ed.', 'autor': 'Ana Souza'}]

Com sqlite3.Row, cada linha vira um dicionário com dict(linha), e as chaves são os nomes das colunas do SELECT.

Exercício 4. Escreva buscar_por_trecho(conexao, trecho) que devolva os títulos que contêm o trecho informado (busca parcial, sem diferenciar maiúsculas), usando placeholder. Teste com "python" e "java".

Ver solução
1
2
3
4
5
6
def buscar_por_trecho(conexao, trecho):
    sql = "SELECT titulo FROM livros WHERE titulo LIKE ? ORDER BY titulo"
    return [linha[0] for linha in conexao.execute(sql, (f"%{trecho}%",))]

print(buscar_por_trecho(conexao, "python"))
print(buscar_por_trecho(conexao, "java"))

Saída:

1
2
['Aprendendo Python', 'Python Avançado 2a ed.']
[]

Os % do LIKE vão no valor, não no SQL: a f-string aqui monta só o dado ("%python%"), e o placeholder continua protegendo a consulta. No SQLite, o LIKE não diferencia maiúsculas para letras sem acento.

Exercício 5. Escreva atualizar_ano(conexao, titulo, novo_ano) que atualize o ano de um livro dentro de um with conexao: e devolva quantas linhas foram alteradas. Teste com um título que existe e com um que não existe.

Ver solução
1
2
3
4
5
6
7
8
9
def atualizar_ano(conexao, titulo, novo_ano):
    with conexao:
        cursor = conexao.execute(
            "UPDATE livros SET ano = ? WHERE titulo = ?", (novo_ano, titulo)
        )
    return cursor.rowcount

print(atualizar_ano(conexao, "O Alquimista", 1987))
print(atualizar_ano(conexao, "Livro que não existe", 2000))

Saída:

1
2
1
0

O with faz o commit ao sair do bloco. Um rowcount de 0 indica que o WHERE não encontrou nada, sem gerar exceção.

Exercício 6. Crie a tabela usuarios (email TEXT UNIQUE) e tente inserir [email protected], [email protected] e [email protected] de novo, em um único lote dentro de with conexao:. Mostre a mensagem de erro e quantos usuários ficaram gravados.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
conexao.execute("CREATE TABLE usuarios (email TEXT UNIQUE)")
conexao.commit()

novos = [("[email protected]",), ("[email protected]",), ("[email protected]",)]
try:
    with conexao:
        conexao.executemany("INSERT INTO usuarios VALUES (?)", novos)
except sqlite3.IntegrityError as erro:
    print("Lote cancelado:", erro)

print(conexao.execute("SELECT COUNT(*) FROM usuarios").fetchone()[0])

Saída:

1
2
Lote cancelado: UNIQUE constraint failed: usuarios.email
0

O terceiro e-mail violou o UNIQUE, o with fez rollback e nenhum dos três ficou gravado, nem os dois primeiros. É o “tudo ou nada” da transação.

Exercício 7 (estilo prova). A variável nome vem de um formulário web. Qual das linhas abaixo consulta o banco de forma segura contra SQL injection?

a) cursor.execute(f"SELECT * FROM clientes WHERE nome = '{nome}'")
b) cursor.execute("SELECT * FROM clientes WHERE nome = '%s'" % nome)
c) cursor.execute("SELECT * FROM clientes WHERE nome = '" + nome + "'")
d) cursor.execute("SELECT * FROM clientes WHERE nome = ?", (nome,))

Ver solução

Resposta: d. As alternativas a, b e c colam o texto do usuário dentro do SQL (f-string, formatação com % e concatenação são a mesma coisa nesse sentido), então um valor como x' OR '1'='1 altera a consulta. Na d, o valor vai separado pelo placeholder ? e é sempre tratado como dado.

Exercício 8 (estilo prova). O arquivo notas.db não existe antes da execução. O que o código abaixo imprime?

1
2
3
4
5
6
7
8
9
10
import sqlite3

conexao = sqlite3.connect("notas.db")
conexao.execute("CREATE TABLE IF NOT EXISTS notas (valor REAL)")
conexao.execute("INSERT INTO notas VALUES (?)", (8.5,))
conexao.close()

conexao = sqlite3.connect("notas.db")
print(conexao.execute("SELECT COUNT(*) FROM notas").fetchone()[0])
conexao.close()

a) 1
b) 0
c) sqlite3.OperationalError: no such table: notas
d) 8.5

Ver solução

Saída: 0

Resposta: b. O CREATE TABLE foi gravado (no modo padrão, o sqlite3 só abre transação automática para INSERT, UPDATE, DELETE e REPLACE), então a tabela existe e não ocorre o erro da alternativa c. Já o INSERT ficou pendente e o close() não faz commit, então a nota foi descartada e a contagem é zero.

Quer praticar bancos de dados em projetos completos, do script ao sistema web? Conheça o nosso curso de Python completo.

Conclusão

Neste guia de SQLite com Python, você aprendeu:

✅ Conectar e criar tabelas - sqlite3.connect(), cursor e CREATE TABLE IF NOT EXISTS
✅ Placeholders - ? e :nome no lugar de f-string, e por que isso evita SQL injection
✅ executemany - inserir listas de registros de uma vez
✅ Consultas - fetchone(), fetchall(), fetchmany() e sqlite3.Row
✅ Transações - commit(), rollback() e with conexao:
✅ Banco em memória - :memory: para testes e dados temporários

Próximos passos:

Se ficou com alguma dúvida, fique à vontade para deixar um comentário no box aqui embaixo! Será um prazer te responder! :wink:

Perguntas frequentes

Como usar SQLite no Python?

O Python já vem com o módulo sqlite3 na biblioteca padrão, então não é preciso instalar nada. Importe com import sqlite3, abra o banco com conexao = sqlite3.connect('banco.db') (o arquivo é criado se não existir), execute comandos SQL com conexao.execute(), confirme as alterações com conexao.commit() e feche com conexao.close().

Por que usar ? em vez de f-string nas consultas do sqlite3?

Porque montar SQL com f-string permite SQL injection: se o valor vier do usuário, um texto como admin' -- altera a própria consulta e pode, por exemplo, liberar um login sem senha. Com placeholders (execute('SELECT * FROM usuarios WHERE nome = ?', (nome,))), o sqlite3 envia o valor separado do comando e ele é sempre tratado como dado, nunca como SQL.

Qual a diferença entre fetchone, fetchall e fetchmany?

fetchone() devolve a próxima linha do resultado como tupla, ou None se não houver mais linhas. fetchall() devolve todas as linhas restantes em uma lista. fetchmany(n) devolve uma lista com até n linhas, útil para processar resultados grandes em lotes. Também é possível percorrer o cursor diretamente com for linha in cursor:.

Preciso chamar commit() no sqlite3?

Sim, depois de INSERT, UPDATE e DELETE. No modo padrão, o sqlite3 abre uma transação automaticamente antes desses comandos, e as alterações só ficam gravadas no arquivo após conexao.commit(). Fechar a conexão com close() não faz commit: o que não foi confirmado é descartado. Usar with conexao: faz o commit automaticamente se o bloco terminar sem erro.

O with sqlite3.connect() fecha a conexão?

Não. O with conexao: controla a transação: faz commit() se o bloco terminar sem erro e rollback() se ocorrer uma exceção, mas a conexão continua aberta depois do bloco. Para fechar automaticamente, use contextlib.closing(sqlite3.connect('banco.db')) ou chame conexao.close().

Como retornar as linhas do sqlite3 como dicionário?

Defina conexao.row_factory = sqlite3.Row antes de executar a consulta. Cada linha passa a aceitar acesso por nome de coluna (linha['nome']) e por índice, e pode ser convertida em dicionário com dict(linha). Para uma lista de dicionários: [dict(l) for l in conexao.execute('SELECT ...')].

Qual o erro ao passar uma string em vez de tupla para o execute do sqlite3?

Escrever execute('... VALUES (?)', ('Maria')) gera sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 5 supplied. Os parênteses sozinhos não criam uma tupla, então o sqlite3 trata cada letra de ‘Maria’ como um parâmetro. A correção é a vírgula: ('Maria',).

Em uma questão de prova, qual comando do sqlite3 insere várias linhas de uma vez?

O método executemany(). Ele recebe o comando SQL com placeholders e uma lista de tuplas, executando o comando uma vez para cada tupla: cursor.executemany('INSERT INTO produtos VALUES (?, ?)', lista). O execute() executa um único comando, e o executescript() executa um script SQL inteiro, sem suporte a placeholders.

Começe agora sua Jornada na Programação!

Não deixe para amanhã o sucesso que você pode começar a construir hoje!

#newsletter Olá :wave: Curtiu o artigo? Então faça parte da nossa Newsletter! Privacidade Não se preocupe, respeitamos sua privacidade. Você pode se descadastrar a qualquer momento.