Knex: Wie führe ich mehrere Abfragen in einem Lauf aus?

Erstellt am 29. Apr. 2014  ·  40Kommentare  ·  Quelle: knex/knex

Ich stehe vor einem Problem, bei dem ich mehrere Abfragen ausführen möchte, die durch ';' getrennt sind. von einem einzelnen Exec, ist das möglich?

Mein fehlgeschlagener Testcode sieht so aus:

      var query = '' +
        'UPDATE "records_raw" ' +
        'SET "title" = ? ' +
        'WHERE "id" = ?' +
        ';' +
        'UPDATE "records_raw" ' +
        'SET "title" = ? ' +
        'WHERE "id" = ?' +
        ';';

      var bindings = [
        "x", "1",
        "y", "2"
      ];

      knex.raw(query, bindings).exec(function(err, result) {
        assert.isNotError(err);
      });

Der Fehler:

Error(cannot insert multiple commands into a prepared statement, ...

Gibt es eine Möglichkeit, vorbereitete Anweisungen für solche Abfragen zu deaktivieren?

feature request

Hilfreichster Kommentar

Irgendein Update ?
Ist es jetzt möglich, multiQuery zu machen?
Wenn ja, mit welcher Syntax?

Alle 40 Kommentare

Nein, Sie müssen sie als zwei Anweisungen ausführen.

Nun, das ist eine etwas einschränkende Lösung

Sie könnten versuchen, toString() für die Abfrage aufzurufen und diese auszuführen, aber ich empfehle es nicht wirklich.

knex.raw(knex.raw(query, bindings) + '').exec(function(err, result) {
  assert.isNotError(err);
});

Nun, ich werde sehen, was damit zu tun ist, im Grunde möchte ich nur vorbereitete Anweisungen deaktivieren und die Möglichkeit behalten, Abfragezeichenfolgen und Bindungen zu haben, aber ich weiß nicht, ob es möglich ist, ich habe keine Dokumentation dazu gefunden.

Jedenfalls habe ich es versucht

knex.raw(query, bindings) + ''

aber es wirft - Objektobjekt hat keine Methode 'klonen'.

Wenn Sie den 0.6.0-Zweig ausprobieren möchten, weiß ich, dass er dort funktionieren wird, der veröffentlicht werden sollte, sobald ich die Tests für ein paar Dinge abgeschlossen habe.

Warum müssten Sie jedoch zwei Abfrageanweisungen mit einer einzigen solchen Zeichenfolge ausführen? Verletzt es nicht den Zweck der Verwendung des Pakets?

Persönlich muss ich mehrere INSERT ... ON DUPLICATE KEY UPDATE gleichzeitig machen. Ich kann keine Einfügung mit mehreren Werten durchführen, da ich wissen möchte, welche Zeile aktualisiert und welche Zeile erstellt wurde (überprüfen Sie den betroffenen Zeilenwert).

Ich habe mit verschiedenen Abfrage-Buildern für node.js gespielt, nachdem wir Knex-Einschränkungen gefunden haben (was dazu führte, dass die meiste Zeit im Wesentlichen Rohabfragen verwendet wurden). Am Ende habe ich gerade SQL Builder selbst codiert - QSql Demo . Es ist nicht vollständig oder produktionsreif, fasst aber im Grunde meine Denkweise zusammen.

Sehr coole Demo, ich hatte geplant, so etwas nach der nächsten Version hinzuzufügen - neugierig, hast du den 0.6-Zweig ausprobiert?

Ich frage mich, ob es hier Raum für Zusammenarbeit gibt, wo QSQL möglicherweise den Bedarf an einem robusteren Abfragegenerator decken kann, während Knex sich mit dem Verbindungspooling und der Glättung der Warzen zwischen verschiedenen Dialekten befasst.

Wie auch immer, schöne Arbeit, ich schaue mir das mal an!

Nun, nein, wir haben 0.6 nicht ausprobiert, da wir viel Code hatten, der Knex an einer Stelle benutzte und dann an anderer Stelle einige Rohabfragen machte. Nach einigen Recherchen habe ich beschlossen, den gesamten Stack durch etwas zu ersetzen, das gewartet werden kann und dem sofort Funktionen hinzugefügt werden können.

Nun, ich denke, es gibt definitiv einen Raum für Zusammenarbeit, aber es wird einige Zeit dauern, QSql zuerst zu stabilisieren. Ich meine, eine nette API zu definieren, die zu 99% den Anforderungen und Anwendungsfällen entspricht (wie Sie sehen, sind einige Konstrukte immer noch ein bisschen hässlich) und die richtige Abstraktion zu implementieren, damit andere Backends hinzugefügt werden können.

vielleicht die Möglichkeit, ein Array von Abfragen zu übergeben und die Ergebnisse sind dann auch ein Array? so etwas einfaches wäre cool, könnte so etwas wie async.parallel verwenden, um es zu tun

@niftylettuce Bluebird wird bereits mit der Funktion "async.parallel" ausgeliefert. Aber das ist nicht das Problem. Wir brauchen wirklich eine Möglichkeit, durch Koma getrennte Abfragen in einem Lauf auszuführen.

Ist das nicht durch einfaches Markieren möglich

    multipleStatements: true

im Anschlussblock der eigenen Konfiguration:
z.B

    connection: {
        host: 'localhost',
        user: 'superSecret',
        password: 'notGonnaTell',
         port: 8889,
        database: 'babelverse_core',
        multipleStatements: true
    }

Okay, falls jemand hier interessiert ist - ich arbeite an einigen neuen Dingen in der bevorstehenden Überarbeitung, um dies zu ermöglichen.

Ich frage mich, was die ideale API dafür wäre... wollen wir eine einzelne Kette haben?

knex
  .update('records_raw')
  .set({title: x}).where({id: 1})
  .end()
  .update('records_raw')
  .set({title: y}).where({id: 2})
  .spread((resultA, resultB) => {

  })

oder so ähnlich:

knex.multiQuery([
  knex.update('records_raw').set({title: x}).where({id: 1})
  knex.update('records_raw').set({title: y}).where({id: 2})
]).spread((resultA, resultB) => {

})

Betrachten Sie auch die Möglichkeit, den Fall knex.raw automatisch arbeiten zu lassen, indem Sie das Semikolon aufteilen.

Das Aufteilen auf Semikolon bei Rohabfragen wäre ein guter Anfang.

Ich stoße gerade auf ein Problem, bei dem mein Löschvorgang nicht rechtzeitig vor dem Einfügen abgeschlossen ist, sodass doppelte Schlüsselfehler ausgegeben werden.

ich mache sowas wie

knex("mytable")
.where({
  id: 32423
})
.del()
.then( ->
  knex("mytable")
  .insert()
 ..... 

du verstehst das Wesentliche..

Das Del wird nicht rechtzeitig abgeschlossen.

@tgriesser meine Stimme ist für knex.multiQuery

@tgriesser ein Update zur Möglichkeit, mehrere Anweisungen in einer einzigen Abfrage auszuführen? Ich würde bevorzugen:

.update('records_raw')
.set({title: x}).where({id: 1})
.Ende()
.update('records_raw')
.set({title: y}).where({id: 2})
.dann (Funktion (Ergebnis) {
result[0] //Ergebnis 1
Ergebnis[1] // Ergebnis 2
})
.catch(funktion(err) {
Konsole.log(err);
});

+1

Um die Semikolons richtig aufzuteilen, müssen wir die gesamte Rohabfrage mit einem dialektspezifischen Parser analysieren. Insbesondere wenn Zeichenfolgen ; , sollten sie ignoriert werden. Hier sind zwei Beispiele für gültige SQL, deren Analyse nicht trivial wäre.

-- generic SQL
SELECT * FROM book WHERE title = 'Lord of the Rings; The Fellowship of the Ring';
-- MySQL specific
CREATE PROCEDURE dorepeat(p1 INT)
  BEGIN
    SET <strong i="9">@x</strong> = 0;
    REPEAT SET <strong i="10">@x</strong> = <strong i="11">@x</strong> + 1; UNTIL <strong i="12">@x</strong> > p1 END REPEAT;
  END

Beachten Sie auch, dass wir im ersten Fall mit 2 Anweisungen enden würden, sodass der Code leere Ergebnisse aus der Aufteilung berücksichtigen müsste.

Ehrlich gesagt halte ich das insgesamt für eine schlechte Idee. Es besteht einfach eine zu große Chance, dass SQL-Injection und Abfragen nicht richtig aufgeteilt werden.

Außerdem möchten Sie beim Ausführen mehrerer Anweisungen fast immer, dass dies in derselben Transaktion geschieht. Knex löst dieses Problem bereits sehr gut mit der .transacting(function(transaction) { /* code */ }) Syntax. In den seltenen Fällen, in denen Sie keine Transaktion benötigen, können Sie einfach bluebird verwenden, um Ihre Knex-Anweisungen zu verbinden und die Ergebnisse zu erhalten.

Aufgrund dieser Probleme ist meine Meinung, dass dies nicht passieren sollte.

:-1:

Außerdem möchten Sie beim Ausführen mehrerer Anweisungen fast immer, dass dies in derselben Transaktion geschieht. Knex löst dieses Problem bereits sehr gut mit der .transacting(function(transaction) { /* code */ }) Syntax. In den seltenen Fällen, in denen Sie keine Transaktion benötigen, können Sie einfach bluebird verwenden, um Ihre Knex-Anweisungen zu verbinden und die Ergebnisse zu erhalten.

Ich kann Ihnen aus Erfahrung sagen, dass es in bestimmten Fällen einen _enormen_ Leistungsunterschied zwischen diesen beiden Ansätzen gibt und mehrere Werte als ein Befehl an den Server gesendet werden.

Ich verstehe, dass es sehr schwer ist, es sauber zu implementieren, weshalb es noch nicht durchgeführt wurde, aber dieses Problem hat meine Verwendung von Knex für ein Projekt stark eingeschränkt.

Ich hatte ein ähnliches Problem und konnte es lösen. Siehe hier. https://github.com/tgriesser/knex/issues/1075

Ich kann keinen Anwendungsfall für diese Funktion sehen. Würde dieses Ticket gerne schließen, da es nicht behoben wird. Wenn eine von der Reihenfolge der Abfragen abhängt, die auf derselben Verbindung ausgeführt werden, möchten Sie wahrscheinlich Transaktionen verwenden.

Die einzige Sache, die ich sehe, die diese Verwendung von "Mehrfachabfragen" zu etwas macht, das wir gerne hätten, ist, dass es in einigen Fällen ein potenzieller Leistungsgewinn ist. Es erfordert weniger Roundtrips, um alle Abfragen und Ergebnisse zum und vom Server zu übertragen.

Aber es ist nicht so, dass ich einen messbaren Anwendungsfall zur Verfügung habe. Ich spreche nur aus Erfahrung/Erinnerung...

@jurko-gospodnetic Dies könnte der Fall sein, wenn Sie kein Verbindungspooling haben. Beim Pooling bedeutet das Senden mehrerer Abfragen im Grunde nur das Einreichen von Daten in einen bereits erstellten TCP-Socket. Auch wenn Ihre Leistung davon abhängt, dass TCP-Puffer nicht ausreichend gefüllt sind, ist Knex bereits zu langsam für Sie :) In diesem Fall ist es besser, den Treiber direkt zu verwenden.

@jurko-gospodnetic hat am 20. Mai kommentiert

Die einzige Sache, die ich sehe, die diese Verwendung von "Mehrfachabfragen" zu etwas macht, das wir gerne hätten, ist, dass es in einigen Fällen ein potenzieller Leistungsgewinn ist. Es erfordert weniger Roundtrips, um alle Abfragen und Ergebnisse zum und vom Server zu übertragen.

Aus diesem Grund verwende ich node-mysql (mysqljs) in einem Enterprise-Grade-Projekt. Wir möchten das gesamte Projekt aus einer Vielzahl von Gründen auf knex.js migrieren

Es gibt eine beträchtliche Leistungsverbesserung bei solchen Operationen, die ich nutze, indem ich die Mehrfachanweisungsabfragefunktion des node-mysql (mysqljs) -Treibers

Es könnte sich für mich als Showstopper erweisen, wenn Knex dies nicht unterstützt. Also werde ich diesen Thread wiederbeleben, um genau das zu fragen.

Werden jetzt mehrere Abfragen in einer Anweisung in Knex.js unterstützt?

@nicholaswmin können Sie etwas genauer sagen, was Sie mit "erheblicher Leistungsverbesserung" meinen? Inwiefern ist das Senden mehrerer Abfragen auf diese Weise wesentlich effizienter als das Senden mehrerer Abfragen über dieselbe Verbindung? Irgendwelche Benchmarks?

@elhigu Keine Benchmarks, aber auf Anhieb kann ich

Ein einzelner Aufruf an die DB (mit allen Anweisungen verpackt) eliminiert die Server-DB-Netzwerk-Roundtrips.

Auf der anderen Seite ist das Senden mehrerer Abfragen über dieselbe Verbindung kein Allheilmittel. Diese Lösung würde nur mit nicht-transaktionalen Flows funktionieren, siehe dieses Problem

@nicholaswmin Senden Sie eine große Menge kleiner Anfragen und meistens ignorierte oder kleine Ergebnisse? In diesem Fall kann der Unterschied spürbar sein, da er es dem Treiber ermöglicht, mehrere Abfragen in jedes TCP-Paket zu packen, während sonst jedes TCP-Paket hauptsächlich Header und eine sehr geringe Menge an Nutzlast hätte.

Diese Art von Leistungsunterschied sollte selbst anhand der Menge des Netzwerkverkehrs leicht gemessen werden können.

Ich weiß nicht, ob Treiber das Senden mehrerer Abfragen von separaten connection.query Aufrufen an die DB unterstützen, ohne zuerst auf die Ergebnisse vorheriger Aufrufe zu warten. Wenn dies der Fall ist, sollte es keinen großen Unterschied im TCP-Verkehr zwischen dem Senden mehrerer connection.query oder einer einzelnen Multi-Abfrage geben, die vom MySQL-Treiber unterstützt wird.

@elhigu Es handelt sich in der Tat um kleine Abfragen, aber ihre Ergebnisse werden benötigt, damit sie weiter unten in der Transaktions-/

Gibt es eine Möglichkeit für mich zu sehen, wie die TCP-Pakete über eine Debug- oder ähnliche Option gesendet werden? Es werden nicht die Abfragen selbst gesendet, sondern die eigentlichen Pakete.

Ein Anwendungsfall:

Beim Aktualisieren der Daten eines Benutzers in meinem System möchte ich den Audit Trail (was sich geändert hat) für diesen Benutzer berechnen.

// # PSEUDOCODE

// get current data of user
getUserData();
// set data of user
setUserData()
// get new data of user
getUserData()
// compute the audit trail by comparing the difference between before-set/after-set datums
computeAuditTrail(previousData, newData);

Jeder der oben genannten Aufrufe führt mehrere DB-Aufrufe durch, so dass Sie sich vorstellen können, dass dies viele Netzwerk-Roundtrips sind.

Wie in #1806 erwähnt, kann ich dies umgehen, indem ich Promise.all() für Abfragen verwende, die nicht sequenziell sein müssen (meistens müssen die DB-Aufrufe in getData/setData nicht sequenziell sein).

Das würde mit nicht-transaktionalen Flüssen funktionieren, da die Abfragen auf verschiedenen Verbindungen aus dem Pool gesendet werden können. Sobald ich eine Transaktion verwende, um den obigen Ablauf durchzuführen, wird er langsamer (ca. 4x), da die Abfragen über eine einzige Verbindung gesendet werden.

Als Randnotiz verwende ich MSSQL.

Wireshark ist ein ziemlich verbreitetes plattformübergreifendes Tool zur Analyse des Netzwerkverkehrs.

Ich glaube nicht, dass es eine Möglichkeit gibt, mit Node auf diese Ebene zu gelangen.

Eine Möglichkeit könnte sein, iptraf oder etwas anderes zu verwenden, um die Menge der gesendeten / empfangenen Daten zu messen, wenn die gleiche Anzahl von Abfragen gesendet wird, die in einer Abfrage gepackt oder separat an den Treiber übergeben werden.

Ich würde gerne eine multiQuery Syntax sehen.

Irgendein Update ?
Ist es jetzt möglich, multiQuery zu machen?
Wenn ja, mit welcher Syntax?

var knex = require("knex");
var _ = require("lodash");
var Promise = require("bluebird");

var knex = require('knex')({
    client: 'sqlite3',
    connection: {
        filename: "./data.sqlite"
    }
});

// Create Schema
let createScript = `

CREATE TABLE Class ( 
    Id                   integer NOT NULL  ,
    Name                 varchar(100)   ,
    CONSTRAINT Pk_Classes_Id PRIMARY KEY ( Id )
 );

CREATE TABLE Material ( 
    Id                   integer NOT NULL  ,
    Description          varchar(500)   ,
    CONSTRAINT Pk_Material_Id PRIMARY KEY ( Id )
 )

-- ... and so on (leave off the last semi or remove it later) ... 

`;

let statementPromises = _.map(createScript.split(';'), (statement) => {
    return knex.raw(statement);
});

Promise.all(statementPromises).then(function() {
    console.log('Schema generated. Populating...');

     // ...

das ist nur eine Schleife, nicht mehrere Anfragen in einer, und es ist LANGSAM

@mscheffer die Frage war:

Ich stehe vor einem Problem, bei dem ich mehrere Abfragen ausführen möchte, die durch ';' getrennt sind. von einem einzelnen Exec, ist das möglich?

Was meine Antwort löst. Wenn Sie eine schnellere bessere Lösung haben, geben Sie diese bitte an.

@VictorioBerra Das Aufteilen von ; funktioniert im Allgemeinen nicht, da ; in String-Literalen und Kommentaren enthalten sein kann. Wenn Sie Datendumps einlesen, würde ich sagen, dass der einfachste Weg darin besteht, den SQL-Dump-Code an die SQLite-Shell zu leiten. Leider funktioniert das nicht für In-Memory-Datenbanken, da es sich nur um eine einzelne Verbindung handelt.

Auch in Ihrem Fall, wo Sie SQL-Strings in einem Template-String erstellen und dann aufteilen, verwenden Sie knex.schema.* Builder effektiv dasselbe, aber sicherer.

Soweit ich weiß, gibt es keine Möglichkeit, diese Funktion zum Ausführen mehrerer Abfragen in einem einzigen Befehl für alle Dialekttreiber zu erreichen. Ich denke, es waren mysql und oracledb oder mssql, die diese Unterstützung hatten und mit mysql erhalten Sie immer noch nur das Ergebnis der letzten Abfrage als Antwort (was jedoch in Ordnung sein könnte).

Denken Sie daran, Transaktionen zu verwenden, wenn Sie Daten mit mehreren Abfragen ändern, damit Sie sicherstellen können, dass sie nacheinander ausgeführt werden!

AUSFÜHRUNG MEHRERER ABFRAGEN MIT RAW-SQL
Ich hatte eine ähnliche Situation, in der eine unserer Knex-Migrationen CREATE DATABASE mehrmals hintereinander ausführen musste. Ich hoffte, die Abfragen mit Semikolons zu trennen, aber Knex warf einen Syntaxfehler aus. Die Lösung dafür besteht darin, "multipleStatements": true in Ihr Verbindungsobjekt aufzunehmen, sodass das Endergebnis so aussehen kann:

"host": "192.168.x.x",
"user": "userLogin",
"password": "userPassword",
"database": "schemaToUse",
"multipleStatements": true

Danach können Sie eine unformatierte Anweisung wie folgt verwenden:

return knex.raw("CREATE DATABASE schema0; CREATE DATABASE schema1; CREATE DATABASE schema2")
   .then((result) => {
   })
   .catch((error) => {
   });

AUSFÜHRUNG MEHRERER ABFRAGEN MIT KNEX QUERY BUILDER
Wenn Sie den Knex-Abfrage-Generator verwenden müssen, anstatt das Roh-SQL selbst zu schreiben, müssen Sie die Ergebnisse in Knex.QueryBuilder in einen String konvertieren und dann mehrere Abfragen zusammenfügen. Hier ist ein Beispiel:

// Suppose that we wanted to add 100 currency for all players in multiple games
const addCurrency = 100;
const updateCurrency = { currency: "currency + " + addCurrency };
const queries = [
   knex.table("game0.stats").update(updateCurrency),
   knex.table("game1.stats").update(updateCurrency),
   knex.table("game2.stats").update(updateCurrency),
];
const multiQuery = queries.join(";");
console.log(multiQuery);
return knex.raw(multiQuery)
   .then((result) => {
   })
   .catch((error) => {
   });

Dies trägt auch dazu bei, menschliche Fehler beim Schreiben redundanter Abfragen zu reduzieren, die nacheinander ausgeführt werden müssen. Auch bei so etwas würde ich vorschlagen, es in eine Transaktion einzuwickeln.

Ich denke, dies kann geschlossen werden, da dies hauptsächlich durch DB-Treiber begrenzt ist. Zumindest eine ordnungsgemäße Funktionsanforderung ist erforderlich.

@AksharaKarikalan Selbst wenn Sie mehrere Unterabfragen durchführen und zwischen diesen subtrahieren, führen Sie immer noch nur eine einzige Abfrage durch. Also habe ich einen Kommentar entfernt, der für dieses Problem irrelevant ist. Stackoverflow ist der richtige Ort für Knex-Nutzungsanfragen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

zettam picture zettam  ·  3Kommentare

mishitpatel picture mishitpatel  ·  3Kommentare

tjwebb picture tjwebb  ·  3Kommentare

hyperh picture hyperh  ·  3Kommentare

lanceschi picture lanceschi  ·  3Kommentare