UNIQUE significa "no se permiten duplicados"
La restricción UNIQUE en SQLite le indica al motor que los valores de una columna (o de un conjunto de columnas) no pueden repetirse entre filas. Es la forma de decir "dos usuarios no pueden compartir el mismo email" o "un código de producto aparece como mucho una vez".
El tercer INSERT falla con UNIQUE constraint failed: users.email. SQLite valida la restricción en cada escritura y rechaza cualquier cosa que genere un duplicado. Las dos primeras filas sí se guardan; la tercera nunca llega a la tabla.
Por dentro, UNIQUE se implementa como un índice único — la misma estructura de datos que SQLite usa para hacer búsquedas rápidas — así que la comprobación es barata y, de paso, la columna queda indexada automáticamente.
Sintaxis a nivel de columna vs a nivel de tabla
Hay dos formas de declarar UNIQUE en SQLite: en línea, junto a la columna, o como una cláusula aparte al final de la definición de la tabla:
Para una sola columna, ambas formas son equivalentes: usa la que te resulte más legible. La forma a nivel de tabla se vuelve indispensable en cuanto necesitas garantizar unicidad en más de una columna.
UNIQUE en varias columnas: clave única compuesta en SQLite
A veces una columna por sí sola no tiene por qué ser única, pero la combinación de varias sí debe serlo. Un usuario puede matricularse en muchos cursos, y un curso puede tener muchos usuarios, pero el mismo par (user_id, course_id) no debería aparecer dos veces:
La restricción aplica al par, no a cada columna por separado. El usuario 1 puede inscribirse en muchos cursos y el curso 100 puede tener muchos usuarios; lo único que no se permite es repetir la misma combinación.
Es el patrón clásico para tablas intermedias en relaciones muchos a muchos.
UNIQUE vs PRIMARY KEY en SQLite
Suenan parecido y están relacionadas, pero no son lo mismo:
- Una tabla tiene como mucho una
PRIMARY KEY, pero puede tener varias restriccionesUNIQUE. - La
PRIMARY KEYes la identidad de la fila: a ella apuntan las claves foráneas y de ella es alias elrowid. UNIQUEsignifica simplemente "este valor (o combinación) no se repite".- En una tabla normal, una columna
UNIQUEsí admite valoresNULL; unaPRIMARY KEYno (con una excepción histórica que vamos a obviar).
Una forma habitual de usarlas juntas:
id es lo que referencia el resto de la base de datos. email y username son únicos porque la aplicación lo exige, no porque sean la identidad. Si un usuario cambia su correo, el id se mantiene igual: ese es justo el sentido de separarlos.
El detalle con los NULL
Esto pilla a casi todo el mundo la primera vez. Una columna UNIQUE en SQLite acepta tantos valores NULL como quieras:
Tres NULL, ningún problema. Dos 'ada@example.com', ahí sí hay conflicto.
¿Por qué? Porque SQL trata a NULL como "desconocido", y dos valores desconocidos no se consideran iguales entre sí. Por eso la verificación de unicidad no los marca como duplicados. Si necesitas que haya como máximo un NULL, lo más limpio es usar NOT NULL UNIQUE. Y si los NULL son válidos pero solo quieres uno por cada combinación de las demás columnas, lo tuyo es un índice único parcial (lo vemos más adelante en el capítulo de índices).
Resolver conflictos con ON CONFLICT
Por defecto, una violación de la restricción UNIQUE aborta la sentencia. Pero a veces te interesa otro comportamiento: reemplazar la fila existente, ignorar la nueva o actualizar ciertas columnas. SQLite te ofrece dos formas de pedirlo.
La primera va incrustada en la propia restricción mediante ON CONFLICT:
La segunda vez que se inserta theme, la fila existente se elimina y la nueva ocupa su lugar. Otras opciones son IGNORE (lo omite sin avisar), ABORT (la predeterminada), FAIL y ROLLBACK.
La segunda forma se aplica por sentencia, mediante la sintaxis upsert, que suele ser más flexible porque permite actualizar columnas concretas:
El primer INSERT crea la fila. Los dos siguientes chocan con la restricción UNIQUE y caen en la rama DO UPDATE, que incrementa count. Este es el patrón upsert de INSERT ... ON CONFLICT — más adelante hay una página dedicada solo a este tema.
Restricción UNIQUE vs índice único en SQLite
CREATE UNIQUE INDEX cumple el mismo papel que una restricción UNIQUE. De hecho, cuando defines una restricción UNIQUE, SQLite crea un índice único por debajo: son prácticamente el mismo mecanismo con distinto nombre.
¿Cuándo conviene usar cada una?
- Restricción cuando la unicidad forma parte de la definición de la tabla. Queda documentada justo al lado de las columnas.
- Índice único cuando necesitas un índice parcial (con cláusula
WHERE), quieres ponerle un nombre concreto o añadirlo a una tabla existente sin tener que reescribirla. ElALTER TABLEde SQLite no permite agregar restricciones, pero sí siempre crear un índice.
A la hora de escribir, el comportamiento es idéntico. La elección depende sobre todo de dónde prefieres que viva la regla dentro del esquema.
Añadir UNIQUE a una tabla existente en SQLite
El ALTER TABLE de SQLite es limitado a propósito: no existe ALTER TABLE ... ADD CONSTRAINT. Tienes dos opciones prácticas:
La opción 2 —cuando lo que quieres de verdad es dejar la cláusula UNIQUE cocida dentro de la definición de la tabla— es el típico baile de reescribir la tabla: creas una tabla nueva con la restricción, copias los datos, eliminas la vieja y renombras. Lo vemos en la siguiente página.
Un aviso: si intentas añadir unicidad sobre una columna que ya contiene duplicados, el CREATE UNIQUE INDEX va a fallar. Primero limpia las filas duplicadas y después crea el índice.
Cuándo falla UNIQUE: cómo leer el error
El mensaje de error te dice exactamente qué restricción se rompió:
Error: UNIQUE constraint failed: users.email
Error: UNIQUE constraint failed: enrollments.user_id, enrollments.course_id
La primera forma es una restricción de una sola columna sobre users.email. La segunda es compuesta: aparecen ambas columnas porque ya existe esa combinación. Cuando veas este error:
- Localiza qué fila tiene ya el valor en conflicto (
SELECT ... WHERE email = '...'). - Decide si quieres actualizar esa fila, saltarte el insert o usar otro valor.
- Si los duplicados son esperables y lo que buscas es fusionarlos, usa
INSERT ... ON CONFLICT DO UPDATE.
El error salta de forma escandalosa porque la mayoría de las veces te interesa enterarte: un duplicado silencioso sería peor que un INSERT fallido.
Siguiente paso: borrar y modificar tablas
Las restricciones UNIQUE no se pueden añadir a una tabla existente con un simple ALTER TABLE. Esa limitación es la razón por la que SQLite tiene su propio baile para los cambios de esquema —la reescritura de tabla— y de eso va la próxima página, junto con lo básico para eliminar tablas sin dejar restos.
Preguntas frecuentes
¿Cómo agrego una restricción UNIQUE en SQLite?
Puedes añadir UNIQUE directamente en la definición de una columna (email TEXT UNIQUE) o, si necesitas unicidad sobre varias columnas, usar una cláusula a nivel de tabla con UNIQUE(col1, col2). Por debajo, SQLite crea un índice único y rechaza cualquier INSERT o UPDATE que produzca un duplicado.
¿Qué diferencia hay entre UNIQUE y PRIMARY KEY en SQLite?
Una tabla solo puede tener una PRIMARY KEY, pero admite tantas restricciones UNIQUE como quieras. Además, PRIMARY KEY implica NOT NULL (en tablas estrictas y en columnas INTEGER PRIMARY KEY), mientras que una columna UNIQUE sí permite varios NULL. La regla práctica: usa la clave primaria para identificar la fila y UNIQUE para el resto de columnas que no deben repetirse.
¿Por qué SQLite permite varios NULL en una columna UNIQUE?
Porque en SQL NULL significa «desconocido», y dos valores desconocidos no se consideran iguales entre sí. Por eso una columna UNIQUE acepta tantas filas con NULL como quieras: la restricción solo aplica a los valores no nulos. Si necesitas que solo haya un NULL (o ninguno), añade NOT NULL o crea un índice único parcial.
¿Cómo soluciono el error «UNIQUE constraint failed»?
Ese error aparece cuando un INSERT o UPDATE intenta crear un valor duplicado en una columna UNIQUE (o PRIMARY KEY). Tienes tres salidas: cambiar el valor que estás insertando, eliminar antes la fila existente, o usar INSERT ... ON CONFLICT (un upsert) para indicarle a SQLite cómo actuar cuando ocurra el conflicto.