MASUK JIKA TIDAK ADA PEMBARUAN BARU?

275

Saya telah menemukan beberapa "akan menjadi" solusi untuk klasik "Bagaimana cara menyisipkan catatan baru atau memperbarui jika sudah ada" tetapi saya tidak dapat membuat mereka bekerja di SQLite.

Saya memiliki tabel yang didefinisikan sebagai berikut:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

Yang ingin saya lakukan adalah menambahkan catatan dengan Nama yang unik. Jika Nama sudah ada, saya ingin memodifikasi bidang.

Adakah yang bisa memberi tahu saya cara melakukan ini?

SparkyNZ
sumber
3
"Sisipkan atau ganti" sama sekali berbeda dari "sisipkan atau perbarui"
Fattie

Jawaban:

320

Lihat http://sqlite.org/lang_conflict.html .

Anda menginginkan sesuatu seperti:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

Perhatikan bahwa bidang apa pun yang tidak ada dalam daftar sisipan akan disetel ke NULL jika baris sudah ada dalam tabel. Inilah sebabnya mengapa ada subselect untuk IDkolom: Dalam kasus penggantian pernyataan akan mengaturnya ke NULL dan kemudian ID baru akan dialokasikan.

Pendekatan ini juga dapat digunakan jika Anda ingin meninggalkan nilai bidang tertentu sendiri jika baris dalam case pengganti tetapi setel field ke NULL dalam kasus insert.

Misalnya, dengan asumsi Anda ingin pergi Seensendiri:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));
janm
sumber
109
Kesalahan "menyisipkan atau mengganti" berbeda dengan "menyisipkan atau memperbarui". Untuk jawaban yang valid, lihat stackoverflow.com/questions/418898/…
rds
12
@ rds Tidak, ini tidak salah karena pertanyaan ini mengatakan "ubah field" dan kunci utama bukan bagian dari daftar kolom, tetapi semua bidang lainnya adalah. Jika Anda akan memiliki kotak sudut di mana Anda tidak mengganti semua nilai bidang, atau jika Anda bermain-main dengan kunci utama Anda harus melakukan sesuatu yang berbeda. Jika Anda memiliki kumpulan bidang baru yang lengkap, pendekatan ini baik-baik saja. Apakah Anda memiliki masalah khusus yang tidak dapat saya lihat?
janm
9
Ini valid jika Anda mengetahui semua nilai baru untuk semua bidang. Jika pengguna hanya memperbarui, katakanlah, Levelpendekatan ini tidak dapat diikuti.
rds
4
Yang benar . Jawaban lainnya relevan, tetapi pendekatan ini berlaku untuk memperbarui semua bidang (tidak termasuk kunci).
Ярослав Рахматуллин
2
Ya, ini benar-benar salah. Apa yang akan terjadi jika saya hanya ingin memperbarui nilai kolom tunggal pada Konflik dari yang baru. Dalam kasus di atas semua data lain akan diganti dengan yang baru yang tidak benar.
Mrug
85

Anda harus menggunakan INSERT OR IGNOREperintah yang diikuti oleh UPDATEperintah: Dalam contoh berikut nameadalah kunci utama:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

Perintah pertama akan memasukkan catatan. Jika catatan ada, itu akan mengabaikan kesalahan yang disebabkan oleh konflik dengan kunci utama yang ada.

Perintah kedua akan memperbarui catatan (yang sekarang pasti ada)

moshik
sumber
6
pada saat mana ia akan diabaikan? kapan nama dan umur keduanya sama?
mou
Ini harus menjadi solusi ... jika Anda menggunakan pemicu apa pun pada sisipan, jawaban yang diterima akan menyala setiap saat. Ini tidak dan hanya melakukan pembaruan
PodTech.io
1
Mengabaikan hanya berdasarkan nama. Ingatlah bahwa hanya kolom "nama" yang merupakan kunci utama.
Gabriel Ferrer
3
Ketika catatan baru, maka pembaruan tidak perlu tetapi akan tetap dieksekusi, yang mengarah ke kinerja yang buruk?
MarcG
Bagaimana cara menjalankan pernyataan yang sudah disiapkan untuk ini?
prashant
65

Anda perlu menetapkan batasan pada tabel untuk memicu " konflik " yang kemudian Anda selesaikan dengan melakukan penggantian:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

Maka Anda dapat mengeluarkan:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

"SELECT * FROM data" akan memberi Anda:

2|2|2|3.0
3|1|2|5.0

Perhatikan bahwa data.id adalah "3" dan bukan "1" karena REPLACE melakukan DELETE dan INSERT, bukan UPDATE. Ini juga berarti bahwa Anda harus memastikan bahwa Anda mendefinisikan semua kolom yang diperlukan atau Anda akan mendapatkan nilai NULL yang tidak terduga.

gaspard
sumber
33

Perbarui dulu. Jika jumlah baris yang terpengaruh = 0, maka masukkan. Yang paling mudah dan cocok untuk semua RDBMS .

Burçin
sumber
11
Dua operasi seharusnya tidak ada masalah dengan transaksi di tingkat isolasi yang tepat, terlepas dari database.
Januari
5
Insert or Replacebenar-benar lebih disukai.
MPelletier
1
Saya sangat berharap dokumentasi TI memuat lebih banyak contoh. Saya sudah mencoba ini di bawah dan tidak berhasil (sintaks saya jelas salah). Ada ide tentang apa yang seharusnya? INSERT INTO Book (Name, TypeID, Level, Seen) VALUES ('Superman', '2', '14', '0') TENTANG PENGGANTIAN KONFLIK Buku (Nama, TypeID, Level, Seen) VALUES ('Superman', ' 2 ',' 14 ',' 0 ')
SparkyNZ
6
Juga bagaimana jika nilai-nilai baris persis sama, maka jumlah baris yang terpengaruh akan menjadi nol, dan baris duplikat baru akan dibuat.
barkside
2
+1, karena MASUKKAN ATAU PENGGANTIAN akan menghapus baris asli pada konflik dan jika Anda tidak mengatur semua kolom, Anda akan kehilangan nilai asli
Pavel Machyniak
20

INSERT OR REPLACE akan mengganti bidang lain ke nilai default.

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

Jika Anda ingin melestarikan bidang lainnya

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

atau menggunakan UPSERT (sintaks ditambahkan ke SQLite dengan versi 3.24.0 (2018-06-04))

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

The excluded.awalan sama dengan nilai di VALUES.

Steely Wing
sumber
16

Upsert adalah apa yang Anda inginkan. UPSERTsintaks telah ditambahkan ke SQLite dengan versi 3.24.0 (2018-06-04).

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

Berhati-hatilah bahwa pada titik ini kata "UPSERT" yang sebenarnya bukan bagian dari sintaks yang tidak aktif.

Sintaks yang benar adalah

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

dan jika Anda melakukan INSERT INTO SELECT ...kebutuhan pilih Anda setidaknya WHERE trueuntuk menyelesaikan ambiguitas parser tentang token ONdengan sintaks join.

Berhati-hatilah bahwa INSERT OR REPLACE...akan menghapus catatan sebelum memasukkan yang baru jika harus diganti, yang bisa jadi buruk jika Anda memiliki kaskade kunci asing atau pemicu penghapusan lainnya.

KawanJoecool
sumber
Itu ada dalam dokumentasi dan saya memiliki versi terbaru dari sqlite yaitu 3.25.1 tetapi tidak berfungsi untuk saya
Bentaiba Miled Basma
apakah Anda mencoba dua pertanyaan di atas kata demi kata?
Kamerad Joecool
Perhatikan bahwa Ubuntu 18.04 hadir dengan SQLite3 v3.22 , bukan 3.25, sehingga tidak mendukung UPSERTsintaksis.
starbeamrainbowlabs
Terima kasih untuk versi referensi pada fitur ini!
Matteo
6

Jika Anda tidak memiliki kunci utama, Anda dapat menyisipkan jika tidak ada, kemudian lakukan pembaruan. Tabel harus mengandung setidaknya satu entri sebelum menggunakan ini.

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';
matt
sumber
Ini adalah satu-satunya solusi yang berfungsi untuk saya tanpa membuat entri rangkap. Mungkin karena tabel yang saya gunakan tidak memiliki kunci utama, dan memiliki 2 kolom tanpa nilai default. Meskipun ini merupakan solusi yang panjang, ia melakukan pekerjaan dengan baik dan bekerja seperti yang diharapkan.
Inbar Rose
4

Saya yakin Anda menginginkan UPSERT .

"INSERT ATAU REPLACE" tanpa tipu daya tambahan dalam jawaban itu akan mengatur ulang setiap bidang yang tidak Anda tentukan ke NULL atau nilai default lainnya. (Perilaku INSERT ATAU PENGGANTIAN ini tidak seperti UPDATE; ini persis seperti INSERT, karena sebenarnya INSERT; namun jika yang Anda inginkan adalah UPDATE-jika-ada, Anda mungkin menginginkan semantik UPDATE dan akan terkejut dengan hasil yang sebenarnya.)

Tipuan dari implementasi UPSERT yang disarankan pada dasarnya adalah dengan menggunakan INSERT atau REPLACE, tetapi tentukan semua bidang, menggunakan klausa SELECT tertanam untuk mengambil nilai saat ini untuk bidang yang tidak ingin Anda ubah.

metamatt
sumber
1

Saya pikir ada baiknya menunjukkan bahwa mungkin ada beberapa perilaku tak terduga di sini jika Anda tidak benar-benar memahami bagaimana PRIMARY KEY dan UNIK berinteraksi.

Sebagai contoh, jika Anda ingin menyisipkan catatan hanya jika bidang NAME saat ini tidak diambil, dan jika itu, Anda ingin pengecualian kendala untuk memberi tahu Anda, maka MASUKKAN ATAU PENGGANTIAN tidak akan melempar dan pengecualian dan sebaliknya akan menyelesaikan kendala UNIK itu sendiri dengan mengganti catatan yang bertentangan (catatan yang ada dengan NAMA yang sama ). Gaspard's menunjukkan ini dengan sangat baik dalam jawabannya di atas.

Jika Anda ingin pengecualian kendala untuk api, Anda harus menggunakan pernyataan INSERT , dan bergantung pada perintah UPDATE terpisah untuk memperbarui catatan setelah Anda tahu nama tidak diambil.

Matt Matthias
sumber