Menu

Parámetros en SQLite: ?, :name y valores seguros

Cómo funciona el binding de parámetros en SQLite: placeholders posicionales, parámetros con nombre y las reglas para pasar valores de forma segura desde tu aplicación.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

El binding es cómo los valores entran en una sentencia preparada

Una sentencia preparada no es más que SQL con huecos. El binding (o enlazado de parámetros) es justo el paso de rellenar esos huecos con valores — de forma segura, uno a uno, usando la API del driver en lugar de andar concatenando strings.

El patrón siempre se ve igual: escribes el SQL con placeholders y luego pasas los valores por separado.

En la CLI no se puede mostrar realmente el binding (la shell no tiene código de aplicación detrás), pero el SQL de arriba es exactamente lo que tu aplicación envía. Los ? son placeholders. Tu driver — sqlite3 en Python, better-sqlite3 en Node, rusqlite en Rust — los rellena mediante una llamada aparte a bind.

La idea mental: el SQL es la receta y los valores enlazados son los ingredientes. Nunca se mezclan entre sí.

Placeholders posicionales: ?

El placeholder más simple es ?. Cada uno se asocia al siguiente valor que enlazas, en orden.

INSERT INTO users (name, email) VALUES (?, ?);

En Python eso se traduce en:

cursor.execute(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    ("Rosa", "rosa@example.com"),
)

El primer ? recibe "Rosa" y el segundo "rosa@example.com". Si pasas más o menos valores de la cuenta, el driver lanza un error antes de ejecutar la sentencia.

También puedes numerarlos de forma explícita con ?1, ?2, ?3, algo muy útil cuando el mismo valor aparece varias veces:

SELECT ?1 AS saludo, ?1 AS sigue_igual;

?1 reutiliza el primer valor enlazado. Sin numerar, tendrías que hacer el binding del mismo valor dos veces.

Placeholders con nombre: :name

Cuando una sentencia tiene más de dos o tres huecos, el binding por posición se vuelve un juego de adivinanzas. Los parámetros con nombre solucionan justo eso:

INSERT INTO users (name, email)
VALUES (:name, :email);

En Python:

cursor.execute(
    "INSERT INTO users (name, email) VALUES (:name, :email)",
    {"name": "Boris", "email": "boris@example.com"},
)

El orden de las claves del diccionario no importa, lo que cuenta son los nombres. SQLite también acepta @name y $name como prefijos alternativos; todos funcionan igual. Eso sí, :name es con diferencia el más usado.

Los parámetros con nombre se vuelven imprescindibles en cuanto tienes un UPDATE con cinco columnas, o una consulta que reutiliza el mismo valor en WHERE y en RETURNING.

Enlazar NULL en SQLite

La forma correcta de insertar NULL es pasar el valor nulo de tu lenguaje a través de la API de binding. El driver se encarga de la conversión:

INSERT INTO users (name, email) VALUES (?, ?);
-- Bind: ("Cyrus", None)   en Python
-- Bind: ["Cyrus", null]   en Node

SELECT id, name, email FROM users;

None, null, nil, o como lo llame tu lenguaje: el driver lo convierte en un NULL SQL de verdad. No enlaces la cadena "NULL", porque eso guarda el texto literal de cuatro caracteres "NULL". Y tampoco interpoles la palabra NULL dentro del SQL: eso anula por completo el binding.

La misma regla aplica para números, blobs y fechas: pasa el valor nativo y deja que el driver se encargue de enlazarlo.

Reutilizar una sentencia preparada con distintos valores

El binding de parámetros encaja de maravilla con las sentencias preparadas en sqlite. Preparas una vez y luego enlazas y ejecutas tantas veces como quieras. El parser hace su trabajo una sola vez, y la base de datos reaprovecha el plan compilado para cada conjunto de valores enlazados.

INSERT INTO users (name, email) VALUES (?, ?);
-- Vincular ("Ada",   "ada@example.com")    -> ejecutar
-- Vincular ("Boris", "boris@example.com")  -> ejecutar
-- Vincular ("Cyrus", NULL)                 -> ejecutar

SELECT id, name, email FROM users ORDER BY id;

La mayoría de los drivers envuelven esto en un executemany (Python) o un bucle con .run() (Node). En cualquier caso, lo que te ahorras es el coste de parseo: poco por cada sentencia, pero se nota cuando insertas miles de filas.

No mezcles estilos en una misma sentencia

Técnicamente, SQLite te deja combinar placeholders posicionales y con nombre dentro de la misma sentencia. Resístete a hacerlo.

-- Permitido pero peligroso:
INSERT INTO users (name, email) VALUES (?, :email);

Quien lee tu código tiene que llevar la cuenta mental de dos APIs de binding a la vez, y la mayoría de los drivers no manejan bien la forma mixta. Elige un estilo por sentencia: ? cuando son uno o dos valores, :name para todo lo demás.

Un error típico: hacer binding no es formatear strings

La gracia del binding de parámetros en SQLite es justamente esa: los valores no pasan por el parser SQL. Compara estas dos líneas de Python:

# Incorrecto — formateo de cadenas:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")

# Correcto — vinculación de parámetros:
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))

La primera línea arma el SQL concatenando cadenas. Si name vale "'; DROP TABLE users; --", la base de datos parsea tan tranquila la sentencia inyectada y la ejecuta. La segunda línea, en cambio, manda el SQL y el valor por canales distintos: el valor se enlaza como cadena y punto, da igual qué caracteres lleve dentro. Por eso todas las guías insisten en hacer binding: no es cuestión de estilo, es cuestión de qué ve el parser.

En la siguiente página entraremos a fondo en el tema de la inyección.

Otro detalle importante: los identificadores no se pueden enlazar

Los placeholders sirven para valores: cadenas, números, blobs, NULLs. No funcionan con nombres de tabla, nombres de columna ni palabras reservadas de SQL:

-- Esto NO hace lo que quieres:
SELECT * FROM ? WHERE id = ?;
-- El primer ? se vincula como un literal de cadena, no como un nombre de tabla.

Si de verdad necesitas un nombre de tabla o columna dinámico (algo poco habitual en código de aplicación), valídalo contra una lista blanca y concaténalo en el SQL tú mismo — nunca directamente desde la entrada del usuario. Para todo lo demás, usa binding.

Un ejemplo completo

Juntemos las piezas: una pequeña tabla users que se escribe y se lee íntegramente mediante consultas parametrizadas:

En código real, tanto los INSERT como el SELECT usarían placeholders. La CLI no tiene una aplicación desde la cual hacer el binding, así que los literales hacen las veces de lo que produciría el binding.

Siguiente: cómo prevenir la inyección SQL

El binding de parámetros es el mecanismo. Por qué frena la inyección SQL —y los pocos casos en los que el binding por sí solo no basta— es justo lo que veremos en la siguiente página.

Preguntas frecuentes

¿Qué es el binding de parámetros en SQLite?

Es la forma de pasar valores a una sentencia preparada sin meterlos dentro del propio SQL. En la consulta escribes un placeholder como ? o :name y luego envías el valor real a través de la API de bind del driver. SQLite trata esos valores como datos puros: nunca se interpretan como SQL.

¿Cuál es la diferencia entre ? y :name en SQLite?

? es un placeholder posicional: los valores se enlazan en el orden en que aparecen. :name (y también @name o $name) son placeholders con nombre, así que enlazas por nombre en lugar de por posición. Los parámetros con nombre se leen mucho mejor y son más fáciles de reordenar cuando manejas más de dos o tres valores.

¿Cómo enlazo un valor NULL en SQLite?

Pasa el valor null/None/nil de tu lenguaje a través de la API de binding: el driver lo traduce automáticamente a NULL de SQL. Nunca escribas la cadena 'NULL' ni metas la palabra NULL directamente en el texto SQL. La gracia del binding es precisamente mantener los valores fuera del parser de SQL.

¿Puedo mezclar parámetros posicionales y con nombre en una misma sentencia?

SQLite lo permite, pero no lo hagas. Una sentencia que combina ? y :name se vuelve confusa y es muy fácil enlazar un valor donde no toca. Elige un único estilo por consulta: en cuanto pases de dos o tres valores, tira de parámetros con nombre.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR