Knex: Upsert-Typ-Fähigkeit hinzugefügt

Erstellt am 30. Aug. 2013  ·  54Kommentare  ·  Quelle: knex/knex

Von @adamscybot in tgriesser/bookshelf#55 aufgezogen - dies könnte ein nettes Feature sein, das hinzugefügt werden könnte.

feature request

Hilfreichster Kommentar

@NicolajKN Sie sollten toString() nicht verwenden, da dies viele Arten von Problemen verursachen kann und keine Werte durch Bindungen an DB weitergibt (potenzielle Sicherheitslücke durch SQL-Injektion).

Gleiches richtig gemacht wäre so:

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

Alle 54 Kommentare

Ich stimme zu, das _wäre_ ein nettes Feature!

:+1:

:+1:

Ich importiere einige Daten aus einer CSV-Datei und es besteht eine gute Chance, dass sich einige der Datensätze seit dem letzten Import überschneiden (dh das letzte Mal wurde vom 1. Januar bis 31. Mai importiert, diesmal vom 31. Mai bis 18. Juni).

Glücklicherweise weist das Drittsystem zuverlässig eindeutige IDs zu.

Was ist der beste Weg, um die neuen Datensätze einzufügen und die alten zu aktualisieren?

Ich habe es noch nicht ausprobiert, aber ich dachte, es wäre so etwas:

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');
  });
});

Manchmal wird das, was ich speichere, auch von einem bestimmten ID-Wert gespeichert, z. B. einem Hash. In diesen Fällen möchte ich nur die Daten hinzufügen oder ersetzen.

Ich verwende SQL nicht immer als herkömmliches SQL. Oft verwende ich es als hybrides NoSQL mit dem Vorteil einer klaren Zuordnung von Beziehungen und Indizes.

:+1:

Hallo,
gibt es Neuigkeiten zu dieser neuen Funktion?

Oder kann jemand Beispiele empfehlen, die zeigen, wie man diese Funktionalität für MySQL simuliert?

Vielen Dank

Im Moment mache ich es mit raw , aber ich arbeite hart daran, es hier bald verfügbar zu machen.

Postgres hat übrigens gerade Upsert-Unterstützung implementiert :+1:

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

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

Syntax ist INSERT ... ON CONFLICT DO UPDATE

Ich habe nach einer Möglichkeit gesucht, ein REPLACE INTO in MySql zu erstellen, und bin auf diese Feature-Anfrage gestoßen. Da REPLACE und INSERT in MySql genau die gleiche Syntax haben, würde ich mir vorstellen, dass es einfacher zu implementieren ist als ein ON DUPLICATE KEY UPDATE . Gibt es Pläne, ein REPLACE zu implementieren? Wäre eine PR etwas Wertvolles?

Irgendwelche Updates dazu, insbesondere mit PostreSQL 9.5?

Ich denke, eine wichtige Frage ist, ob dieselbe upsert -Methodensignatur für verschiedene Dialekte wie PostgreSQL und MySQL verfügbar gemacht werden soll oder nicht. In Sequelize wurde ein Problem bezüglich des Rückgabewerts von upsert gemeldet: https://github.com/sequelize/sequelize/issues/3354.

Mir ist klar, dass einige Methoden der KnexJS-Bibliothek Unterschiede hinsichtlich der Rückgabewerte im Kontext verschiedener Dialekte aufweisen (z. B. insert , wo ein Array der ersten eingefügten ID für Sqlite und MySQL zurückgegeben wird, während ein Array von all die eingefügten IDs werden mit PostgreSQL zurückgegeben).

Laut Dokumentation hat die INSERT ... ON DUPLICATE KEY UPDATE -Syntax in MySQL folgendes Verhalten (http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html):

Bei ON DUPLICATE KEY UPDATE ist der Wert der betroffenen Zeilen pro Zeile 1, wenn die Zeile als neue Zeile eingefügt wird, 2, wenn eine vorhandene Zeile aktualisiert wird, und 0, wenn eine vorhandene Zeile auf ihre aktuellen Werte gesetzt wird.

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

Bei erfolgreichem Abschluss gibt ein INSERT-Befehl ein Befehlstag des Formulars zurück

INSERT oid count

Die Anzahl ist die Anzahl der eingefügten oder aktualisierten Zeilen. Wenn count genau eins ist und die Zieltabelle OIDs hat, dann ist oid die OID, die der eingefügten Zeile zugewiesen ist. Die einzelne Zeile muss eingefügt und nicht aktualisiert worden sein. Andernfalls ist oid Null.

Wenn der INSERT-Befehl eine RETURNING-Klausel enthält, ähnelt das Ergebnis dem einer SELECT-Anweisung, die die Spalten und Werte enthält, die in der RETURNING-Liste definiert sind und anhand der durch den Befehl eingefügten oder aktualisierten Zeile(n) berechnet werden.

In diesem Fall können die Rückgabewerte mit der Klausel RETURNING geändert werden.

Die Gedanken?

Ich habe Client_PG gepatcht, um die „onConflict“-Methode zum Einfügen hinzuzufügen. Angenommen, wir möchten Github-Oauth-Anmeldeinformationen aktualisieren, können wir die Abfrage wie folgt schreiben:

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())

Das Array von Spaltennamen gibt die Einschränkung der Eindeutigkeit an.

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'

Siehe Gist: https://gist.github.com/hayeah/1c8d642df5cfeabc2a5b für den Monkey-Patch.

Dies ist ein super hackiges Experiment ... also kopieren Sie den Affen-Patch nicht genau und fügen Sie ihn in Ihren Produktionscode ein: p

Bekannte Probleme:

  • Der Monkey-Patch ist auf QueryBuilder und betrifft alle Dialekte, da Client_PG den Builder nicht spezialisiert.
  • Unterstützt kein Raw-Update wie count = count + 1
  • onConflict sollte wahrscheinlich auslösen, wenn die Abfragemethode nicht eingefügt wird.

Feedback?

@hayeah Ich mag deinen Ansatz und er passt zu Postgres. Ich werde Ihren Monkey-Patch in einem Projekt ausprobieren, um zu sehen, ob ich andere Probleme als die, auf die Sie hingewiesen haben, empirisch erkennen kann.

Syntaxvorschlag: knex('table').upsert(['col1','col2']).insert({...}).update({...}); wobei upsert die Bedingungsanweisung aufnehmen würde. Auf diese Weise ist es nicht DB-spezifisch.

Eine Zusammenfassung der verschiedenen Implementierungen von Upserts finden Sie unter https://en.wikipedia.org/wiki/Merge_ (SQL)

Ich interessiere mich auch für diese Fähigkeit. Anwendungsfall: Aufbau eines Systems, das auf viele externe Daten von einem externen Dienst angewiesen ist; Ich frage es regelmäßig nach Daten ab, die ich in einer lokalen MySQL-Datenbank speichere. Wird wahrscheinlich vorerst knex.raw verwenden.

Auch interessiert, aber in meinem Anwendungsfall müsste es auf eine Weise funktionieren, die nicht auf Konflikten basiert, da die Spalten nicht immer "eindeutige" Einschränkungen haben - aktualisieren Sie einfach Einträge, die der Abfrage entsprechen, falls vorhanden, andernfalls einfügen neue Reihen.

@haywirez Ich bin neugierig, warum es keine eindeutigen Einschränkungen gibt? Wären Sie nicht den Rennbedingungen ausgesetzt?

@hayeah Ich habe einen bestimmten Anwendungsfall mit Zeitfensterdaten, in dem Einträge gespeichert werden, deren Wert an einen bestimmten Tag gebunden ist. Daher füge ich Einträge ein und aktualisiere sie, die einen "kombinierten Schlüssel" mit einem übereinstimmenden (Tages-)Zeitstempel und zwei weitere IDs haben, die PKs in anderen Tabellen entsprechen. Innerhalb eines 24-Stunden-Fensters muss ich sie entweder einfügen oder mit den neuesten Zahlen aktualisieren.

Das wäre ein tolles Feature!

Hallo an alle die hier schon mal kommentiert haben. Ich füge ein PR-Bitte-Etikett hinzu.

Ich freue mich über eine PR, die diese Funktionalität hinzufügt, aber ich würde gerne zuerst eine Diskussion über die gewünschte API hier sehen.

PS.

^ Einverstanden.

Ich werde Kommentare wie diesen löschen, wenn Sie ein +1 hinzufügen möchten, tun Sie dies mit der kleinen Emoji-Reaktion.

Ich habe ein kleines Problem mit der Reihe von Spaltenbeschränkungen wie in den Beispielen von @willfarrell und @hayeah . Nicht sicher, ob diese Beispiele json -Eigenschaften unterstützen können. Gibt es einen Grund, warum keiner dieser Vorschläge where-Anweisungen/richtige "Abfragen" enthält, um mit dem Datensatz übereinzustimmen?

Vorschlag 1

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

Vorschlag 2

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

Vorschlag 3

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

Ich tendiere am meisten zu so etwas wie 3.

Nur um hinzuzufügen, hier ist, wie mongodb es macht .

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

@reggi Ich glaube, mein Monkey-Patch ist mit where kompatibel ...

@reggi Ich verstehe deinen Punkt nicht.
Können Sie näher darauf eingehen, welche Funktionalität in dem in den Beispielen von @willfarrell und @hayeah vorgeschlagenen Ansatz fehlt.
Warum brauchst du überhaupt where ?
Es ist nur eine insert Operation.

@reggi Das von Ihnen bereitgestellte MongoDB-Beispiel lautet "Versuchen Sie zuerst, WHERE zu aktualisieren ... und führen Sie dann ein INSERT durch, wenn kein Dokument mit der Abfrage übereinstimmt", während SQL UPSERT "INSERT INTO ... UPDATING" lautet, falls eine Zeile mit diesem Primärschlüssel bereits vorhanden ist. .
Ich denke, Sie sprechen also von einem ganz anderen "Upsert", als es in SQL-Datenbanken implementiert ist.

Ich würde diese API vorschlagen:

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

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

Die Funktion .insert() würde den zweiten Parameter analysieren.
Wenn es ein String ist, dann ist es der alte Parameter returning .
Wenn es sich um ein Objekt handelt, dann ist es ein options Parameter mit options.returning und options.upsert , wobei options.upsert eine Liste der eindeutigen Schlüssel ist (kann > 1 sein in Fall einer zusammengesetzten eindeutigen Schlüsselbeschränkung).
Dann wird eine SQL-Abfrage generiert, die einfach den Primärschlüssel und alle options.upsert -Schlüssel aus den object (über clone(object) && delete cloned_object.id && delete cloned_object.unique ) ausschließt und dann diese entfernten cloned_object verwendet die primären (und eindeutigen) Schlüssel zum Erstellen der SET -Klausel im zweiten Teil der SQL-Abfrage: ... ON CONFLICT DO UPDATE SET [iterate cloned_object] .

Ich denke, das wäre die einfachste und eindeutigste Lösung, die mit der vorliegenden API homogen ist.

@slavafomin @ScionOfBytes Sieht so aus, als hätte man sich noch nicht einmal auf die API geeinigt. Das wäre der nächste Schritt und dann kann es jemand machen, der Lust hat, es umzusetzen. Also keine Neuigkeiten.

p.s. Ich habe begonnen, alle zusätzlichen Nachrichtenanfragen zu löschen, wenn es keine gibt, um zu verhindern, dass dieser Thread mit Nachrichtenanfragen-Spam und anderen weniger verwandten Nachrichten gefüllt wird.

@amir-s Ich stimme zu, aber das Thema dieses Problems ist die Upsert-Fähigkeit.

IMO, das eigentliche Problem ist nicht die API, sondern die ungewöhnliche Art, Upserts in jeder Datenbank durchzuführen.

MySQL (ON DUPLICATE KEY UPDATE) und PostgreSQL 9.5+ (ON CONFLICT DO UPDATE) unterstützen standardmäßig Upsert.

MSSQL und Oracle können dies mit einer Zusammenführungsklausel unterstützen, knex sollte jedoch die Namen der Konfliktspalten kennen, um die Abfrage erstellen zu können.

-- 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 (?, ?);

Aber SQLite nicht. Wir benötigen zwei Abfragen, um den Upsert zu simulieren

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

Oder verwenden Sie INSERT OR REPLACE , auch bekannt als REPLACE

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

Wenn die Zieltabelle mehr Spalten als a und b hat, werden ihre Werte leider durch Standardwerte ersetzt

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

Eine weitere Lösung, die CTE verwendet, finden Sie in dieser Stackoverflow-Antwort

Ich bin auf der Suche nach einem knex-basierten Postgres-Upsert mehrmals auf dieses Problem gestoßen. Wenn jemand anderes dies braucht, hier ist, wie es geht. Ich habe dies sowohl mit einzelnen als auch mit zusammengesetzten eindeutigen Schlüsseln getestet.

Die Einrichtung

Erstellen Sie mithilfe der folgenden Anweisungen eine eindeutige Schlüsselbeschränkung für die Tabelle. Ich brauchte eine zusammengesetzte Schlüsselbeschränkung:

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

Die Funktion

(Bearbeiten: aktualisiert, um rohe Parameterbindungen zu verwenden)

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);
};

Verwendung

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

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

Wenn Ihre Einschränkung nicht zusammengesetzt ist, wäre diese eine Zeile natürlich nur constraint: '(a)' .

Dadurch wird entweder das aktualisierte Objekt oder das eingefügte Objekt zurückgegeben.

Ein Hinweis zu zusammengesetzten Nullable-Indizes

Wenn Sie einen zusammengesetzten Index (a,b) haben und b nullfähig ist, dann werden die Werte (1, NULL) und (1, NULL) von Postgres als gegenseitig eindeutig betrachtet (ich verstehe es nicht entweder). Wenn dies Ihr Anwendungsfall ist, müssen Sie einen partiellen eindeutigen Index erstellen und dann vor dem Upsert auf null testen, um zu bestimmen, welche Einschränkung verwendet werden soll. So erstellen Sie den teilweise eindeutigen Index: CREATE UNIQUE INDEX unique_index_name ON table (a) WHERE b IS NULL . Wenn Ihr Test feststellt, dass b null ist, müssen Sie diese Einschränkung in Ihrem Upsert verwenden: constraint: '(a) WHERE b IS NULL' . Wenn a auch nullable ist, würde ich vermuten, dass Sie 3 eindeutige Indizes und 4 if/else -Zweige benötigen (obwohl dies nicht mein Anwendungsfall ist, also bin ich mir nicht sicher).

Hier ist das kompilierte Javascript .

Hoffe, jemand findet das nützlich. @elhigu Irgendwelche Kommentare zur Verwendung von knex().update(object) ? (edit: egal - sah die Warnung - benutze jetzt knex.queryBuilder() )

@timhuff sieht gut aus, eine Sache, die geändert werden müsste, wäre, jede Abfrage mit Wertbindung an raw zu übergeben. Andernfalls wird query.toString() verwendet, um jeden Teil der Abfrage zu rendern, und es öffnet ein mögliches Abhängigkeitsinjektionsloch (queryBuilder.toString() ist nicht so sicher wie das Übergeben von Parametern an den Treiber als Bindungen).

@elhigu Warte... query.toString() verwendet keine Bindungen? Können Sie mir ein grobes Beispiel für die von Ihnen empfohlene Änderung geben? Ich ... habe möglicherweise eine Menge Code zu aktualisieren.

Fand den Teil der Dokumentation mit der Bezeichnung Raw Bindings . Jetzt aktualisieren Ich habe das Beispiel aktualisiert. Ich dachte, query.toString wäre sicher. Es wäre gut, einen Abschnitt der Dokumentation zu haben, der so etwas wie "Wie man unsichere Abfragen macht" beschriftet hat. Es gibt nur eine Handvoll No-Nos und auf diese Weise können die Leute die Bibliothek nutzen und wissen, "solange ich diese Dinge nicht tue, bin ich sicher".

Ich habe das folgende Upsert erstellt: https://gist.github.com/adnanoner/b6c53482243b9d5d5da4e29e109af9bd
Es handhabt Einzel- und Batch-Upserts. Ich habe es ein bisschen von @plurch angepasst. Verbesserungen sind immer willkommen :)

Für das, was es wert ist, habe ich dieses Format verwendet:

Bearbeiten: Aktualisiert, um für alle, die danach suchen, sicher zu sein. Danke @elhigu

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

@NicolajKN Sie sollten toString() nicht verwenden, da dies viele Arten von Problemen verursachen kann und keine Werte durch Bindungen an DB weitergibt (potenzielle Sicherheitslücke durch SQL-Injektion).

Gleiches richtig gemacht wäre so:

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

Gelöschte Diskussion eines nicht verwandten Problems.

@elhigu Warte , wird diese Einfügeabfrage nicht sofort nach der Erstellung ausgeführt? Schafft das nicht eine Race Condition?

@cloutiertyler Du hast nicht mit mir gesprochen, aber vielleicht kann ich @elhigu hier etwas Zeit sparen. Keine dieser Abfragen würde ausgeführt werden. Die Anweisung knex('account').insert(accounts) führt keine Abfrage aus. Es wird erst ausgeführt, wenn die Daten tatsächlich abgerufen werden (z. B. über ein .then ). Er sendet das an knex.raw('? ON CONFLICT DO NOTHING', [query]) , das query.toString() , das nur die Abfrage in die auszuführende SQL-Anweisung umwandelt.

@timhuff Danke Tim, ich nahm an, dass es so etwas sein musste, aber das ist kein normales Verhalten für ein Versprechen. Promises werden normalerweise bei der Erstellung ausgeführt. Der Grund, warum ich frage, ist, dass ich immer wieder Fehlermeldungen mit der Aufschrift "Connection Terminated" erhielt, als ich versuchte, diesen Upsert auszuführen. Nachdem ich zum Entfernen der Einfügung und zum Erstellen einer vollständig rohen Abfrage gewechselt war, verschwanden sie. Es scheint, als ob dies mit einer Rennbedingung vereinbar wäre.

knex QueryBuilder s sind jedoch keine Promise s. Wenn Sie anfangen, eine Knex-Abfrage zu schreiben, bleiben Sie im „Knexland“. Alles, was Sie tun, ist mehr oder weniger nur das Konfigurieren einer JSON-Spezifikation der Abfrage, die Sie erstellen möchten. Wenn Sie .toString ausführen, wird es erstellt und ausgegeben. Es wird nicht zu einem ( bluebird ) Versprechen, bis Sie eines davon ausführen. Sie könnten daran interessiert sein, .return zu verwenden, wenn Sie die Anweisung sofort ausführen möchten.

Ah, ich verstehe, nun, das klärt meine Verwirrung auf. Danke für die Aufklärung und Hinweise! Dann muss mein Problem woanders liegen.

Nebenbei gesagt, die Tatsache, dass es nicht sofort läuft, ist oft nützlich. Manchmal möchten Sie das Ding herumreichen, es konfigurieren, bevor Sie es ausführen. Es gibt auch Situationen, in denen man Dinge tun kann wie...

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

(super erfundenes Beispiel, aber lass uns damit laufen)

Ich möchte diese erste Aussage eigentlich nicht ausführen - sie ist nur ein Teil meiner zweiten.

Ganz zu schweigen davon, dass, wenn alle Abfragegeneratoren bei der Erstellung ausgeführt würden, die Builder-Musterabfragen ausgelöst würden, bevor die Erstellung abgeschlossen ist. Ohne eine Abschlussmethode (die die Abfrage ausführt) würde es überhaupt nicht funktionieren.

@elhigu Ich meine ... Ich denke, du könntest es einfach immer beim nächsten Tick ausführen, oder? Ich behaupte nicht, dass das auf jeden Fall eine gute Idee wäre, aber wie viele Abfragen werden tatsächlich erstellt und bei verschiedenen Ticks ausgeführt?

@timhuff Daran hatte ich nicht gedacht. Ja ich denke das wäre auch möglich. Ich finde den Fall ziemlich häufig, in dem man mit dem Erstellen einer Abfrage beginnt, dann einige asynchrone Daten abruft und weiter baut. Das mache ich aber nicht sehr oft.

@lukewlms , dass die 'execute()'-ähnliche Methode '.then()' heißt, Sie können sie immer aufrufen, wenn Sie eine Abfrage ausführen und eine Zusage erhalten möchten. Es ist nur, wie "thenable" funktioniert und es wird in Promise Spec erklärt. Es ist ein wichtiges und weit verbreitetes Konzept in Javascript, wenn es um Promises und async/await geht (was so ziemlich nur verherrlichte Abkürzungen für Promise.resolve und .then sind). Auch wenn Sie Abfragen ausführen, ohne Ergebnisse zu verarbeiten, suchen Sie nach Problemen wie App-Abstürzen.

Eigentlich ist es besser, einfach dieser PR über die Upsert-Funktionsimplementierung zu folgen https://github.com/tgriesser/knex/pull/2197 es hat bereits eine API entwickelt, wie es funktionieren sollte. In diesem Thread gibt es nicht wirklich nützliche Informationen, die nicht bereits in Kommentaren dieser PR erwähnt werden. Bei Bedarf (PR ist geschlossen und wird nie abgeschlossen) können Sie ein neues Problem für dieses mit zusätzlicher API-Beschreibung eröffnen.

@elhigu Danke für den Hinweis! Der Thread war mir nicht bekannt. Schön zu hören, dass wir Fortschritte bei einem Upsert machen, der zur API kommt. Sieht so aus, als hätte es vor 6 Monaten 1 der 802 Tests nicht bestanden und daher Travis-ci nie bestanden. Ist dieser 1 fehlgeschlagene Testfall das Einzige, was dies davon abhält, Teil der Knex-API zu werden?

@timhuff es wurde nur eine erste Implementierung durchgeführt, es muss komplett neu geschrieben werden. Der wichtigste Teil dieser PR ist das gemeinsame API-Design, das von den meisten Dialekten unterstützt werden kann. Die Funktion kommt also, wenn jemand einfach beschließt, diese API zu implementieren. Wenn das sonst niemand macht und ich eines Tages etwas mehr Zeit habe oder es dringend brauche, mache ich es selbst. Das ist eine der wichtigsten Funktionen, die Knex erhalten soll (zusätzlich zu Updates).

@elhigu Danke, dass du mich informiert hast. Ich muss hier über den Fortschritt nachlesen, wenn ich etwas mehr Zeit habe.

Ich bin mir nicht sicher, ob dies jemandem hilft oder ob ich nur ein Noob bin, aber für die Lösung von @timhuff musste ich meine Einschränkung in Anführungszeichen setzen, weil ich einen Abfragesyntaxfehler erhielt.

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

Ich bin auf der Suche nach einem knex-basierten Postgres-Upsert mehrmals auf dieses Problem gestoßen. Wenn jemand anderes dies braucht, hier ist, wie es geht. Ich habe dies sowohl mit einzelnen als auch mit zusammengesetzten eindeutigen Schlüsseln getestet.

Die Einrichtung

Erstellen Sie mithilfe der folgenden Anweisungen eine eindeutige Schlüsselbeschränkung für die Tabelle. Ich brauchte eine zusammengesetzte Schlüsselbeschränkung:

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

Die Funktion

(Bearbeiten: aktualisiert, um rohe Parameterbindungen zu verwenden)

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);
};

Verwendung

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

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

Wenn Ihre Einschränkung nicht zusammengesetzt ist, wäre diese eine Zeile natürlich nur constraint: '(a)' .

Dadurch wird entweder das aktualisierte Objekt oder das eingefügte Objekt zurückgegeben.

Ein Hinweis zu zusammengesetzten Nullable-Indizes

Wenn Sie einen zusammengesetzten Index (a,b) haben und b nullfähig ist, dann werden die Werte (1, NULL) und (1, NULL) von Postgres als gegenseitig eindeutig betrachtet (ich verstehe es nicht entweder). Wenn dies Ihr Anwendungsfall ist, müssen Sie einen partiellen eindeutigen Index erstellen und dann vor dem Upsert auf null testen, um zu bestimmen, welche Einschränkung verwendet werden soll. So erstellen Sie den teilweise eindeutigen Index: CREATE UNIQUE INDEX unique_index_name ON table (a) WHERE b IS NULL . Wenn Ihr Test feststellt, dass b null ist, müssen Sie diese Einschränkung in Ihrem Upsert verwenden: constraint: '(a) WHERE b IS NULL' . Wenn a auch nullable ist, würde ich vermuten, dass Sie 3 eindeutige Indizes und 4 if/else -Zweige benötigen (obwohl dies nicht mein Anwendungsfall ist, also bin ich mir nicht sicher).

Hier ist das kompilierte Javascript .

Hoffe, jemand findet das nützlich. @elhigu ~Jeder Kommentar zur Verwendung von knex().update(object) ?~ (edit: egal - sah die Warnung - jetzt knex.queryBuilder() verwenden)

Einige nicht verwandte Diskussionen entfernt (darüber, wie Promises/Thenables funktionieren).

Wurde das hinzugefügt?

Nein. Es gibt Funktionsanfragen und Spezifikationen in https://github.com/knex/knex/issues/3186

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

sandrocsimas picture sandrocsimas  ·  3Kommentare

rarkins picture rarkins  ·  3Kommentare

mattgrande picture mattgrande  ·  3Kommentare

koskimas picture koskimas  ·  3Kommentare

saurabhghewari picture saurabhghewari  ·  3Kommentare