Bagaimana cara menambahkan kunci asing ke tabel SQLite yang sudah ada?

128

Saya memiliki tabel berikut:

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

Bagaimana cara menambahkan batasan kunci asing parent_id? Asumsikan kunci asing diaktifkan.

Sebagian besar contoh menganggap Anda sedang membuat tabel - Saya ingin menambahkan batasan ke tabel yang sudah ada.

Dane O'Connor
sumber
Perintah SQLite ALTER hanya mendukung "ganti nama tabel" dan "tambahkan kolom". Namun, kami dapat membuat perubahan sewenang-wenang lainnya ke format tabel menggunakan urutan operasi sederhana. Periksa jawaban saya
situee

Jawaban:

198

Tidak boleh.

Meskipun sintaks SQL-92 untuk menambahkan kunci asing ke tabel Anda adalah sebagai berikut:

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

SQLite tidak mendukung dengan ADD CONSTRAINTvarian dari ALTER TABLEperintah ( sqlite.org: SQL Fitur Itu SQLite Apakah Tidak Melaksanakan ).

Oleh karena itu, satu-satunya cara untuk menambahkan kunci asing di sqlite 3.6.1 adalah CREATE TABLEsebagai berikut:

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

Sayangnya Anda harus menyimpan data yang ada ke tabel sementara, menghapus tabel lama, membuat tabel baru dengan batasan FK, lalu menyalin kembali data dari tabel sementara. ( sqlite.org - FAQ: Q11 )

Daniel Vassallo
sumber
28
Saya pikir lebih mudah untuk mengganti nama tabel lama, membuat tabel baru dan menyalin datanya kembali. Kemudian Anda dapat membuang tabel lama.
tuinstoel
Ya, itu lebih mudah. Saya baru saja mengutip FAQ sqlite: sqlite.org/faq.html#q11 . Faktanya, RENAME TOadalah salah satu dari sedikit ALTER TABLEvarian yang saat ini didukung di sqlite 3.
Daniel Vassallo
3
Bukankah seharusnya: FOREIGN KEY (parent_id) REFERENSI induk (id) Benar, Jonathan tidak memberikan nama "tabel induk". Faktanya, tabel harus diberi nama orang, tapi ...
igorludi
3
Ini sepertinya menjadi masalah besar bagi saya. Biasanya saat Anda membuang database, Anda mengekspor perintah CREATE TABLE terlebih dahulu. Kemudian SISIPKAN KE perintah, dan terakhir TAMBAHKAN perintah BATAS. Jika ada ketergantungan melingkar (nilai kunci asing) dalam data Anda, maka Anda tidak dapat memasukkan data Anda saat kunci asing diterapkan. Tetapi jika Anda tidak dapat menambahkan batasan kunci asing nanti, maka Anda macet. Tentu saja ada kendala yang ditangguhkan, tetapi ini sangat kikuk.
nagylzs
9
JANGAN mengganti nama tabel lama seperti yang dikatakan pada komentar pertama jika tabel lain memiliki referensi ke tabel ini! Dalam kasus ini, Anda juga harus membuat ulang semua tabel ini.
rocknow
57

Anda dapat menambahkan batasan jika Anda mengubah tabel dan menambahkan kolom yang menggunakan batasan.

Pertama, buat tabel tanpa parent_id:

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

Kemudian, ubah tabel:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Jorge Novaes
sumber
2
Bagus untuk terbiasa dengan urutan ini, tetapi ini tidak menjawab pertanyaan sebenarnya: Saya ingin menambahkan batasan ke yang sudah ada.
Serigala
9

Silakan periksa https://www.sqlite.org/lang_altertable.html#otheralter

Satu-satunya perintah untuk mengubah skema yang didukung langsung oleh SQLite adalah perintah "ganti nama tabel" dan "tambahkan kolom" yang ditunjukkan di atas. Namun, aplikasi dapat membuat perubahan sewenang-wenang lainnya ke format tabel menggunakan urutan operasi sederhana. Langkah-langkah untuk membuat perubahan sewenang-wenang pada desain skema beberapa tabel X adalah sebagai berikut:

  1. Jika batasan kunci asing diaktifkan, nonaktifkan mereka menggunakan PRAGMA foreign_keys = OFF.
  2. Mulailah transaksi.
  3. Ingat format semua indeks dan pemicu yang terkait dengan tabel X. Informasi ini akan dibutuhkan pada langkah 8 di bawah. Salah satu cara untuk melakukannya adalah dengan menjalankan query seperti berikut: SELECT type, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Gunakan CREATE TABLE untuk membuat tabel baru "new_X" yang dalam format revisi yang diinginkan dari tabel X. Pastikan bahwa nama "new_X" tidak bertabrakan dengan nama tabel yang ada, tentunya.
  5. Transfer konten dari X ke new_X menggunakan pernyataan seperti: INSERT INTO new_X SELECT ... FROM X.
  6. Jatuhkan tabel lama X: DROP TABLE X.
  7. Ubah nama dari new_X menjadi X menggunakan: ALTER TABLE new_X RENAME TO X.
  8. Gunakan CREATE INDEX dan CREATE TRIGGER untuk merekonstruksi indeks dan pemicu yang terkait dengan tabel X. Mungkin gunakan format lama dari pemicu dan indeks yang disimpan dari langkah 3 di atas sebagai panduan, membuat perubahan yang sesuai untuk perubahan tersebut.
  9. Jika ada tampilan yang merujuk ke tabel X dengan cara yang dipengaruhi oleh perubahan skema, lepaskan tampilan tersebut menggunakan DROP VIEW dan buat ulang dengan perubahan apa pun yang diperlukan untuk mengakomodasi perubahan skema menggunakan CREATE VIEW.
  10. Jika batasan kunci asing pada awalnya diaktifkan kemudian jalankan PRAGMA foreign_key_check untuk memverifikasi bahwa perubahan skema tidak merusak batasan kunci asing.
  11. Lakukan transaksi yang dimulai pada langkah 2.
  12. Jika batasan kunci asing awalnya diaktifkan, aktifkan kembali sekarang.

Prosedur di atas sangat umum dan akan berfungsi meskipun perubahan skema menyebabkan informasi yang disimpan dalam tabel berubah. Jadi prosedur lengkap di atas sesuai untuk menjatuhkan kolom, mengubah urutan kolom, menambah atau menghapus kendala UNIQUE atau PRIMARY KEY, menambahkan kendala CHECK atau FOREIGN KEY atau NOT NULL, atau mengubah tipe data untuk kolom, misalnya.

situee
sumber
4

Ya, Anda bisa, tanpa menambahkan kolom baru. Anda harus berhati-hati melakukannya dengan benar untuk menghindari kerusakan database, jadi Anda harus benar-benar mencadangkan database Anda sebelum mencoba ini.

untuk contoh spesifik Anda:

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

atau lebih umum:

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;

Apa pun itu, Anda mungkin ingin melihat definisi SQL terlebih dahulu sebelum membuat perubahan:

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

Jika Anda menggunakan pendekatan replace (), Anda mungkin merasa terbantu, sebelum menjalankan, untuk terlebih dahulu menguji perintah replace () Anda dengan menjalankan:

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

Jika Anda menggunakan add-on Firefox sqlite-manager, Anda dapat melakukan hal berikut:

Alih-alih menjatuhkan dan membuat tabel lagi, seseorang dapat memodifikasinya seperti ini.

Di kotak teks Kolom, klik kanan pada nama kolom terakhir yang terdaftar untuk membuka menu konteks dan pilih Edit Kolom. Perhatikan bahwa jika kolom terakhir dalam definisi TABEL adalah KUNCI UTAMA, maka pertama-tama perlu menambahkan kolom baru dan kemudian mengedit jenis kolom dari kolom baru untuk menambahkan definisi KUNCI ASING. Di dalam kotak Jenis Kolom, tambahkan koma dan

FOREIGN KEY (parent_id) REFERENCES parent(id)

definisi setelah tipe data. Klik pada tombol Change dan kemudian klik tombol Yes pada kotak dialog Dangerous Operation.

Referensi: Manajer Sqlite

Baso
sumber
2

Anda dapat mencoba ini:

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

Pada dasarnya Anda tidak bisa tetapi Anda bisa melewati situasinya.

Cara yang benar untuk menambahkan batasan kunci asing ke tabel yang ada adalah dengan perintah berikut.

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

Kemudian salin data parent_Id ke newCol lalu hapus kolom Parent_Id . Karenanya, tidak perlu tabel sementara.

saeed khalafinejad
sumber
Sepertinya Anda tidak membaca pertanyaan itu dengan cermat. Masalahnya adalah menambahkan batasan asing saja, bukan menambahkan kolom dengan batasan.
Serigala
Nggak. Itu tidak menjawab pertanyaan yang diajukan.
MK
-4

Pertama menambahkan kolom di tabel anak Cidsebagai intkemudian alter tabledengan kode di bawah. Dengan cara ini Anda dapat menambahkan kunci asing Cidsebagai kunci utama dari tabel induk dan menggunakannya sebagai kunci asing di tabel anak ... semoga ini akan membantu Anda karena ini baik untuk saya:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Tariq Nawaz Khan
sumber
1
Ini tidak valid di SQLite. Juga ini adalah sintaks MS SQL.
StilesCrisis