¿Cómo agrego una clave foránea a una tabla SQLite existente?

128

Tengo la siguiente tabla:

CREATE TABLE child( 
  id INTEGER PRIMARY KEY, 
  parent_id INTEGER, 
  description TEXT);

¿Cómo agrego una restricción de clave externa parent_id? Suponga que las claves externas están habilitadas.

La mayoría de los ejemplos asumen que está creando la tabla; me gustaría agregar la restricción a una existente.

Dane O'Connor
fuente
El comando ALTER de SQLite solo admite "cambiar el nombre de la tabla" y "agregar columna". Sin embargo, podemos hacer otros cambios arbitrarios en el formato de una tabla usando una secuencia simple de operaciones. Comprueba mi respuesta
situee

Respuestas:

198

No puedes

Aunque la sintaxis SQL-92 para agregar una clave foránea a su tabla sería la siguiente:

ALTER TABLE child ADD CONSTRAINT fk_child_parent
                  FOREIGN KEY (parent_id) 
                  REFERENCES parent(id);

SQLite no admite la ADD CONSTRAINTvariante del ALTER TABLEcomando ( sqlite.org: características de SQL que SQLite no implementa ).

Por lo tanto, la única forma de agregar una clave foránea en sqlite 3.6.1 es durante CREATE TABLElo siguiente:

CREATE TABLE child ( 
    id           INTEGER PRIMARY KEY, 
    parent_id    INTEGER, 
    description  TEXT,
    FOREIGN KEY (parent_id) REFERENCES parent(id)
);

Desafortunadamente, tendrá que guardar los datos existentes en una tabla temporal, descartar la tabla anterior, crear la nueva tabla con la restricción FK y luego copiar los datos nuevamente desde la tabla temporal. ( sqlite.org - Preguntas frecuentes: P11 )

Daniel Vassallo
fuente
28
Creo que es más fácil cambiar el nombre de la tabla anterior, crear la tabla nueva y copiar los datos nuevamente. Luego puede descartar la tabla anterior.
tuinstoel
Si, eso es más fácil. Estaba citando las preguntas frecuentes de sqlite: sqlite.org/faq.html#q11 . De hecho, RENAME TOes una de las pocas ALTER TABLEvariantes que actualmente se admite en sqlite 3.
Daniel Vassallo
3
No debería ser: FOREIGN KEY (parent_id) REFERENCES parent (id) Verdadero, Jonathan no dio el nombre de la "tabla padre". De hecho, la tabla debería llamarse persona, pero ...
igorludi
3
Esto parece ser un gran problema para mí. Por lo general, cuando volca una base de datos, exporta primero los comandos CREATE TABLE. Luego INSERTAR EN comandos, y finalmente AGREGAR comandos CONSTRAINT. Si hay una dependencia circular (valor de clave externa) en sus datos, entonces no puede insertar sus datos mientras se aplican claves externas. Pero si no puede agregar las restricciones de clave externa más tarde, entonces está atascado. Por supuesto, hay restricciones diferidas, pero esto es muy torpe.
nagylzs
9
¡NO cambie el nombre de la tabla anterior como se dijo en el primer comentario si otras tablas tienen referencias a esta tabla! En este caso, tendrá que recrear todas estas tablas también.
rocknow
57

Puede agregar la restricción si modifica la tabla y agrega la columna que usa la restricción.

Primero, cree una tabla sin parent_id:

CREATE TABLE child( 
  id INTEGER PRIMARY KEY,  
  description TEXT);

Luego, alter table:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Jorge Novaes
fuente
2
Es bueno acostumbrarse a esta secuencia, pero esto no responde a la pregunta real: me gustaría agregar la restricción a una existente.
Wolf
9

Por favor, consulte https://www.sqlite.org/lang_altertable.html#otheralter

Los únicos comandos que alteran el esquema directamente admitidos por SQLite son los comandos "renombrar tabla" y "agregar columna" que se muestran arriba. Sin embargo, las aplicaciones pueden realizar otros cambios arbitrarios en el formato de una tabla utilizando una secuencia simple de operaciones. Los pasos para realizar cambios arbitrarios en el diseño del esquema de alguna tabla X son los siguientes:

  1. Si las restricciones de clave externa están habilitadas, desactívelas usando PRAGMA foreign_keys = OFF.
  2. Comience una transacción.
  3. Recuerde el formato de todos los índices y desencadenantes asociados con la tabla X. Esta información será necesaria en el paso 8 a continuación. Una forma de hacer esto es ejecutar una consulta como la siguiente: SELECT type, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Use CREATE TABLE para construir una nueva tabla "new_X" que esté en el formato revisado deseado de la tabla X. Asegúrese de que el nombre "new_X" no colisione con ningún nombre de tabla existente, por supuesto.
  5. Transfiera contenido de X a new_X usando una instrucción como: INSERT INTO en new_X SELECT ... FROM X.
  6. Suelta la vieja mesa X: DROP TABLE X.
  7. Cambie el nombre de new_X a X usando: ALTER TABLE new_X RENAME TO X.
  8. Use CREATE INDEX y CREATE TRIGGER para reconstruir índices y desencadenantes asociados con la tabla X. Tal vez use el formato anterior de los desencadenantes e índices guardados del paso 3 anterior como guía, haciendo los cambios apropiados para la alteración.
  9. Si alguna de las vistas se refiere a la tabla X de una manera que se ve afectada por el cambio de esquema, elimine esas vistas usando DROP VIEW y vuelva a crearlas con los cambios necesarios para acomodar el cambio de esquema usando CREATE VIEW.
  10. Si las restricciones de clave externa se habilitaron originalmente, ejecute PRAGMA foreign_key_check para verificar que el cambio de esquema no rompió ninguna restricción de clave externa.
  11. Confirmar la transacción iniciada en el paso 2.
  12. Si las restricciones de claves externas se habilitaron originalmente, vuelva a habilitarlas ahora.

El procedimiento anterior es completamente general y funcionará incluso si el cambio de esquema hace que cambie la información almacenada en la tabla. Por lo tanto, el procedimiento completo anterior es apropiado para soltar una columna, cambiar el orden de las columnas, agregar o eliminar una restricción ÚNICA o CLAVE PRIMARIA, agregar restricciones CHECK o FOREIGN KEY o NOT NULL, o cambiar el tipo de datos para una columna, por ejemplo.

situee
fuente
4

Sí, puede, sin agregar una nueva columna. Debe tener cuidado de hacerlo correctamente para evitar corromper la base de datos, por lo que debe hacer una copia de seguridad completa de la base de datos antes de intentarlo.

para su ejemplo específico:

CREATE TABLE child(
  id INTEGER PRIMARY KEY,
  parent_id INTEGER,
  description TEXT
);

--- create the table we want to reference
create table parent(id integer not null primary key);

--- now we add the foreign key
pragma writable_schema=1;
update SQLITE_MASTER set sql = replace(sql, 'description TEXT)',
    'description TEXT, foreign key (parent_id) references parent(id))'
) where name = 'child' and type = 'table';

--- test the foreign key
pragma foreign_keys=on;
insert into parent values(1);
insert into child values(1, 1, 'hi'); --- works
insert into child values(2, 2, 'bye'); --- fails, foreign key violation

o más generalmente:

pragma writable_schema=1;

// replace the entire table's SQL definition, where new_sql_definition contains the foreign key clause you want to add
UPDATE SQLITE_MASTER SET SQL = new_sql_definition where name = 'child' and type = 'table';

// alternatively, you might find it easier to use replace, if you can match the exact end of the sql definition
// for example, if the last column was my_last_column integer not null:
UPDATE SQLITE_MASTER SET SQL = replace(sql, 'my_last_column integer not null', 'my_last_column integer not null, foreign key (col1, col2) references other_table(col1, col2)') where name = 'child' and type = 'table';

pragma writable_schema=0;

De cualquier manera, es probable que desee ver primero cuál es la definición de SQL antes de realizar cambios:

select sql from SQLITE_MASTER where name = 'child' and type = 'table';

Si usa el enfoque replace (), puede que le resulte útil, antes de ejecutar, probar primero el comando replace () ejecutando:

select replace(sql, ...) from SQLITE_MASTER where name = 'child' and type = 'table';
mwag
fuente
3

Si está utilizando el complemento sqlite-manager de Firefox, puede hacer lo siguiente:

En lugar de soltar y crear la tabla nuevamente, uno puede modificarla así.

En el cuadro de texto Columnas, haga clic con el botón derecho en el último nombre de columna de la lista para que aparezca el menú contextual y seleccione Editar columna. Tenga en cuenta que si la última columna en la definición de TABLA es la CLAVE PRIMARIA, primero será necesario agregar una nueva columna y luego editar el tipo de columna de la nueva columna para agregar la definición de CLAVE EXTRANJERA. Dentro del cuadro Tipo de columna, agregue una coma y el

FOREIGN KEY (parent_id) REFERENCES parent(id)

definición después del tipo de datos. Haga clic en el botón Cambiar y luego haga clic en el botón Sí en el cuadro de diálogo Operación peligrosa.

Referencia: Sqlite Manager

Baso
fuente
2

Puedes probar esto:

ALTER TABLE [Child] ADD COLUMN column_name INTEGER REFERENCES parent_table_name(column_id);
Jamshy EK
fuente
-1

Básicamente no puedes pero puedes pasar por alto la situación.

La forma correcta de agregar la restricción de clave externa a una tabla existente es el siguiente comando.

db.execSQL("alter table child add column newCol integer REFERENCES parent(parent_Id)");

Luego copie los datos parent_Id a newCol y luego elimine la columna Parent_Id . Por lo tanto, no hay necesidad de una tabla temporal.

saeed khalafinejad
fuente
Parece que no leíste la pregunta con cuidado. El problema era agregar solo una restricción externa, no agregar una columna con una restricción.
Wolf
No No responde la pregunta formulada.
MK
-4

Primero agregue una columna en la tabla secundaria Cidcomo intluego alter tablecon el código a continuación. De esta manera, puede agregar la clave externa Cidcomo la clave principal de la tabla primaria y usarla como clave externa en la tabla secundaria ... espero que lo ayude, ya que es bueno para mí:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Tariq Nawaz Khan
fuente
1
Esto no es válido en SQLite. También esta es la sintaxis de MS SQL.
StilesCrisis