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.

Você vai aprender
  • 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'Água passa 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 PI para o espaço reservado chamado :uf. É um comando de ponto, só do programa sqlite3 (não é SQL).
WHERE estado = :uf
Na consulta, o :uf é o espaço reservado. O sqlite3 encaixa o PI guardado na hora de rodar.
.parameter list
Mostra os valores guardados.
.parameter unset :uf
Esquece o valor.
Isso é do terminal, não do editor do guia

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).
O ?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

FormaComo escreve no SQLComo passa o valor (Python)Quando usar
PosicionalWHERE estado = ?uma tupla, na ordem: (uf,)Poucos valores; o mais comum.
NumeradoWHERE estado = ?1pela posição; repete o ?1 para reusar o valorQuando o mesmo valor aparece em dois lugares.
NomeadoWHERE estado = :ufum dicionário: {"uf": uf}Muitos valores; o nome deixa claro o que é cada um.

Quando dá errado

No editor do guia, o espaço reservado fica vazio

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.

Número de valores diferente do número de espaços

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.

Pôr o ? 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

Cada biblioteca tem a sua marca de espaço reservado
  • PostgreSQL usa valores numerados com cifrão: $1, $2… A documentação oficial do PREPARE mostra SELECT * FROM users WHERE usrid = $1 e, 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 :nome nomeado. 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

EscritaO que faz
?Espaço reservado posicional; o valor vai por fora, na ordem.
?1, ?2Espaço reservado numerado; o número diz qual valor usar (e dá para repetir).
:nomeEspaço reservado nomeado; o valor vai por nome, a ordem não importa.
.parameter set :nome valorNo sqlite3, guarda um valor para um espaço reservado nomeado.
Lista de permitidosPara 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:

SELECT nome FROM clientes WHERE estado = ;

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 (vira NULL), 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'Água funciona 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: $1 no PostgreSQL; ? e :nome no PDO.