CREATE TABLE define el esquema
En SQLite, todos los datos estructurados viven dentro de una tabla, y cada tabla nace con una sentencia CREATE TABLE. Le pones un nombre, declaras las columnas y, si hace falta, le añades restricciones. SQLite guarda el esquema dentro del archivo de la base de datos y la tabla queda lista para usarse.
Veamos el ejemplo mínimo útil:
Tres columnas, una clave primaria y una regla NOT NULL. SQLite rellenó id solo porque es una clave primaria de tipo entero, y dejó que email quedara en NULL en la segunda fila porque nada decía lo contrario. Esa es toda la estructura: nombre, columnas y restricciones. Todo lo demás en esta página son variaciones sobre ese mismo esqueleto.
Sintaxis de CREATE TABLE en SQLite, parte por parte
La definición de una columna sigue el patrón nombre TIPO restricción restricción .... El tipo es opcional en el SQLite clásico (lo veremos a fondo en la página sobre afinidad de tipos), pero conviene indicarlo siempre: tanto quien lee el código como las herramientas se apoyan en esa información.
Vale la pena mencionar algunos detalles:
- Las restricciones se encadenan con espacios:
NOT NULL UNIQUEenskuaplica ambas reglas a la vez. DEFAULT 1enin_stockpermite que elINSERTomita esa columna.- SQLite usa
INTEGERpara los booleanos, ya que no existe un tipoBOOLEANnativo.0es falso y1es verdadero. - Una coma sobrante después de la última columna es un error de sintaxis. SQL es más estricto que JavaScript en esto.
IF NOT EXISTS: evita errores al reejecutar el script
Si ejecutas un CREATE TABLE sobre una base de datos que ya tiene esa tabla, SQLite lanza un error:
Error: la tabla users ya existe
Está bien la primera vez, pero a la centésima ya cansa. Con IF NOT EXISTS la sentencia no hace nada si la tabla ya existe:
El segundo CREATE TABLE no hace nada: ni error, ni cambios en el esquema. Esta es la forma que querrás usar en código de arranque, scripts de migración y cualquier sitio donde el mismo SQL pueda ejecutarse más de una vez.
Eso sí, ojo con un detalle: IF NOT EXISTS solo comprueba el nombre. Si ya existe una tabla con ese nombre pero con columnas distintas, SQLite la deja tal cual. No te va a "arreglar" ni "actualizar" el esquema por su cuenta. Para eso están las migraciones.
Restricciones: reglas que viajan con el esquema
Las restricciones (constraints) son la forma de meter la validación dentro de la propia base de datos. Estas son las cuatro a las que vas a recurrir todo el tiempo:
PRIMARY KEY— identifica de forma única cada fila. Tienes más detalles en la doc sobre claves primarias.NOT NULL— la columna está obligada a tener un valor.DEFAULT valor— se aplica cuando unINSERTomite la columna. Puede ser un literal o una expresión comodatetime('now').CHECK (expr)— debe dar verdadero en cada fila.UNIQUE (col, col)— restricción a nivel de tabla que garantiza unicidad sobre la combinación de columnas.
Las restricciones se evalúan en cada INSERT y UPDATE. Si una fila viola alguna, se rechaza y la sentencia falla. Cazar datos inválidos en la propia base de datos sale mucho más barato que detectarlos cuando ya se han colado por toda la aplicación.
Claves foráneas en SQLite
Una clave foránea (foreign key) viene a decir: "esta columna apunta a una fila de otra tabla". Sirve para mantener la coherencia de los datos: no puedes referenciar un usuario que no existe y, con las opciones adecuadas, borrar un usuario puede arrastrar en cascada sus pedidos.
Un detalle de SQLite que conviene grabarse a fuego: la verificación de claves foráneas viene desactivada por defecto. Tenés que ejecutar PRAGMA foreign_keys = ON en cada conexión donde quieras que se respeten las restricciones. La mayoría de los drivers de aplicación lo hacen por vos o exponen una opción para activarlo; si el tuyo no, lanzá el pragma justo después de conectar.
El ON DELETE CASCADE que usamos acá significa que, al borrar un usuario, sus posts se borran automáticamente. Otras opciones son SET NULL, RESTRICT y la que viene por defecto, NO ACTION, que rechaza el borrado si existen registros hijos.
CREATE TABLE AS SELECT en SQLite
A veces necesitás una copia rápida del resultado de una consulta como tabla nueva: para una foto del momento, un backup o una tabla temporal mientras hacés análisis. Para eso está CREATE TABLE ... AS SELECT:
La nueva tabla hereda los nombres de columnas, los tipos (en la medida de lo posible) y los datos. Pero hay algo igual de importante que no se copia: ni la clave primaria, ni las restricciones NOT NULL, ni los índices, ni las claves foráneas. Es una foto plana de los datos. Úsala como punto de partida para trabajo puntual, no como forma de clonar un esquema real.
Si lo que quieres es la estructura sin los datos, añade WHERE 0:
Te queda una tabla vacía con la misma estructura de columnas: ideal para tablas de archivo que rellenarás más adelante.
Crear tabla temporal en SQLite
Una tabla TEMP existe únicamente mientras dura la conexión actual a la base de datos. Al cerrar la conexión, desaparece sola: sin limpieza manual ni esquema residual:
Buenos casos de uso: preparar filas para una consulta de varios pasos, guardar resultados intermedios demasiado complicados para un CTE, o aislar datos por conexión en una sesión que dura mucho tiempo. CREATE TEMP TABLE y CREATE TEMPORARY TABLE son equivalentes.
También puedes combinarlo con AS SELECT: CREATE TEMP TABLE snapshot AS SELECT ... es un patrón muy habitual para congelar un resultado a mitad de un análisis.
Cómo escapar nombres
La mayoría de las veces, los nombres de columnas y tablas son identificadores normales y corrientes. Pero si necesitas usar una palabra reservada o un nombre con espacios, enciérralo entre comillas dobles (lo que dicta el estándar SQL) o entre acentos graves (una herencia de MySQL que SQLite también admite):
Funciona, sí, pero te genera fricción cada vez que tengas que referenciar la tabla. Mejor usa nombres simples como orders, selection o user_id y olvídate de las comillas.
Un ejemplo realista
Juntemos las piezas con un esquema pequeño para una app de tareas, usando IF NOT EXISTS para que se pueda ejecutar en cada arranque sin problemas:
Ese esquema ya está listo para producción: creación idempotente, claves foráneas activas, un CHECK que mantiene a raya el campo done, valores por defecto razonables y marcas de tiempo que se rellenan solas.
Lo que sigue: tipos de datos
CREATE TABLE te deja escribir INTEGER, TEXT, REAL... pero SQLite es famoso por ser bastante laxo a la hora de almacenar esos valores. En la siguiente página vamos a ver las cinco clases de almacenamiento que SQLite usa de verdad, y por qué el tipo que tú declaras no siempre es el tipo que acabas obteniendo.
Preguntas frecuentes
¿Cómo se crea una tabla en SQLite?
Con CREATE TABLE nombre (columna1 TIPO, columna2 TIPO, ...). A cada columna le asignas un nombre y, opcionalmente, un tipo, y puedes añadir restricciones como PRIMARY KEY, NOT NULL o DEFAULT. La sentencia se ejecuta al momento y la tabla queda guardada en el archivo de la base de datos.
¿Para qué sirve IF NOT EXISTS en CREATE TABLE?
CREATE TABLE IF NOT EXISTS nombre (...) solo crea la tabla si todavía no existe otra con ese nombre. Sin esta cláusula, volver a ejecutar el script sobre una base de datos ya existente lanza un error tipo table already exists. Es la salvaguarda habitual en scripts de migración y en el código de arranque de aplicaciones.
¿Puedo crear una tabla a partir de un SELECT en SQLite?
Sí: CREATE TABLE nuevo_nombre AS SELECT ... genera una tabla nueva con el resultado de la consulta. Hereda los nombres de columna y los datos, pero no copia las restricciones, claves primarias ni índices de la tabla original. Va bien para snapshots o tablas auxiliares, pero no la uses como sustituto de un esquema bien definido.
¿Qué diferencia hay entre una tabla temporal y una normal?
CREATE TEMP TABLE (o CREATE TEMPORARY TABLE) crea una tabla que solo existe durante la conexión actual y desaparece cuando esta se cierra. Las tablas normales se guardan de forma permanente en el archivo de la base de datos. Las temporales son útiles para preparar resultados intermedios sin ensuciar el esquema.