Consultas com Parâmetros
Na aula SQL Injection você viu o perigo de colar o texto digitado por alguém dentro do SQL. A solução — e também o jeito mais limpo de escrever consultas num programa — é a consulta com parâmetros: você deixa um espaço reservado no SQL (um ? ou um :nome) e entrega o valor por fora. O banco monta a consulta primeiro e só depois encaixa o valor, tratando-o sempre como dado, nunca como comando. É isso que os programadores chamam de prepared statement ("comando preparado"). Nesta aula você vê as três formas de espaço reservado, rodando de verdade no terminal do SQLite e em programas Python da Ana.
- O que é um espaço reservado e por que o valor "vai por fora".
- As três formas:
?(posicional),?1(numerado) e:nome(nomeado). - Como experimentar parâmetros no próprio
sqlite3, com.parameter set. - Parâmetros em Python, com um e com vários valores.
- Por que o apóstrofo de
Loja D'Águapassa a funcionar sozinho. - O que um parâmetro não pode substituir (nome de coluna, de tabela) e as formas dos outros bancos.
A ideia: o formulário com um campo em branco
Pense num formulário já impresso da Loja Aurora, com o texto fixo e um só campo para preencher:
Listar os clientes do estado: [ ]
O texto do formulário é sempre o mesmo. O que muda é só o que vai no campo: hoje PI, amanhã CE. O campo em branco é o espaço reservado; o valor que você escreve nele é o parâmetro. Ninguém que preencha o campo consegue mudar o texto impresso do formulário — e é exatamente essa separação que protege a consulta.
No SQL, o espaço reservado é um ? (ou um :nome). Repare que ele fica sem aspas, mesmo quando o valor é texto:
SELECT nome, cidade FROM clientes WHERE estado = ?;
↑
o campo em branco: o valor entra por fora
1. Experimentando no próprio sqlite3
Antes de ir para um programa, dá para sentir como funciona dentro do próprio terminal do sqlite3 (aula O Terminal do SQLite). O comando de ponto .parameter set guarda um valor para um espaço reservado nomeado; depois, toda consulta que usar aquele nome recebe o valor:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> .mode box sqlite> .parameter set :uf 'PI' sqlite> SELECT nome, cidade FROM clientes WHERE estado = :uf LIMIT 3; ┌────────────────────┬──────────┐ │ nome │ cidade │ ├────────────────────┼──────────┤ │ José Ribamar Silva │ Teresina │ │ Maria Clara Mendes │ Teresina │ │ Conceição Prado │ Teresina │ └────────────────────┴──────────┘ sqlite> .parameter list :uf 'PI' sqlite> .parameter unset :uf
- .parameter set :uf 'PI'
- Guarda o valor
PIpara o espaço reservado chamado:uf. É um comando de ponto, só do programasqlite3(não é SQL). - WHERE estado = :uf
- Na consulta, o
:ufé o espaço reservado. Osqlite3encaixa oPIguardado na hora de rodar. - .parameter list
- Mostra os valores guardados.
- .parameter unset :uf
- Esquece o valor.
Os comandos de ponto (.parameter, .mode…) existem só no programa sqlite3. No editor do guia (as caixas "Experimente") não há como guardar um valor para um espaço reservado; por isso, lá, um ? ou um :uf fica vazio — você vai ver isso em Quando dá errado. Para experimentar parâmetros de verdade, use o terminal ou um programa, como nas próximas seções.
2. Parâmetros em Python: o ? posicional
Num programa, o valor costuma vir de fora (de um formulário, da linha de comando). A Ana usa a biblioteca sqlite3 do Python. A forma mais comum de espaço reservado é o ?, e o valor vai num segundo argumento, numa lista à parte:
import sqlite3
con = sqlite3.connect("loja.db")
uf = "PI"
sql = "SELECT nome, cidade FROM clientes WHERE estado = ? LIMIT 3"
for linha in con.execute(sql, (uf,)):
print(linha)
- estado = ?
- O espaço reservado. O SQL é fixo: não muda com o valor. Sem aspas em volta do
?. - con.execute(sql, (uf,))
- O valor vai no segundo argumento, numa tupla (uma lista de valores). O
(uf,)tem uma vírgula porque é uma tupla de um item só. - a ordem
- O primeiro
?recebe o primeiro valor da lista, o segundo?o segundo, e assim por diante. Por isso o?se chama posicional.
$ python3 um.py
('José Ribamar Silva', 'Teresina')
('Maria Clara Mendes', 'Teresina')
('Conceição Prado', 'Teresina')
Vários valores
Quando a consulta precisa de mais de um valor, você põe mais de um ? e passa a lista na mesma ordem. Aqui, a Ana procura produtos numa faixa de preço:
import sqlite3
con = sqlite3.connect("loja.db")
barato, caro = 20, 25
sql = "SELECT nome, preco FROM produtos WHERE preco >= ? AND preco <= ? ORDER BY preco"
for linha in con.execute(sql, (barato, caro)):
print(linha)
$ python3 faixa.py
('Garrafa Squeeze 700ml', 21.75)
('Avental de Cozinha', 22.9)
('Café Torrado 500g', 23.85)
('Cabo HDMI 2 metros', 24.75)
O primeiro ? virou 20; o segundo, 25. Repare que, para números, você não põe aspas em lugar nenhum — nem no SQL, nem no valor.
3. O :nome nomeado
Com muitos ?, é fácil se perder na ordem. A forma nomeada resolve isso: cada espaço reservado ganha um nome (:uf, :lim), e você passa os valores com esses nomes, numa estrutura chave→valor (em Python, um dicionário). A ordem deixa de importar:
import sqlite3
con = sqlite3.connect("loja.db")
valores = {"uf": "CE", "lim": 2}
sql = "SELECT nome, cidade FROM clientes WHERE estado = :uf LIMIT :lim"
for linha in con.execute(sql, valores):
print(linha)
$ python3 nomeado.py
('Carlos Eduardo Lima', 'Fortaleza')
('Fernanda Carvalho', 'Fortaleza')
- :uf e :lim
- Espaços reservados nomeados: dois-pontos seguidos de um nome.
- {"uf": "CE", "lim": 2}
- Os valores por nome. No dicionário o nome vai sem os dois-pontos (
"uf"), e no SQL com (:uf).
?1 numerado
O SQLite ainda aceita uma terceira forma: ? seguido de um número — ?1, ?2… —, em que o número diz qual valor da lista usar. A documentação oficial do SQLite descreve as formas assim: ? (posicional), ?NNN (numerado, "guarda o lugar do NNN-ésimo parâmetro") e :nome, @nome, $nome (nomeados). A vantagem do ?1 é poder repetir o mesmo valor em dois lugares da consulta (WHERE a = ?1 OR b = ?1) sem passá-lo duas vezes. Na prática, a maioria dos programas usa só o ? ou o :nome; a própria documentação do SQLite desaconselha o ? sem número quando há muitos, "porque é fácil errar a contagem", e sugere as formas nomeadas ou o ?NNN.
4. De quebra: o apóstrofo que passa a funcionar
Lembra do fornecedor Loja D'Água, que quebrava a busca feita por colagem (aula SQL Injection)? Com parâmetro, o apóstrofo deixa de ser problema: como o valor vai por fora e é tratado como dado, o banco o guarda e procura certinho, sem você precisar dobrar aspa nenhuma:
import sqlite3
con = sqlite3.connect("loja.db")
alvo = "Loja D'Água"
linha = con.execute(
"SELECT id, nome, cidade FROM fornecedores WHERE nome = ?",
(alvo,),
).fetchone()
print(linha)
$ python3 nome.py (2, "Loja D'Água", 'Fortaleza')
O apóstrofo de D'Água foi tratado como uma letra qualquer do texto. É essa a diferença: no parâmetro, o valor nunca "se mistura" com o SQL. (No editor do guia, para escrever esse nome à mão, você ainda dobra a aspa — 'Loja D''Água' —, como na aula WHERE; o parâmetro é quem faz esse trabalho por você nos programas.)
5. O que um parâmetro NÃO pode substituir
Um espaço reservado guarda o lugar de um valor — um texto, um número, uma data. Ele não pode guardar o lugar de um nome de coluna, de um nome de tabela, nem de uma palavra-chave. A documentação do SQLite diz que parâmetros sem valor viram NULL; então, se você puser um ? onde deveria haver um nome de coluna, não dá erro de "proibido" — a consulta só não faz o que você queria.
Se o que varia no seu programa é a coluna da ordenação ou o nome de uma tabela, use uma lista de permitidos: compare o que veio de fora com uma lista fixa que você escreveu e recuse o resto. Nunca cole um nome vindo de fora.
import sqlite3
con = sqlite3.connect("loja.db")
coluna = "preco" # viria de fora
permitidas = {"nome", "preco", "estoque"}
if coluna not in permitidas: # a lista de permitidos
raise ValueError("coluna não permitida")
sql = f"SELECT nome, preco FROM produtos ORDER BY {coluna} LIMIT 3"
for linha in con.execute(sql):
print(linha)
Aqui o nome da coluna só entra no SQL depois de passar pela lista permitidas. O valor de busca continuaria indo por parâmetro; a lista de permitidos é só para a parte que o parâmetro não cobre.
6. As formas, lado a lado
| Forma | Como escreve no SQL | Como passa o valor (Python) | Quando usar |
|---|---|---|---|
| Posicional | WHERE estado = ? | uma tupla, na ordem: (uf,) | Poucos valores; o mais comum. |
| Numerado | WHERE estado = ?1 | pela posição; repete o ?1 para reusar o valor | Quando o mesmo valor aparece em dois lugares. |
| Nomeado | WHERE estado = :uf | um dicionário: {"uf": uf} | Muitos valores; o nome deixa claro o que é cada um. |
Quando dá errado
Como o editor não tem como receber valores por fora, um ? ou um :uf fica sem valor — e, segundo a própria documentação do SQLite, um parâmetro sem valor vira NULL. A comparação estado = NULL nunca é verdadeira (aula IS NULL), então a consulta volta vazia, sem dar erro:
SELECT nome, cidade FROM clientes WHERE estado = :uf LIMIT 3;
Zero linhas. Não é um erro do seu SQL; é só que, aqui, não há quem preencha o campo. No terminal (seção 1) ou num programa (seções 2 e 3), com o valor preenchido, a mesma consulta devolve as linhas.
Num programa, se a consulta tem dois ? e você passa um valor só, a biblioteca reclama antes mesmo de ir ao banco:
$ python3 -c "import sqlite3; sqlite3.connect('loja.db').execute(
> 'SELECT nome FROM clientes WHERE estado = ? AND vip = ?', ('PI',))"
Traceback (most recent call last):
File "<string>", line 1, in <module>
import sqlite3; sqlite3.connect('loja.db').execute(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
'SELECT nome FROM clientes WHERE estado = ? AND vip = ?', ('PI',))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 2, and there are 1 supplied.
O Python mostra primeiro o caminho do erro (o traceback) e, na última linha, a mensagem. "Número incorreto de valores: o comando usa 2 e foram passados 1." Conte os ? e os valores: têm de bater.
? entre aspas
Um erro comum é escrever '?', com aspas, achando que "é texto". Aí o ? deixa de ser espaço reservado e vira a letra ponto de interrogação; não sobra nenhum espaço para o valor, e a biblioteca avisa que recebeu um valor a mais:
$ python3 -c "import sqlite3; sqlite3.connect('loja.db').execute(
> \"SELECT nome FROM clientes WHERE estado = '?'\", ('PI',))"
Traceback (most recent call last):
File "<string>", line 1, in <module>
import sqlite3; sqlite3.connect('loja.db').execute(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
"SELECT nome FROM clientes WHERE estado = '?'", ('PI',))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 0, and there are 1 supplied.
"O comando usa 0 espaços reservados e foi passado 1 valor." A regra: o espaço reservado vai sem aspas, mesmo para texto. Quem cuida das aspas é a biblioteca.
Nos outros bancos
- PostgreSQL usa valores numerados com cifrão:
$1,$2… A documentação oficial doPREPAREmostraSELECT * FROM users WHERE usrid = $1e, depois,EXECUTE … (1)para passar o valor. - PHP (PDO), que vale para MySQL, PostgreSQL e SQLite, aceita as duas formas que você viu aqui: o
?posicional e o:nomenomeado. A documentação avisa que não se misturam as duas no mesmo comando, e que você deve ligar todo valor vindo do usuário por parâmetro, "nunca incluir o dado direto na consulta". - A regra que vale em todos: a mesma do SQLite. A documentação do PDO diz que um parâmetro representa "apenas um literal de dado completo" — nunca um nome de coluna, de tabela ou um pedaço de palavra-chave. Para isso, lista de permitidos.
Referência rápida
| Escrita | O que faz |
|---|---|
? | Espaço reservado posicional; o valor vai por fora, na ordem. |
?1, ?2 | Espaço reservado numerado; o número diz qual valor usar (e dá para repetir). |
:nome | Espaço reservado nomeado; o valor vai por nome, a ordem não importa. |
.parameter set :nome valor | No sqlite3, guarda um valor para um espaço reservado nomeado. |
| Lista de permitidos | Para nome de coluna/tabela, que o parâmetro não cobre. |
Exercícios
1. Por que o espaço reservado (? ou :nome) protege contra SQL Injection?
2. No SQL, como você escreve o espaço reservado para um valor de texto?
3. O que um parâmetro não pode substituir?
4. O espaço reservado nomeado
Complete a consulta para procurar clientes de um estado passado pelo nome uf:
Experimente: a consulta que o parâmetro monta
No editor não dá para preencher um parâmetro, então aqui você escreve o resultado da "montagem": a consulta com o valor já no lugar. É o que o banco executa depois de encaixar o parâmetro.
Missão: o programa da seção 2 usou WHERE preco >= ? AND preco <= ? com os valores 20 e 25. Escreva a consulta com esses dois valores já no lugar dos ?, ordenando por preco. Devem vir 4 produtos.
SELECT nome, preco FROM produtos WHERE preco >= ? AND preco <= ? ORDER BY preco;
SELECT nome, preco FROM produtos WHERE preco >= 20 AND preco <= 25 ORDER BY preco;
Missão final
Missão: o programa da seção 1 procurou os clientes do estado guardado em :uf, que era PI. Escreva a consulta com o valor PI já no lugar do :uf (não esqueça as aspas simples: é texto). Devem vir 14 clientes.
SELECT nome, cidade FROM clientes WHERE estado = :uf;
SELECT nome, cidade FROM clientes WHERE estado = 'PI';
Resumo
- Uma consulta com parâmetros deixa um espaço reservado no SQL e entrega o valor por fora; o banco trata o valor sempre como dado. É a defesa contra SQL Injection e o jeito limpo de escrever consultas num programa.
- As formas:
?(posicional),?1(numerado) e:nome(nomeado). O espaço reservado vai sem aspas, mesmo para texto. - No terminal, dá para experimentar com
.parameter set :nome valor. No editor do guia o campo fica vazio (viraNULL), porque não há como preenchê-lo. - Em Python, o valor vai no segundo argumento de
execute: uma tupla para o?, um dicionário para o:nome. - O apóstrofo de
Loja D'Águafunciona sozinho num parâmetro: o valor nunca se mistura com o SQL. - Um parâmetro guarda o lugar de um valor, nunca de um nome de coluna/tabela; para isso, lista de permitidos. Nos outros bancos:
$1no PostgreSQL;?e:nomeno PDO.