Knex: Comment exĂ©cuter plusieurs requĂȘtes en une seule fois ?

CrĂ©Ă© le 29 avr. 2014  Â·  40Commentaires  Â·  Source: knex/knex

Je suis confrontĂ© Ă  un problĂšme que je voudrais exĂ©cuter plusieurs requĂȘtes sĂ©parĂ©es par ';' par un seul exec, est-ce possible ?

Mon code de test qui Ă©choue ressemble Ă :

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

L'erreur:

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

Existe-t-il un moyen de dĂ©sactiver les instructions prĂ©parĂ©es pour de telles requĂȘtes ?

feature request

Commentaire le plus utile

Toute mise Ă  jour ?
Est-il possible maintenant de faire du multiQuery ?
Si oui, avec quelle syntaxe ?

Tous les 40 commentaires

Non, vous devrez les exécuter comme deux instructions.

Eh bien, c'est une solution un peu limite

Vous pouvez essayer d'appeler toString() sur la requĂȘte et de l'exĂ©cuter, mais je ne le recommande pas vraiment.

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

Eh bien, je vais voir quoi faire avec cela, en gros, je voudrais simplement dĂ©sactiver les instructions prĂ©parĂ©es et conserver la possibilitĂ© d'avoir une chaĂźne de requĂȘte et des liaisons, mais je ne sais pas si c'est possible, je n'ai trouvĂ© aucune documentation Ă  ce sujet.

En tout cas j'ai essayé

knex.raw(query, bindings) + ''

mais il jette - L'objet objet n'a pas de méthode 'clone'.

Si vous voulez essayer la branche 0.6.0, je sais que cela fonctionnera lĂ -bas, cela devrait ĂȘtre publiĂ© une fois les tests terminĂ©s pour quelques choses.

Mais pourquoi auriez-vous besoin d'exĂ©cuter deux instructions de requĂȘte en utilisant une seule chaĂźne comme celle-ci ? Cela ne va-t-il pas en quelque sorte Ă  l'encontre du but de l'utilisation du package ?

Personnellement, je dois faire plusieurs INSERT ... ON DUPLICATE KEY UPDATE à la fois. Je ne peux pas faire d'insertion avec plusieurs valeurs car je veux savoir quelle ligne a été mise à jour et quelle ligne a été créée (en vérifiant la valeur de la ligne affectée).

J'ai jouĂ© avec divers gĂ©nĂ©rateurs de requĂȘtes pour node.js aprĂšs avoir trouvĂ© les limitations de Knex (qui rĂ©sultaient essentiellement en l'utilisation de requĂȘtes brutes la plupart du temps). En fin de compte, je viens de coder le constructeur SQL moi-mĂȘme - QSql Demo . Ce n'est pas terminĂ© ou prĂȘt pour la production, mais rĂ©sume essentiellement ma façon de penser.

Démo trÚs cool, j'avais prévu d'ajouter quelque chose comme ça aprÚs la prochaine version - curieux, aviez-vous essayé la branche 0.6 ?

Je me demande s'il y a une place pour la collaboration ici, oĂč QSQL pourrait ĂȘtre en mesure de rĂ©pondre au besoin d'un gĂ©nĂ©rateur de requĂȘtes plus robuste, tandis que Knex s'occupe de la mise en commun des connexions et du lissage des failles entre les diffĂ©rents dialectes.

En tout cas, beau travail, je vais jeter un Ɠil !

Eh bien non, nous n'avons pas essayĂ© la 0.6 car nous avions beaucoup de code utilisant knex Ă  un endroit, puis faisant des requĂȘtes brutes ailleurs. AprĂšs quelques recherches, j'ai dĂ©cidĂ© de remplacer l'ensemble de la pile par quelque chose qui peut ĂȘtre maintenu et oĂč des fonctionnalitĂ©s peuvent ĂȘtre ajoutĂ©es instantanĂ©ment.

Eh bien, je pense qu'il y a dĂ©finitivement une place pour la collaboration, mais il faudra un certain temps pour stabiliser QSql d'abord. Je veux dire dĂ©finir une belle API qui correspond Ă  99% des besoins et des cas d'utilisation (comme vous pouvez le voir, certaines constructions sont encore un peu laides) et implĂ©menter une abstraction appropriĂ©e afin que d'autres backends puissent ĂȘtre ajoutĂ©s.

peut-ĂȘtre la possibilitĂ© de transmettre un tableau de requĂȘtes et les rĂ©sultats sont Ă©galement un tableau ? quelque chose de simple comme celui-ci serait cool, pourrait utiliser quelque chose comme async.parallel pour le faire

@niftylettuce Bluebird est dĂ©jĂ  livrĂ© avec la fonctionnalitĂ© "async.parallel" prĂȘte Ă  l'emploi. Mais ce n'est pas le problĂšme. Nous avons vraiment besoin d'un moyen d'exĂ©cuter des requĂȘtes sĂ©parĂ©es par des virgules en une seule fois.

N'est-ce pas possible en marquant simplement

    multipleStatements: true

dans le bornier de sa configuration :
par exemple

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

D'accord, si quelqu'un ici est intéressé, je travaille sur de nouvelles choses dans le prochain refactor pour rendre cela possible.

Je me demande quelle serait l'API idéale pour cela... voulons-nous avoir une seule chaßne ?

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

  })

ou quelque chose comme :

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) => {

})

Nous cherchons Ă©galement Ă  rendre possible le fonctionnement automatique du cas knex.raw en le divisant sur le point-virgule.

Le fractionnement en point-virgule sur les requĂȘtes brutes serait un bon dĂ©but.

Je rencontre actuellement un problĂšme oĂč ma suppression n'est pas terminĂ©e Ă  temps avant mon insertion, donc je reçois des erreurs de clĂ© en double.

je fais quelque chose comme

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

vous voyez l'essentiel..

Le del ne se termine pas Ă  temps.

@tgriesser mon vote est pour knex.multiQuery

@tgriesser une mise Ă  jour sur la possibilitĂ© d'exĂ©cuter plusieurs instructions dans une seule requĂȘte ? Je prĂ©fĂ©rerais:

.update('records_raw')
.set({title : x}).where({id : 1})
.finir()
.update('records_raw')
.set({title : y}).where({id : 2})
.then(fonction(résultat) {
résultat[0] //résultat 1
résultat[1] // résultat 2
})
.catch(fonction(err) {
console.log(err);
});

+1

Afin d'effectuer une division appropriĂ©e sur les points-virgules, nous aurions besoin d'analyser l'intĂ©gralitĂ© de la requĂȘte brute Ă  l'aide d'un analyseur spĂ©cifique au dialecte. Plus prĂ©cisĂ©ment, si les chaĂźnes contiennent ; , elles doivent ĂȘtre ignorĂ©es. Voici deux exemples de SQL valide qui ne serait pas trivial Ă  analyser.

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

Notez également que dans le premier cas, nous nous retrouverions avec 2 instructions, le code devrait donc prendre en compte les résultats vides de la scission.

HonnĂȘtement, dans l'ensemble, je pense que c'est une mauvaise idĂ©e. Il y a tout simplement trop de chances pour l'injection SQL et ne pas diviser correctement les requĂȘtes.

De plus, lors de l'exĂ©cution de plusieurs instructions, vous souhaitez presque toujours que cela se produise dans la mĂȘme transaction. Knex rĂ©sout dĂ©jĂ  trĂšs bien ce problĂšme avec la syntaxe .transacting(function(transaction) { /* code */ }) . Dans les rares cas oĂč vous n'avez pas besoin d'une transaction, vous pouvez simplement utiliser bluebird pour joindre vos relevĂ©s knex et obtenir les rĂ©sultats.

Donc, Ă  cause de ces problĂšmes, mon opinion est que cela ne devrait pas arriver.

:-1:

De plus, lors de l'exĂ©cution de plusieurs instructions, vous souhaitez presque toujours que cela se produise dans la mĂȘme transaction. Knex rĂ©sout dĂ©jĂ  trĂšs bien ce problĂšme avec la syntaxe .transacting(function(transaction) { /* code */ }) . Dans les rares cas oĂč vous n'avez pas besoin d'une transaction, vous pouvez simplement utiliser bluebird pour joindre vos relevĂ©s knex et obtenir les rĂ©sultats.

Je peux vous dire par expérience que dans certains cas, il existe une différence de performances énorme entre ces deux approches et le traitement par lots de plusieurs valeurs sur le serveur en une seule commande.

Je comprends que c'est trĂšs difficile Ă  mettre en Ɠuvre proprement, c'est pourquoi cela n'a pas encore Ă©tĂ© fait, mais ce problĂšme a considĂ©rablement limitĂ© mon utilisation de knex pour un projet.

J'ai eu un problÚme similaire et j'ai pu le résoudre. Voir par ici. https://github.com/tgriesser/knex/issues/1075

Je ne vois aucun cas d'utilisation pour cette fonctionnalitĂ©. J'adorerais fermer ce ticket car cela ne rĂ©soudra pas. Si l'un dĂ©pend de l'ordre des requĂȘtes exĂ©cutĂ©es sur la mĂȘme connexion, vous souhaiterez probablement utiliser des transactions.

La seule chose que je vois qui fait de cette utilisation de « requĂȘtes multiples » quelque chose que nous aimerions avoir, c'est qu'il s'agit d'une amĂ©lioration potentielle des performances dans certains cas. Il nĂ©cessite moins d'allers-retours pour communiquer toutes les requĂȘtes et les rĂ©sultats vers et depuis le serveur.

Mais ce n'est pas comme si j'avais un cas d'utilisation mesurable disponible. Ne parlant que d'expérience/mémoire passée...

@jurko-gospodnetic Cela peut ĂȘtre le cas lorsque vous n'avez pas de pool de connexion. Avec la mise en commun, l'envoi de plusieurs requĂȘtes consiste essentiellement Ă  mettre des donnĂ©es sur un socket TCP dĂ©jĂ  crĂ©Ă©. De plus, si vos performances dĂ©pendent du fait que les tampons TCP ne sont pas suffisamment remplis, alors knex est dĂ©jĂ  trop lent pour vous :) Dans ce cas, il vaut mieux utiliser directement le pilote.

@jurko-gospodnetic a commenté le 20 mai

La seule chose que je vois qui fait de cette utilisation de « requĂȘtes multiples » quelque chose que nous aimerions avoir, c'est qu'il s'agit d'une amĂ©lioration potentielle des performances dans certains cas. Il nĂ©cessite moins d'allers-retours pour communiquer toutes les requĂȘtes et les rĂ©sultats vers et depuis le serveur.

J'utilise node-mysql (mysqljs) sur un projet de niveau entreprise pour cette raison particuliĂšre. Nous cherchons Ă  migrer l'ensemble du projet vers knex.js pour une multitude de raisons

Il y a une amĂ©lioration considĂ©rable des performances sur de telles opĂ©rations que je tire en tirant parti de la fonctionnalitĂ© de requĂȘtes Ă  instructions multiples du pilote node-mysql (mysqljs) .

Cela pourrait s'avérer un obstacle pour moi si Knex ne prend pas en charge cela. Je vais donc relancer ce fil pour demander juste cela.

Les requĂȘtes multiples dans une seule instruction sont-elles dĂ©sormais prises en charge dans Knex.js ?

@nicolaswmin pouvez-vous dire un peu plus prĂ©cisĂ©ment ce que vous entendez par « amĂ©lioration considĂ©rable des performances » ? En quoi l'envoi de plusieurs requĂȘtes de cette maniĂšre est considĂ©rablement plus efficace que le simple envoi de plusieurs requĂȘtes via la mĂȘme connexion ? Des repĂšres ?

@elhigu Pas de repĂšres mais d'

Un seul appel à la base de données (avec toutes les instructions) élimine les allers-retours réseau serveur-db.

D'un autre cĂŽtĂ©, envoyer plusieurs requĂȘtes via la mĂȘme connexion n'est pas une solution miracle. Cette solution ne fonctionnerait qu'avec des flux non transactionnels, voir ce numĂ©ro

@nicolaswmin Envoyez -vous une Ă©norme quantitĂ© de petites requĂȘtes et la plupart du temps des rĂ©sultats ignorĂ©s ou petits ? Dans ce cas, je suppose que la diffĂ©rence peut ĂȘtre perceptible, car elle permet au pilote de regrouper plusieurs requĂȘtes pour chaque paquet TCP, oĂč sinon chaque paquet TCP aurait principalement des en-tĂȘtes et une trĂšs petite quantitĂ© de charge utile.

Ce type de diffĂ©rence de performances devrait pouvoir ĂȘtre mesurĂ© facilement, mĂȘme Ă  partir de la quantitĂ© de trafic rĂ©seau.

Je ne sais pas si les pilotes prennent en charge l'envoi de plusieurs requĂȘtes Ă  partir d'appels connection.query distincts vers la base de donnĂ©es sans attendre au prĂ©alable les rĂ©sultats des appels prĂ©cĂ©dents. S'ils le font, il ne devrait pas y avoir beaucoup de diffĂ©rence dans le trafic TCP entre l'envoi de plusieurs connection.query ou une seule multi-requĂȘte qui est prise en charge par le pilote mysql.

@elhigu Ce sont en effet de petites requĂȘtes, mais leurs rĂ©sultats sont nĂ©cessaires pour pouvoir ĂȘtre utilisĂ©s plus loin dans la chaĂźne de transaction/requĂȘte.

Existe-t-il un moyen pour moi de voir les paquets TCP envoyĂ©s via un dĂ©bogage ou une option similaire ? Pas les requĂȘtes elles-mĂȘmes envoyĂ©es, mais les paquets rĂ©els.

Un cas d'utilisation :

Lors de la mise à jour des données d'un utilisateur dans mon systÚme, j'aimerais calculer la piste d'audit (ce qui a changé) pour cet utilisateur.

// # 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);

Chacun des appels ci-dessus effectue plusieurs appels de base de données, vous pouvez donc imaginer qu'il s'agit de nombreux allers-retours réseau.

Comme indiquĂ© sur #1806, je peux contourner ce problĂšme en utilisant Promise.all() pour les requĂȘtes qui n'ont pas besoin d'ĂȘtre sĂ©quentielles (la plupart des appels de base de donnĂ©es dans getData/setData n'ont pas besoin d'ĂȘtre sĂ©quentiels).

Cela fonctionnerait avec des flux non transactionnels, car les requĂȘtes peuvent ĂȘtre envoyĂ©es sur diffĂ©rentes connexions Ă  partir du pool. DĂšs que j'utilise une transaction pour effectuer le flux ci-dessus, il ralentit (environ 4x) puisque les requĂȘtes sont envoyĂ©es sur une seule connexion.

En remarque, j'utilise MSSQL.

Wireshark est un outil multiplateforme assez courant utilisé pour analyser le trafic réseau.

Je ne crois pas qu'il y ait un moyen d'atteindre ce niveau avec node.

Une façon pourrait ĂȘtre d'utiliser iptraf ou quoi que ce soit pour mesurer la quantitĂ© de donnĂ©es envoyĂ©es / reçues lors de l'envoi du mĂȘme nombre de requĂȘtes avec une seule requĂȘte ou transmis sĂ©parĂ©ment au pilote.

J'aimerais voir une syntaxe multiQuery .

Toute mise Ă  jour ?
Est-il possible maintenant de faire du multiQuery ?
Si oui, avec quelle syntaxe ?

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

     // ...

c'est juste une boucle, pas plusieurs requĂȘtes en une seule, et c'est LENT

@mscheffer la question Ă©tait :

Je suis confrontĂ© Ă  un problĂšme que je voudrais exĂ©cuter plusieurs requĂȘtes sĂ©parĂ©es par ';' par un seul exec, est-ce possible ?

Ce que ma réponse résout. Si vous avez une meilleure solution plus rapide, veuillez la fournir.

@VictorioBerra Le fractionnement à partir de ; ne fonctionne pas dans le cas général, car il peut y avoir ; intérieur des littéraux de chaßne et des commentaires. Si vous lisez dans un vidage de données, je dirais que le moyen le plus simple consiste à rediriger le code de vidage SQL vers le shell sqlite. Malheureusement, cela ne fonctionne pas pour les bases de données en mémoire, car il ne s'agit que d'une seule connexion.

Également dans votre cas, oĂč vous crĂ©ez des chaĂźnes SQL dans une chaĂźne de modĂšle Ă  l'intĂ©rieur, puis la divisez, en utilisant les constructeurs knex.schema.* effectivement la mĂȘme chose, mais plus sĂ©curisĂ©e.

Pour autant que je sache, il n'y a aucun moyen d'obtenir cette fonctionnalitĂ© d'exĂ©cution de plusieurs requĂȘtes en une seule commande pour tous les pilotes de dialecte. Je pense que c'Ă©tait mysql et oracledb ou mssql, qui avaient ce support et avec mysql, vous n'obtenez toujours que le rĂ©sultat de la derniĂšre requĂȘte en tant que rĂ©ponse (ce qui pourrait ĂȘtre correct cependant).

N'oubliez pas d'utiliser des transactions si vous modifiez des donnĂ©es Ă  l'aide de plusieurs requĂȘtes afin de vous assurer qu'elles s'exĂ©cutent successivement !

EXÉCUTION DE REQUÊTES MULTIPLES À L'AIDE DE RAW SQL
J'ai rencontrĂ© une situation similaire oĂč l'une de nos migrations knex devait exĂ©cuter CREATE DATABASE plusieurs fois de suite. J'espĂ©rais sĂ©parer les requĂȘtes par des points-virgules, mais knex a renvoyĂ© une erreur de syntaxe. La solution pour cela est d'inclure "multipleStatements": true dans votre objet de connexion, donc le rĂ©sultat final peut ressembler Ă  ceci :

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

AprÚs cela, vous pouvez utiliser une instruction brute comme ceci :

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

EXÉCUTION DE REQUÊTES MULTIPLES À L'AIDE DE KNEX QUERY BUILDER
Si vous devez utiliser le gĂ©nĂ©rateur de requĂȘtes knex au lieu d'Ă©crire vous-mĂȘme le code SQL brut, vous devez convertir les rĂ©sultats en Knex.QueryBuilder en une chaĂźne, puis joindre plusieurs requĂȘtes. Voici un exemple :

// 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) => {
   });

Cela permet Ă©galement de rĂ©duire les erreurs humaines lors de l'Ă©criture de requĂȘtes redondantes qui doivent ĂȘtre exĂ©cutĂ©es successivement. Encore une fois, avec quelque chose comme ça, je suggĂ©rerais de l'envelopper dans une transaction.

Je pense que cela peut ĂȘtre fermĂ© car cela est principalement limitĂ© par les pilotes db. Au moins une demande de fonctionnalitĂ© appropriĂ©e est nĂ©cessaire.

@AksharaKarikalan mĂȘme si vous effectuez plusieurs sous-requĂȘtes et soustractions entre celles-ci, vous ne faites toujours qu'une seule requĂȘte. J'ai donc supprimĂ© le commentaire qui n'a rien Ă  voir avec ce problĂšme. Stackoverflow est le bon endroit pour les demandes d'utilisation de knex.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

tjwebb picture tjwebb  Â·  3Commentaires

npow picture npow  Â·  3Commentaires

aj0strow picture aj0strow  Â·  3Commentaires

mattgrande picture mattgrande  Â·  3Commentaires

ghost picture ghost  Â·  3Commentaires