Knex: Menambahkan kemampuan tipe upser

Dibuat pada 30 Agu 2013  ·  54Komentar  ·  Sumber: knex/knex

Dibawa oleh @adamscybot di tgriesser/rak buku#55 - ini bisa menjadi fitur yang bagus untuk ditambahkan.

feature request

Komentar yang paling membantu

@NicolajKN Anda tidak boleh menggunakan toString(), ini dapat menyebabkan banyak jenis masalah dan tidak akan melewatkan nilai melalui binding ke DB (potensi lubang keamanan injeksi SQL).

Sama ini dilakukan dengan benar akan menjadi seperti ini:

const query = knex('account').insert(accounts);
const safeQuery = knex.raw('? ON CONFLICT DO NOTHING', [query]);

Semua 54 komentar

Saya setuju, ini _would_ menjadi fitur yang bagus!

:+1:

:+1:

Saya mengimpor beberapa data dari CSV dan ada kemungkinan besar bahwa beberapa catatan tumpang tindih dari impor terakhir (yaitu terakhir kali diimpor dari 1 Januari hingga 31 Mei, kali ini mengimpor dari 31 Mei hingga 18 Juni).

Untungnya sistem pihak ketiga memberikan id unik yang andal.

Apa cara terbaik untuk memasukkan catatan baru dan memperbarui yang lama?

Saya belum mencobanya, tetapi saya berpikir bahwa itu akan menjadi seperti ini:

var ids = records.map(function (json) { return json.id })
  ;

Records.forge(ids).fetchAll().then(function () {
  records.forEach(function (record) {
    // now the existing records are loaded in the collection ?
    Object.keys(record).forEach(function (key) {
      Records.forge(record.id).set(key, record[key]);
    });
  });
  Records.invokeThen('save').then(function () {
    console.log('Records have been either inserted or updated');
  });
});

Juga, terkadang barang yang saya simpan disimpan oleh nilai id tertentu, seperti hash. Dalam kasus itu saya hanya ingin menambah atau mengganti data.

Saya tidak selalu menggunakan SQL sebagai SQL tradisional. Seringkali saya menggunakannya sebagai NoSQL hybrid dengan manfaat pemetaan dan indeks hubungan yang jelas.

:+1:

Hai,
ada berita tentang fitur baru ini?

Atau dapatkah seseorang merekomendasikan contoh, yang menunjukkan cara mensimulasikan fungsionalitas ini untuk mysql?

Terima kasih

Saat ini saya sedang melakukannya dengan raw , tetapi saya bekerja keras agar ini segera tersedia di sini.

Postgres baru saja mengimplementasikan dukungan upsert :+1:

http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=168d5805e4c08bed7b95d351bf097cff7c07dd65

https://news.ycombinator.com/item?id=9509870

Sintaksnya adalah INSERT ... ON CONFLICT DO UPDATE

Saya sedang mencari cara untuk melakukan REPLACE INTO di MySql dan menemukan permintaan fitur ini. Karena REPLACE dan INSERT memiliki sintaks yang sama persis di MySql, saya membayangkannya lebih mudah diimplementasikan daripada ON DUPLICATE KEY UPDATE . Apakah ada rencana untuk mengimplementasikan REPLACE ? Akankah PR menjadi sesuatu yang berharga?

Adakah pembaruan tentang ini, terutama dengan PostreSQL 9.5?

Saya pikir satu pertanyaan penting adalah apakah akan mengekspos tanda tangan metode upsert yang sama atau tidak untuk dialek yang berbeda, seperti PostgreSQL dan MySQL. Di Sequelize, masalah telah diangkat mengenai nilai pengembalian upsert : https://github.com/sequelize/sequelize/issues/3354.

Saya menyadari bahwa beberapa metode perpustakaan KnexJS memiliki perbedaan mengenai nilai yang dikembalikan dalam konteks dialek yang berbeda (seperti insert , di mana larik id yang dimasukkan pertama dikembalikan untuk Sqlite dan MySQL, sedangkan larik semua id yang dimasukkan dikembalikan dengan PostgreSQL).

Menurut dokumentasi, sintaks INSERT ... ON DUPLICATE KEY UPDATE di MySQL memiliki perilaku berikut (http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html):

Dengan ON DUPLICATE KEY UPDATE, nilai baris yang terpengaruh per baris adalah 1 jika baris dimasukkan sebagai baris baru, 2 jika baris yang ada diperbarui, dan 0 jika baris yang ada disetel ke nilai saat ini.

Sementara di PostgreSQL (http://www.postgresql.org/docs/9.5/static/sql-insert.html):

Setelah berhasil diselesaikan, perintah INSERT mengembalikan tag perintah dari formulir

INSERT oid count

Hitungan adalah jumlah baris yang dimasukkan atau diperbarui. Jika count tepat satu, dan tabel target memiliki OID, maka oid adalah OID yang ditetapkan ke baris yang disisipkan. Baris tunggal harus telah dimasukkan daripada diperbarui. Jika tidak, oid adalah nol.

Jika perintah INSERT berisi klausa RETURNING, hasilnya akan mirip dengan pernyataan SELECT yang berisi kolom dan nilai yang ditentukan dalam daftar RETURNING, dihitung pada baris yang disisipkan atau diperbarui oleh perintah.

Dalam hal ini, nilai yang dikembalikan dapat diubah dengan klausa RETURNING .

Pikiran?

Saya monyet menambal Client_PG untuk menambahkan metode "onConflict" untuk dimasukkan. Misalkan kita ingin memasukkan kredensial github oauth, kita dapat menulis kueri seperti ini:

const profile = {
    access_token: "blah blah",
    username: "foobar",
    // ... etc
  }

  const oauth = {
    uid: "13344398",
    provider: "github",
    created_at: new Date(),
    updated_at: new Date(),
    info: profile,
  };

  // todo: add a "timestamp" method

const insert = knex("oauths").insert(oauth).onConflict(["provider", "uid"],{
  info: profile,
  updated_at: new Date(),
});

console.log(insert.toString())

Array nama kolom menentukan batasan keunikan.

insert into "authentications" ("created_at", "info", "provider", "uid", "updated_at") values ('2016-02-14T14:42:18.342+08:00', '{\"access_token\":\"blah blah\",\"username\":\"foobar\"}', 'github', '13344398', '2016-02-14T14:42:18.342+08:00') on conflict ("provider", "uid")  do update set "info" = '{\"access_token\":\"blah blah\",\"username\":\"foobar\"}', "updated_at" = '2016-02-14T14:42:18.343+08:00'

Lihat Gist: https://Gist.github.com/hayeah/1c8d642df5cfeabc2a5b untuk patch monyet.

Ini adalah eksperimen yang sangat retas... jadi jangan menyalin & menempelkan patch monyet ke kode produksi Anda : p

Masalah yang diketahui:

  • Patch monyet di QueryBuilder memengaruhi semua dialek, karena Client_PG tidak mengkhususkan pembuatnya.
  • Tidak mendukung pembaruan mentah seperti count = count + 1
  • onConflict mungkin harus dibuang jika metode kueri tidak dimasukkan.

Masukan?

@hayeah Saya suka pendekatan Anda dan itu cocok dengan Postgres. Saya akan mencoba tambalan monyet Anda dalam sebuah proyek untuk melihat apakah saya dapat secara empiris mendeteksi masalah apa pun selain yang Anda tunjukkan.

Saran Sintaks: knex('table').upsert(['col1','col2']).insert({...}).update({...}); di mana upsert akan digunakan dalam pernyataan kondisi. Dengan cara ini tidak spesifik db.

Ringkasan berbagai implementasi upsert dapat ditemukan di https://en.wikipedia.org/wiki/Merge_ (SQL)

Saya tertarik untuk memiliki kemampuan ini juga. Use case: membangun sistem yang bergantung pada banyak data luar dari layanan luar; Saya secara berkala melakukan polling untuk data yang saya simpan ke db MySQL lokal. Mungkin akan menggunakan knex.raw untuk saat ini.

Juga tertarik, tetapi dalam kasus penggunaan saya harus membuatnya bekerja dengan cara yang tidak didasarkan pada konflik, karena kolom tidak selalu memiliki batasan 'unik' - cukup perbarui entri yang cocok dengan kueri jika ada, jika tidak masukkan baris baru.

@haywirez Saya ingin tahu mengapa tidak ada batasan unik? Apakah Anda tidak akan terkena kondisi balapan?

@hayeah Saya memiliki kasus penggunaan khusus dengan data jangka waktu, menyimpan entri yang memiliki nilai terkait dengan hari tertentu. Oleh karena itu saya memasukkan dan memperbarui entri yang memiliki "kunci gabungan" dari stempel waktu (hari) yang cocok, dan dua ID lain yang sesuai dengan PK di tabel lain. Dalam jendela 24 jam, saya harus memasukkannya, atau memperbaruinya dengan hitungan terbaru.

Ini akan menjadi fitur yang bagus untuk dimiliki!

Halo semua yang pernah berkomentar di sini. Saya menambahkan PR Tolong beri label.

Senang mengambil PR untuk menambahkan fungsi ini, tetapi saya ingin melihat diskusi tentang API yang diinginkan di sini terlebih dahulu.

PS.

^ Setuju.

Saya akan menghapus komentar seperti ini, jika Anda ingin menambahkan +1, lakukan dengan reaksi emoji kecil.

Saya memiliki sedikit masalah dengan susunan batasan kolom seperti pada contoh @willfarrell dan @hayeah . Tidak yakin apakah contoh ini dapat mendukung properti json . Apakah ada alasan mengapa tidak satu pun dari proposal ini yang menyertakan pernyataan di mana/"permintaan" yang tepat untuk mencocokkan catatan?

proposal 1

knex('table')
  .where('id', '=', data.id)
  .upsert(data)

proposal 2

knex('table')
  .upsertQuery(knex => {
    return knex('table')
      .where('id', '=', data.id)
  })
  .upsertUpdate(knex => {
    return knex('table')
      .insert(data)
  })

proposal 3

knex('table')
  .where('id', '=', data.id)
  .insert(data)
  .upsert() // or .onConflictDoUpdate()

Saya paling condong ke sesuatu seperti 3.

Hanya untuk menambahkan , inilah cara mongodb melakukannya .

db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)

@reggi Saya percaya patch monyet saya kompatibel dengan where ...

@reggi Saya tidak mengerti maksud Anda.
Bisakah Anda menguraikan lebih lanjut tentang fungsionalitas mana yang hilang dari pendekatan yang diusulkan dalam contoh @willfarrell dan @hayeah .
Mengapa Anda membutuhkan where sama sekali?
Ini hanya operasi insert .

@reggi Contoh MongoDB yang Anda berikan berbunyi "Pertama coba UPDATE WHERE ... kemudian lakukan INSERT jika tidak ada dokumen yang cocok dengan kueri" sedangkan SQL UPSERT berbunyi "INSERT INTO ... MEMPERBARUI jika baris dengan kunci utama ini sudah ada" .
Jadi, saya kira, Anda sedang berbicara tentang "upsert" yang sama sekali berbeda dari yang diterapkan dalam database SQL.

Saya akan mengusulkan API ini:

knex.createTable('test')
   .bigserial('id')
   .varchar('unique').notNull().unique()
   .varchar('whatever')

knex.table('test').insert(object, { upsert: ['unique'] })

Fungsi .insert() akan menganalisis parameter kedua.
Jika itu string, maka itu adalah parameter returning yang lama.
Jika itu sebuah objek, maka itu adalah parameter options yang memiliki options.returning dan options.upsert , di mana options.upsert adalah daftar kunci unik (bisa > 1 in kasus kendala kunci unik majemuk).
Kemudian kueri SQL dibuat yang hanya mengecualikan kunci utama dan semua kunci options.upsert dari object (via clone(object) && delete cloned_object.id && delete cloned_object.unique ) dan kemudian menggunakan cloned_object yang dilucuti kunci utama (dan unik) untuk membuat klausa SET di bagian kedua dari kueri SQL: ... ON CONFLICT DO UPDATE SET [iterate cloned_object] .

Saya kira itu akan menjadi solusi paling sederhana dan tidak ambigu yang homogen dengan API saat ini.

@slavafomin @ScionOfBytes Sepertinya API belum disetujui. Itu akan menjadi langkah selanjutnya dan kemudian seseorang yang suka menerapkannya dapat melakukannya. Jadi tidak ada berita.

hal. Saya mulai menghapus permintaan berita tambahan jika tidak ada yang mencegah utas ini diisi dengan spam permintaan berita dan pesan lain yang kurang terkait.

@amir-s Saya setuju, tetapi subjek dari masalah ini adalah kemampuan upsert.

IMO, masalah sebenarnya bukanlah API, tetapi cara yang tidak biasa untuk melakukan upsert di setiap database.

MySQL (ON DUPLICATE KEY UPDATE) dan PostgreSQL 9.5+ (ON CONFLICT DO UPDATE) mendukung upsert secara default.

MSSQL dan Oracle dapat mendukungnya dengan klausa gabungan, tetapi knex harus mengetahui nama kolom konflik untuk dapat membuat kueri.

-- in this case the conflict column is 'a'
merge into target
using (values (?)) as t(a)
on (t.a = target.a)
when matched then
  update set b = ?
when not matched then
  insert (a, b) values (?, ?);

Tapi SQLite tidak. Kami membutuhkan dua kueri untuk mensimulasikan upsert

-- 'a' is the conflict column
insert or ignore into target (a, b) values (?, ?);
update target set b = ?2 where changes() = 0 and a = ?1;

Atau menggunakan INSERT OR REPLACE , alias REPLACE

-- replace will delete the matched row then add a new one with the given data
replace into target (a, b) values (?, ?);

Sayangnya, jika tabel target memiliki lebih banyak kolom daripada a dan b, nilainya akan diganti secara default

insert or replace into target (a, b, c) values (?, ?, (select c from target where a = ?1))

Solusi lain menggunakan CTE, lihat jawaban stackoverflow ini

Saya telah membahas masalah ini beberapa kali untuk mencari upsert Postgres berbasis knex. Jika ada orang lain yang membutuhkan ini, inilah cara melakukannya. Saya telah menguji ini terhadap kunci unik tunggal dan komposit.

Pengaturan

Buat batasan kunci unik pada tabel menggunakan di bawah ini. Saya membutuhkan batasan kunci komposit:

table.unique(['a', 'b'])

Fungsi:

(edit: diperbarui untuk menggunakan binding parameter mentah)

const upsert = (params)=> {
  const {table, object, constraint} = params;
  const insert = knex(table).insert(object);
  const update = knex.queryBuilder().update(object);
  return knex.raw(`? ON CONFLICT ${constraint} DO ? returning *`, [insert, update]).get('rows').get(0);
};

Penggunaan

const objToUpsert = {a:1, b:2, c:3}

upsert({
    table: 'test',
    object: objToUpsert,
    constraint: '(a, b)',
})

Jika batasan Anda bukan gabungan maka, tentu saja, satu baris itu hanya constraint: '(a)' .

Ini akan mengembalikan objek yang diperbarui atau objek yang dimasukkan.

Catatan tentang indeks nullable gabungan

Jika Anda memiliki indeks komposit (a,b) dan b dapat dibatalkan, maka nilai (1, NULL) dan (1, NULL) dianggap saling unik oleh Postgres (Saya tidak mengerti salah satu). Jika ini adalah kasus penggunaan Anda, Anda harus membuat indeks unik parsial dan kemudian menguji null sebelum upsert untuk menentukan batasan mana yang akan digunakan. Berikut cara membuat indeks unik parsial: CREATE UNIQUE INDEX unique_index_name ON table (a) WHERE b IS NULL . Jika pengujian Anda menentukan bahwa b adalah null, maka Anda harus menggunakan batasan ini di upsert Anda: constraint: '(a) WHERE b IS NULL' . Jika a juga dapat dibatalkan, saya kira Anda memerlukan 3 indeks unik dan 4 cabang if/else (meskipun ini bukan kasus penggunaan saya, jadi saya tidak yakin).

Inilah javascript yang dikompilasi .

Semoga seseorang menemukan ini berguna. @elhigu Adakah komentar tentang penggunaan knex().update(object) ? (edit: nevermind - melihat peringatan - menggunakan knex.queryBuilder() sekarang)

@timhuff terlihat bagus, satu hal yang harus diubah adalah meneruskan setiap kueri ke mentah, menggunakan pengikatan nilai. Jika tidak, query.toString() digunakan untuk merender setiap bagian kueri dan membuka kemungkinan lubang injeksi ketergantungan (queryBuilder.toString() tidak seaman meneruskan parameter ke driver sebagai binding)..

@elhigu Tunggu... query.toString() tidak menggunakan binding? Bisakah Anda memberi saya contoh kasar dari modifikasi yang Anda rekomendasikan? Saya... mungkin memiliki banyak kode untuk diperbarui.

Menemukan bagian dari dokumentasi yang berlabel Raw bindings . Memperbarui sekarang saya telah memperbarui contoh. Saya pikir query.toString aman. Akan lebih baik jika bagian dokumentasi diberi label seperti "Cara membuat kueri yang tidak aman". Hanya ada sedikit larangan dan dengan cara itu orang dapat menggunakan perpustakaan dengan mengetahui "selama saya tidak melakukan hal-hal ini, saya aman".

Saya telah membuat upsert berikut: https://Gist.github.com/adnanoner/b6c53482243b9d5d5da4e29e109af9bd
Ini menangani upser tunggal dan batch. Saya mengadaptasinya sedikit dari @plurch . Perbaikan selalu dihargai :)

Untuk apa nilainya, saya telah menggunakan format ini:

Sunting: Diperbarui agar aman bagi siapa saja yang mencari ini. Terima kasih @elhigu

const query = knex( 'account' ).insert( accounts );
const safeQuery = knex.raw( '? ON CONFLICT DO NOTHING', [ query ]);

@NicolajKN Anda tidak boleh menggunakan toString(), ini dapat menyebabkan banyak jenis masalah dan tidak akan melewatkan nilai melalui binding ke DB (potensi lubang keamanan injeksi SQL).

Sama ini dilakukan dengan benar akan menjadi seperti ini:

const query = knex('account').insert(accounts);
const safeQuery = knex.raw('? ON CONFLICT DO NOTHING', [query]);

Diskusi yang dihapus tentang masalah yang tidak terkait.

@elhigu Tunggu, bukankah kueri penyisipan itu langsung dieksekusi setelah dibuat? Bukankah itu menciptakan kondisi balapan?

@cloutiertyler Anda tidak berbicara dengan saya, tetapi mungkin saya dapat menghemat waktu @elhigu di sini. Tak satu pun dari kueri ini akan dieksekusi. Pernyataan knex('account').insert(accounts) tidak mengeksekusi kueri. Itu tidak dieksekusi sampai data benar-benar dipanggil (misalnya melalui .then ). Dia mengirimkannya ke knex.raw('? ON CONFLICT DO NOTHING', [query]) yang akan memanggil query.toString() , yang hanya mengubah kueri menjadi pernyataan SQL yang akan dieksekusi.

@timhuff Terima kasih Tim, saya berasumsi harus seperti itu, tapi itu bukan perilaku normal untuk sebuah janji. Janji biasanya dilaksanakan setelah penciptaan. Alasan saya bertanya adalah bahwa saya sering mendapatkan kesalahan yang mengatakan "Koneksi Dihentikan" ketika saya mencoba menjalankan upsert ini. Setelah saya beralih untuk menghapus sisipan dan membuat kueri yang sepenuhnya mentah, mereka pergi. Sepertinya itu akan konsisten dengan kondisi balapan.

knex QueryBuilder s bukan Promise s. Saat Anda mulai menulis kueri knex, Anda tetap berada di "knexland". Semua yang Anda lakukan kurang lebih hanya mengonfigurasi spesifikasi JSON dari kueri yang ingin Anda buat. Jika Anda menjalankan .toString , itu membangunnya dan mengeluarkannya. Itu tidak menjadi ( bluebird ) Janji sampai Anda menjalankan salah satu dari ini di atasnya. Anda mungkin tertarik untuk menggunakan .return jika Anda ingin segera mengeksekusi pernyataan tersebut.

Ah, begitu, nah itu menghilangkan kebingungan saya. Terima kasih atas klarifikasi dan petunjuknya! Masalah saya harus ada di tempat lain.

Selain itu, fakta bahwa itu tidak segera berjalan sering berguna. Terkadang Anda ingin menyebarkannya, mengonfigurasinya, sebelum mengeksekusi. Ada juga situasi di mana Anda dapat melakukan hal-hal seperti...

const medicalBuildings = knex.select('building_id').from('buildings').where({type: 'medical'})
const medicalWorkers = knex.select().from('workers').whereIn('building', medicalBuildings)

(contoh yang sangat dibuat-buat tetapi mari kita jalankan dengan itu)

Saya sebenarnya tidak ingin menjalankan pernyataan pertama itu - itu hanya bagian dari pernyataan kedua saya.

Belum lagi jika semua pembuat kueri akan mengeksekusi saat pembuatan, kueri pola pembuat akan dipicu sebelum pembangunan selesai. Itu tidak akan berfungsi sama sekali tanpa memiliki beberapa metode terminator (yang mengeksekusi kueri).

@elhigu Maksud saya... Saya kira Anda selalu bisa menjalankannya di centang berikutnya, bukan? Saya tidak menyarankan bahwa itu adalah ide yang bagus, tetapi berapa banyak kueri yang benar-benar dibuat dan dieksekusi pada kutu yang berbeda?

@timhuff Saya belum memikirkan itu. Ya saya pikir itu mungkin juga. Saya menemukan kasus yang cukup umum di mana seseorang mulai membuat kueri, kemudian mengambil beberapa data asinkron dan terus membangun lebih banyak. Saya tidak melakukan itu sangat sering sekalipun.

@lukewlms bahwa 'execute()' -seperti metode disebut '.then()' Anda dapat selalu memanggilnya ketika Anda ingin menjalankan kueri dan mendapatkan janji. Begitulah cara kerja 'thenable' dan dijelaskan dalam janji spec. Ini adalah salah satu konsep penting dan banyak digunakan dalam javascript ketika berhadapan dengan janji dan async/menunggu (yang cukup banyak hanya jalan pintas yang dimuliakan untuk Promise.resolve dan .then). Juga jika Anda menjalankan kueri tanpa menangani hasil, Anda mencari masalah seperti aplikasi mogok.

Sebenarnya lebih baik ikuti saja PR ini tentang implementasi fitur upsert https://github.com/tgriesser/knex/pull/2197 itu sudah API yang merancang cara kerjanya. Di utas ini sebenarnya tidak ada informasi berguna yang belum disebutkan dalam komentar PR itu. Jika diperlukan (PR ditutup dan tidak pernah selesai) mari buka masalah baru untuk yang satu ini dengan deskripsi API tambahan.

@elhigu Terima kasih atas perhatiannya! Saya tidak menyadari thread itu. Senang mendengar kami membuat kemajuan pada upsert yang akan datang ke API. Sepertinya 6 bulan yang lalu gagal 1 dari 802 tes sehingga tidak pernah lulus travis-ci. Apakah 1 kasus uji yang gagal itu satu-satunya hal yang mencegah ini menjadi bagian dari knex API?

@timhuff hanya ada implementasi awal yang dilakukan, itu harus sepenuhnya ditulis ulang. Bagian terpenting dari PR itu adalah desain API umum, yang dapat didukung oleh sebagian besar dialek. Jadi fitur itu muncul ketika seseorang baru saja memutuskan untuk mengimplementasikan API itu. Jika tidak ada orang lain yang melakukan itu dan suatu hari saya memiliki waktu ekstra atau sangat membutuhkannya, saya akan melakukannya sendiri. Itu adalah salah satu fitur terpenting yang saya ingin knex dapatkan (selain bergabung dalam pembaruan).

@elhigu Terima kasih telah mengisi saya. Saya harus membaca kemajuan di sini ketika saya punya sedikit waktu lagi.

Saya tidak yakin apakah ini membantu siapa pun atau jika saya hanya seorang pemula, tetapi untuk solusi dari @timhuff saya harus membungkus batasan saya dalam tanda kutip karena saya mendapatkan kesalahan sintaksis kueri.

const contraint = '("a", "b")'

Saya telah membahas masalah ini beberapa kali untuk mencari upsert Postgres berbasis knex. Jika ada orang lain yang membutuhkan ini, inilah cara melakukannya. Saya telah menguji ini terhadap kunci unik tunggal dan komposit.

Pengaturan

Buat batasan kunci unik pada tabel menggunakan di bawah ini. Saya membutuhkan batasan kunci komposit:

table.unique(['a', 'b'])

Fungsi:

(edit: diperbarui untuk menggunakan binding parameter mentah)

const upsert = (params)=> {
  const {table, object, constraint} = params;
  const insert = knex(table).insert(object);
  const update = knex.queryBuilder().update(object);
  return knex.raw(`? ON CONFLICT ${constraint} DO ? returning *`, [insert, update]).get('rows').get(0);
};

Penggunaan

const objToUpsert = {a:1, b:2, c:3}

upsert({
  table: 'test',
  object: objToUpsert,
  constraint: '(a, b)',
})

Jika batasan Anda bukan gabungan maka, tentu saja, satu baris itu hanya akan menjadi constraint: '(a)' .

Ini akan mengembalikan objek yang diperbarui atau objek yang dimasukkan.

Catatan tentang indeks nullable gabungan

Jika Anda memiliki indeks komposit (a,b) dan b dapat dibatalkan, maka nilai (1, NULL) dan (1, NULL) dianggap saling unik oleh Postgres (Saya tidak mengerti salah satu). Jika ini adalah kasus penggunaan Anda, Anda harus membuat indeks unik parsial dan kemudian menguji null sebelum upsert untuk menentukan batasan mana yang akan digunakan. Berikut cara membuat indeks unik parsial: CREATE UNIQUE INDEX unique_index_name ON table (a) WHERE b IS NULL . Jika pengujian Anda menentukan bahwa b adalah null, maka Anda harus menggunakan batasan ini di upsert Anda: constraint: '(a) WHERE b IS NULL' . Jika a juga dapat dibatalkan, saya kira Anda memerlukan 3 indeks unik dan 4 cabang if/else (meskipun ini bukan kasus penggunaan saya, jadi saya tidak yakin).

Inilah javascript yang dikompilasi .

Semoga seseorang menemukan ini berguna. @elhigu ~Ada komentar tentang penggunaan knex().update(object) ?~ (edit: nevermind - melihat peringatan - menggunakan knex.queryBuilder() sekarang)

Menghapus beberapa diskusi yang tidak terkait (tentang cara kerja janji/tenables).

Apakah ini ditambahkan?

Tidak. Ada permintaan fitur dan spesifikasi di https://github.com/knex/knex/issues/3186

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

zettam picture zettam  ·  3Komentar

marianomerlo picture marianomerlo  ·  3Komentar

saurabhghewari picture saurabhghewari  ·  3Komentar

rarkins picture rarkins  ·  3Komentar

mishitpatel picture mishitpatel  ·  3Komentar