Como adiciono uma chave estrangeira a uma tabela SQLite existente?

128

Eu tenho a seguinte tabela:

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

Como adiciono uma restrição de chave estrangeira parent_id? Suponha que chaves estrangeiras estejam ativadas.

A maioria dos exemplos pressupõe que você esteja criando a tabela - eu gostaria de adicionar a restrição a uma existente.

Dane O'Connor
fonte
O comando SQLite ALTER suporta apenas "renomear tabela" e "adicionar coluna". No entanto, podemos fazer outras alterações arbitrárias no formato de uma tabela usando uma sequência simples de operações. Verifique minha resposta
situee

Respostas:

198

Você não pode.

Embora a sintaxe SQL-92 para adicionar uma chave estrangeira à sua tabela seja a seguinte:

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

O SQLite não suporta a ADD CONSTRAINTvariante do ALTER TABLEcomando ( sqlite.org: Recursos do SQL que o SQLite não implementa ).

Portanto, a única maneira de adicionar uma chave estrangeira no sqlite 3.6.1 é a CREATE TABLEseguinte:

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

Infelizmente, você terá que salvar os dados existentes em uma tabela temporária, soltar a tabela antiga, criar a nova tabela com a restrição FK e copiar os dados novamente da tabela temporária. ( sqlite.org - Perguntas frequentes: Q11 )

Daniel Vassallo
fonte
28
Eu acho que é mais fácil renomear a tabela antiga, criar a nova tabela e copiar os dados novamente. Em seguida, você pode soltar a tabela antiga.
Tuinstoel
Sim, isso é mais fácil. Acabei de citar a FAQ do sqlite: sqlite.org/faq.html#q11 . De fato, RENAME TOé uma das poucas ALTER TABLEvariantes que atualmente são suportadas no sqlite 3. #
Daniel Vassallo
3
Não deveria ser: FOREIGN KEY (parent_id) REFERÊNCIAS parent (id) Verdadeiro, Jonathan não deu o nome da "tabela pai". Na verdade, a tabela deve ser nomeado pessoa, mas ...
igorludi
3
Este parece ser um grande problema para mim. Geralmente, ao despejar um banco de dados, você exporta os comandos CREATE TABLE primeiro. Em seguida, insira os comandos INTO e, finalmente, os comandos ADD CONSTRAINT. Se houver dependência circular (valor da chave estrangeira) nos seus dados, você não poderá inserir os dados enquanto as chaves estrangeiras forem impostas. Mas se você não puder adicionar as restrições de chave estrangeira posteriormente, estará bloqueado. É claro que existem restrições adiadas, mas isso é muito desajeitado.
Nagylzs 15/03
9
NÃO renomeie a tabela antiga como mencionado no primeiro comentário se outras tabelas tiverem referências a esta tabela! Nesse caso, você terá que recriar todas essas tabelas também.
rocknow
57

Você pode adicionar a restrição se alterar a tabela e adicionar a coluna que usa a restrição.

Primeiro, crie a tabela sem o parent_id:

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

Em seguida, altere a tabela:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Jorge Novaes
fonte
2
É bom se acostumar com essa sequência, mas isso não responde à pergunta real: eu gostaria de adicionar a restrição a uma existente.
Wolf
9

Verifique https://www.sqlite.org/lang_altertable.html#otheralter

Os únicos comandos de alteração de esquema suportados diretamente pelo SQLite são os comandos "renomear tabela" e "adicionar coluna" mostrados acima. No entanto, os aplicativos podem fazer outras alterações arbitrárias no formato de uma tabela usando uma sequência simples de operações. As etapas para fazer alterações arbitrárias no design do esquema de alguma tabela X são as seguintes:

  1. Se restrições de chave estrangeira estiverem ativadas, desative-as usando PRAGMA Foreign_keys = OFF.
  2. Inicie uma transação.
  3. Lembre-se do formato de todos os índices e gatilhos associados à tabela X. Essas informações serão necessárias na etapa 8 abaixo. Uma maneira de fazer isso é executar uma consulta como a seguinte: SELECT type, sql FROM sqlite_master WHERE nome_tabela = 'X'.
  4. Use CREATE TABLE para construir uma nova tabela "new_X" que esteja no formato revisado desejado da tabela X. Certifique-se de que o nome "new_X" não colide com nenhum nome de tabela existente, é claro.
  5. Transfira o conteúdo de X para new_X usando uma instrução como: INSERT INTO new_X SELECT ... FROM X.
  6. Solte a tabela antiga X: DROP TABLE X.
  7. Altere o nome de new_X para X usando: ALTER TABLE new_X RENOMEAR PARA X.
  8. Use CREATE INDEX e CREATE TRIGGER para reconstruir índices e gatilhos associados à tabela X. Talvez use o formato antigo dos gatilhos e índices salvos da etapa 3 acima como um guia, fazendo as alterações apropriadas para a alteração.
  9. Se alguma visualização se referir à tabela X de uma maneira que é afetada pela alteração do esquema, descarte essas visualizações usando o DROP VIEW e recriá-las com as alterações necessárias para acomodar a alteração do esquema usando CREATE VIEW.
  10. Se as restrições de chave estrangeira foram originalmente ativadas, execute PRAGMA Foreign_key_check para verificar se a alteração do esquema não quebrou nenhuma restrição de chave estrangeira.
  11. Confirme a transação iniciada na etapa 2.
  12. Se as restrições de chaves estrangeiras foram originalmente ativadas, reative-as agora.

O procedimento acima é completamente geral e funcionará mesmo se a alteração do esquema fizer com que as informações armazenadas na tabela sejam alteradas. Portanto, o procedimento completo acima é apropriado para descartar uma coluna, alterar a ordem das colunas, adicionar ou remover uma restrição UNIQUE ou PRIMARY KEY, adicionar restrições CHECK ou FOREIGN KEY ou NOT NULL ou alterar o tipo de dados de uma coluna, por exemplo.

situee
fonte
4

Sim, você pode, sem adicionar uma nova coluna. Você deve ter o cuidado de fazê-lo corretamente, a fim de evitar a corrupção do banco de dados; portanto, faça um backup completo do banco de dados antes de tentar isso.

para o seu exemplo 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

ou mais geralmente:

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 qualquer forma, você provavelmente desejará ver primeiro qual é a definição SQL antes de fazer alterações:

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

Se você usar a abordagem replace (), poderá achar útil, antes de executar, testar primeiro o comando replace () executando:

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

Se você estiver usando o sqlite-manager do Firefox, faça o seguinte:

Em vez de soltar e criar a tabela novamente, é possível modificá-la dessa maneira.

Na caixa de texto Colunas, clique com o botão direito do mouse no último nome da coluna listado para abrir o menu de contexto e selecione Editar Coluna. Observe que se a última coluna na definição TABLE for a PRIMARY KEY, será necessário primeiro adicionar uma nova coluna e editar o tipo de coluna da nova coluna para adicionar a definição FOREIGN KEY. Na caixa Tipo de coluna, acrescente uma vírgula e o

FOREIGN KEY (parent_id) REFERENCES parent(id)

definição após o tipo de dados. Clique no botão Alterar e, em seguida, clique no botão Sim na caixa de diálogo Operação perigosa.

Referência: Sqlite Manager

Baso
fonte
2

Você pode tentar isso:

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

Basicamente, você não pode, mas pode ignorar a situação.

A maneira correta de adicionar a restrição de chave estrangeira a uma tabela existente é o seguinte comando.

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

Em seguida, copie os dados parent_Id para o newCol e exclua a coluna Parent_Id . Portanto, não há necessidade de tabela temporária.

saeed khalafinejad
fonte
Parece que você não leu a pergunta com atenção. O problema era adicionar apenas uma restrição externa, não adicionar uma coluna com uma restrição.
Wolf
Não. Não responde à pergunta.
MK
-4

Primeiro, adicione uma coluna na tabela filho Cide, em intseguida, alter tablecom o código abaixo. Dessa forma, você pode adicionar a chave estrangeira Cidcomo chave primária da tabela pai e usá-la como chave estrangeira na tabela filho ... espero que ajude você, pois é bom para mim:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Tariq Nawaz Khan
fonte
1
Isso não é válido no SQLite. Além disso, esta é a sintaxe do MS SQL.
StilesCrisis