Bind é como os valores entram em um prepared statement
Um prepared statement é um SQL cheio de buracos. O bind é o ato de preencher esses buracos com valores — de forma segura, um por um, através da API do driver, em vez de sair concatenando strings.
A receita é sempre a mesma: você escreve o SQL com placeholders e depois passa os valores separadamente.
No CLI dá pra mostrar SQL, mas não dá pra demonstrar bind de parâmetros sqlite de verdade (o shell não tem código de aplicação por trás). Ainda assim, o SQL acima é exatamente o que sua aplicação envia pro banco. As marcas ? são placeholders. Seu driver — sqlite3 no Python, better-sqlite3 no Node, rusqlite no Rust — preenche cada um deles através de uma chamada bind separada.
O modelo mental é o seguinte: o SQL é a receita, os valores que você faz bind são os ingredientes. Eles nunca se misturam diretamente.
Placeholders posicionais: ?
O placeholder mais simples no sqlite é o ?. Cada um casa com o próximo valor que você fizer bind, na ordem.
INSERT INTO users (name, email) VALUES (?, ?);
Em Python, fica assim:
cursor.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
("Rosa", "rosa@example.com"),
)
O primeiro ? recebe "Rosa", e o segundo, "rosa@example.com". Se você passar valores demais ou de menos, o driver dispara um erro antes mesmo de executar o statement.
Também dá pra numerar os placeholders explicitamente com ?1, ?2, ?3 — bem útil quando o mesmo valor aparece mais de uma vez:
SELECT ?1 AS saudacao, ?1 AS continua_igual;
?1 reaproveita o primeiro valor já vinculado. Sem essa numeração, você teria que fazer o bind do mesmo valor duas vezes.
Placeholders nomeados no SQLite: :nome
Quando uma query passa de dois ou três placeholders, o bind por posição vira um jogo de adivinhação. Os parâmetros nomeados resolvem esse problema:
INSERT INTO users (name, email)
VALUES (:name, :email);
In Python:
cursor.execute(
"INSERT INTO users (name, email) VALUES (:name, :email)",
{"name": "Boris", "email": "boris@example.com"},
)
A ordem das chaves no dicionário não importa — o que vale são os nomes. O SQLite também aceita @name e $name como prefixos alternativos, e todos funcionam do mesmo jeito. Mas, na prática, :name é o formato mais usado.
Os parâmetros nomeados começam a fazer diferença na hora em que você precisa de um UPDATE com cinco colunas, ou de uma query que usa o mesmo valor no WHERE e no RETURNING.
Como fazer bind de NULL no SQLite
A forma correta de inserir NULL é passar o valor nulo da sua linguagem direto pela API de bind. O driver cuida da conversão para você:
INSERT INTO users (name, email) VALUES (?, ?);
-- Bind: ("Cyrus", None) em Python
-- Bind: ["Cyrus", null] em Node
SELECT id, name, email FROM users;
None, null, nil, seja qual for o nome na sua linguagem — o driver converte para um NULL SQL de verdade. Não passe a string "NULL" no bind; isso grava o texto "NULL" com quatro caracteres. E nem pense em interpolar a palavra NULL direto no SQL — aí você joga fora todo o sentido do bind.
A mesma regra vale para números, blobs e datas: passe o valor nativo e deixe o driver cuidar do bind.
Reaproveitando um prepared statement com valores diferentes
O bind de parâmetros combina perfeitamente com prepared statements no SQLite. Você prepara uma vez e executa quantas vezes quiser, trocando só os valores. O parser trabalha uma única vez, e o banco reaproveita o plano compilado para cada conjunto de valores que você passar no bind.
INSERT INTO users (name, email) VALUES (?, ?);
-- Vincula ("Ada", "ada@example.com") -> executa
-- Vincula ("Boris", "boris@example.com") -> executa
-- Vincula ("Cyrus", NULL) -> executa
SELECT id, name, email FROM users ORDER BY id;
Boa parte dos drivers já encapsula isso num executemany (Python) ou num loop com .run() (Node). De qualquer forma, o que você economiza é o custo do parsing — pequeno por statement isolado, mas que pesa quando você está inserindo milhares de linhas.
Não misture estilos no mesmo statement
Tecnicamente, o SQLite permite usar placeholders posicionais e nomeados no mesmo statement. Não caia nessa tentação.
-- Permitido, mas é uma cilada:
INSERT INTO users (name, email) VALUES (?, :email);
O leitor acaba tendo que rastrear duas APIs de bind ao mesmo tempo, e a maioria dos drivers não lida bem com essa mistura. Escolha um estilo por statement: ? quando for um ou dois valores, :nome para o resto.
Uma armadilha comum: bind não é formatação de string
A graça toda do bind de parâmetros é justamente que os valores não passam pelo parser SQL. Compare essas duas linhas em Python:
# Errado — formatação de string:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# Certo — vinculação de parâmetros:
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
A primeira linha monta o SQL por concatenação. Se name for "'; DROP TABLE users; --", o banco simplesmente interpreta e executa o comando injetado, sem pestanejar. Já a segunda linha envia o SQL e o valor por caminhos separados — o valor entra como string, ponto final, não importa quais caracteres ele tenha. É por isso que todo tutorial insiste no bind de parâmetros sqlite: não é frescura, é uma questão do que o parser realmente enxerga.
Vamos detalhar melhor o lado da injeção na próxima página.
Outra pegadinha: identificadores não aceitam bind
Os placeholders funcionam apenas para valores — strings, números, blobs, NULLs. Eles não servem para nomes de tabela, nomes de coluna ou palavras-chave do SQL:
-- Isto NÃO faz o que você quer:
SELECT * FROM ? WHERE id = ?;
-- O primeiro ? é vinculado como uma string literal, não como um nome de tabela.
Se você realmente precisa de um nome de tabela ou coluna dinâmico (algo raro em código de aplicação), valide o valor contra uma lista de permitidos e concatene-o na SQL você mesmo — nunca direto da entrada do usuário. Para todo o resto, use bind.
Um exemplo prático de ponta a ponta
Juntando tudo — uma pequena tabela users escrita e lida usando apenas bind de parâmetros:
Em código de verdade, tanto os INSERTs quanto o SELECT usariam placeholders. Acontece que a CLI não tem uma aplicação por trás para fazer o bind, então os literais ficam ali no lugar do que o binding produziria.
A seguir: Como evitar SQL Injection
O bind de parâmetros é o mecanismo. Por que ele barra o SQL injection — e os poucos casos em que só o binding não basta — é o assunto da próxima página.
Perguntas frequentes
O que é bind de parâmetros no SQLite?
Bind de parâmetros é a forma de fornecer valores a um prepared statement separadamente do texto SQL. Você escreve um placeholder como ? ou :nome no SQL e passa o valor real pela API de bind do driver. O SQLite trata os valores vinculados como dado puro — eles nunca são interpretados como SQL.
Qual a diferença entre ? e :nome no SQLite?
? é um placeholder posicional — os valores são vinculados na ordem em que aparecem. Já :nome (assim como @nome e $nome) são placeholders nomeados — o bind é feito pelo nome, não pela posição. Os parâmetros nomeados ficam mais legíveis e fáceis de reordenar quando você tem mais de dois ou três valores.
Como faço bind de um valor NULL no SQLite?
Basta passar o valor null/None/nil da sua linguagem pela API de bind — os drivers já convertem para NULL em SQL automaticamente. Nunca escreva a string 'NULL' e nunca interpole a palavra NULL direto no texto SQL. A ideia do bind é justamente manter os valores fora do parser de SQL.
Posso misturar parâmetros posicionais e nomeados na mesma instrução?
O SQLite até permite, mas evite. Uma instrução com ? e :nome ao mesmo tempo é difícil de ler e fácil de fazer bind errado. Escolha um estilo por statement — em geral, parâmetros nomeados quando passar de dois ou três valores.