Knex: ¿Cómo ejecutar varias consultas en una sola ejecución?

Creado en 29 abr. 2014  ·  40Comentarios  ·  Fuente: knex/knex

Estoy enfrentando un problema en el que me gustaría ejecutar varias consultas separadas por ';' por un solo ejecutivo, ¿es eso posible?

Mi código de prueba que está fallando se ve así:

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

El error:

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

¿Hay alguna forma de deshabilitar declaraciones preparadas para tales consultas?

feature request

Comentario más útil

Cualquier actualización ?
¿Es posible ahora hacer multiQuery?
Si es así, ¿con qué sintaxis?

Todos 40 comentarios

No, tendrás que ejecutarlos como dos declaraciones.

Bueno, esta es una solución un poco limitante

Podría intentar llamar a toString() en la consulta y ejecutarlo, pero no lo recomiendo realmente.

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

Bueno, veré qué hacer con eso, básicamente me gustaría desactivar las declaraciones preparadas y mantener la capacidad de tener cadenas de consulta y enlaces, pero no sé si es posible, no encontré documentación relacionada con estos.

De todos modos, lo intenté

knex.raw(query, bindings) + ''

pero arroja: el objeto Object no tiene método 'clon'.

Si desea probar la rama 0.6.0, sé que funcionará allí, que debería lanzarse una vez que termine las pruebas para algunas cosas.

Sin embargo, ¿por qué necesitaría ejecutar dos declaraciones de consulta usando una sola cadena como esa? ¿No frustra el propósito de usar el paquete?

Personalmente, necesito hacer varios INSERT ... ON DUPLICATE KEY UPDATE a la vez. No puedo hacer una inserción con varios valores porque quiero saber qué fila se ha actualizado y qué fila se ha creado (verificando el valor de la fila afectada).

He estado jugando con varios creadores de consultas para node.js después de que encontramos limitaciones de Knex (que resultaron básicamente en el uso de consultas sin procesar la mayor parte del tiempo). Al final, yo mismo codifiqué el constructor sql: QSql Demo . No está completo ni listo para la producción, pero básicamente resume mi forma de pensar.

Muy buena demostración, había estado planeando agregar algo así después de la próxima versión. Curioso, ¿habías probado la rama 0.6?

Me pregunto si hay espacio para la colaboración aquí, donde QSQL podría satisfacer la necesidad de un constructor de consultas más robusto, mientras que Knex se ocupa de la combinación de conexiones y la suavización de las verrugas entre diferentes dialectos.

De todos modos, buen trabajo, ¡echaré un vistazo!

Bueno, no, no probamos 0.6 ya que teníamos mucho código usando knex en un lugar y luego haciendo algunas consultas sin procesar en otro lugar. Después de investigar un poco, decidí reemplazar toda la pila con algo que se pueda mantener y donde se puedan agregar funciones al instante.

Bueno, creo que definitivamente hay espacio para la colaboración, sin embargo, primero llevará algún tiempo estabilizar QSql. Me refiero a definir una buena API que coincida con el 99% de las necesidades y casos de uso (como puede ver, algunas construcciones todavía son un poco feas) e implementar la abstracción adecuada para que se puedan agregar otros backends.

¿tal vez la capacidad de pasar una serie de consultas y luego los resultados también son una matriz? algo simple como esto sería genial, podría usar algo como async.parallel para hacerlo

@niftylettuce Bluebird ya viene con la función "async.parallel" lista para usar. Pero ese no es el problema. Realmente necesitamos una forma de ejecutar consultas separadas por coma en una sola ejecución.

¿No es esto posible simplemente marcando

    multipleStatements: true

en el bloque de conexión de la propia configuración:
p.ej

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

De acuerdo, si alguien aquí está interesado, estoy trabajando en algunas cosas nuevas en la próxima refactorización para que esto sea posible.

Me pregunto cuál sería la api ideal para esto ... ¿queremos tener una sola cadena?

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

  })

o algo más como:

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

})

También se busca hacer que la posibilidad de hacer que el caso knex.raw funcione automáticamente dividiendo el punto y coma.

Dividir en punto y coma en consultas sin formato sería un gran comienzo.

Me encuentro con un problema en este momento en el que mi eliminación no se completa a tiempo antes de mi inserción, por lo que recibo errores de clave duplicada.

Estoy haciendo algo como

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

entiendes la esencia ..

El del no se completa a tiempo.

@tgriesser mi voto es para knex.multiQuery

@tgriesser ¿ alguna actualización sobre la capacidad de ejecutar múltiples declaraciones en una sola consulta? Yo preferiría:

.update ('records_raw')
.set ({título: x}). donde ({id: 1})
.fin()
.update ('records_raw')
.set ({título: y}). donde ({id: 2})
.entonces (función (resultado) {
resultado [0] // resultado 1
resultado [1] // resultado 2
})
.catch (función (err) {
console.log (err);
});

+1

Para hacer una división adecuada en punto y coma, necesitaríamos analizar toda la consulta sin formato utilizando un analizador específico de dialecto. Específicamente, si las cadenas contienen ; , deben ignorarse. Aquí hay dos ejemplos de SQL válido que no sería trivial de analizar.

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

También tenga en cuenta que en el primer caso terminaríamos con 2 declaraciones, por lo que el código debería tener en cuenta los resultados vacíos de la división.

Honestamente, en general, creo que es una mala idea. Hay demasiadas posibilidades de inyección SQL y no dividir las consultas correctamente.

Además, al ejecutar varias declaraciones, casi siempre desea que eso suceda en la misma transacción. Knex ya resuelve este problema muy bien con la sintaxis .transacting(function(transaction) { /* code */ }) . En el raro caso de que no necesite una transacción, simplemente puede usar bluebird para unir sus declaraciones de knex y obtener los resultados.

Entonces, debido a estos problemas, mi opinión es que esto no debería suceder.

: -1:

Además, al ejecutar varias declaraciones, casi siempre desea que eso suceda en la misma transacción. Knex ya resuelve este problema muy bien con la sintaxis .transacting(function(transaction) { /* code */ }) . En el raro caso de que no necesite una transacción, simplemente puede usar bluebird para unir sus declaraciones de knex y obtener los resultados.

Puedo decirle por experiencia que, en ciertos casos, hay una diferencia de rendimiento _ enorme_ entre estos dos enfoques y el procesamiento por lotes de varios valores en el servidor como un solo comando.

Entiendo que es muy difícil de implementar de manera limpia, por lo que aún no se ha hecho, pero este problema ha limitado en gran medida mi uso de knex para un proyecto.

Tuve un problema similar y pude resolverlo. Mira aquí. https://github.com/tgriesser/knex/issues/1075

No veo ningún caso de uso para esta función. Me encantaría cerrar este ticket ya que no se solucionará. Si uno depende del orden de las consultas que se ejecutan en la misma conexión, es probable que desee utilizar transacciones.

Lo único que veo que convierte este uso de 'consultas múltiples' en algo que nos gustaría tener es que es una ganancia potencial de rendimiento en algunos casos. Requiere menos viajes de ida y vuelta para comunicar todas las consultas y los resultados desde y hacia el servidor.

Pero no es como si tuviera un caso de uso medible disponible. Solo hablando de experiencias / recuerdos pasados ​​...

@ jurko-gospodnetic Podría ser el caso cuando no tiene agrupación de conexiones. Con la agrupación, el envío de varias consultas consiste básicamente en poner datos en el socket TCP ya creado. Además, si su rendimiento depende de que los búferes TCP no se llenen lo suficiente, knex ya es demasiado lento para usted :) En ese caso, es mejor usar el controlador directamente.

@ jurko-gospodnetic comentó el 20 de mayo

Lo único que veo que convierte este uso de 'consultas múltiples' en algo que nos gustaría tener es que es una ganancia potencial de rendimiento en algunos casos. Requiere menos viajes de ida y vuelta para comunicar todas las consultas y los resultados desde y hacia el servidor.

Estoy usando node-mysql (mysqljs) en un proyecto de nivel empresarial por esta razón en particular. Buscamos migrar todo el proyecto a knex.js por una multitud de razones

Hay una mejora considerable del rendimiento en tales operaciones que aprovecho al aprovechar la función de consultas de múltiples instrucciones del controlador node-mysql (mysqljs) .

Podría resultar un éxito para mí si Knex no es compatible con esto. Así que reviviré este hilo para preguntar exactamente eso.

¿Se admiten ahora las consultas múltiples en una declaración en Knex.js?

@nicholaswmin, ¿puedes decir un poco más específico a qué te refieres con "mejora considerable del rendimiento"? ¿Cómo enviar múltiples consultas de esta manera es considerablemente más eficiente que simplemente enviar múltiples consultas a través de la misma conexión? ¿Algún punto de referencia?

@elhigu No hay puntos de referencia, pero de buenas a

Una sola llamada a la base de datos (empaquetada con todas las declaraciones) elimina los viajes de ida y vuelta de la red servidor-base de datos.

Por otro lado, enviar varias consultas a través de la misma conexión no es una solución milagrosa. Esta solución solo funcionaría con flujos no transaccionales, consulte este problema

@nicholaswmin ¿Está enviando una gran cantidad de consultas pequeñas y en su mayoría ignoradas o con resultados pequeños? En ese caso, supongo que la diferencia puede ser notable, ya que permite al controlador empaquetar múltiples consultas en cada paquete TCP, donde de lo contrario cada paquete TCP tendría principalmente encabezados y una cantidad realmente pequeña de carga útil.

Ese tipo de diferencia de rendimiento debería poder medirse fácilmente incluso a partir de la cantidad de tráfico de red.

No sé si los controladores admiten el envío de múltiples consultas desde llamadas connection.query separadas a DB sin esperar primero los resultados de las llamadas anteriores. Si lo hacen, no debería haber mucha diferencia en el tráfico TCP entre el envío de múltiples connection.query o una única consulta múltiple que es compatible con el controlador mysql.

@elhigu Son consultas pequeñas de hecho, pero sus resultados son necesarios para que se puedan usar más adelante en la cadena de transacciones / consultas.

¿Hay alguna forma de ver los paquetes TCP que se envían a través de una opción de depuración o similar? No se envían las consultas en sí, sino los paquetes reales.

Un caso de uso:

Al actualizar los datos de un usuario en mi sistema, me gustaría calcular la pista de auditoría (lo que ha cambiado) para ese usuario.

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

Cada una de las llamadas anteriores realiza varias llamadas a la base de datos, por lo que, como puede imaginar, se trata de una gran cantidad de viajes de ida y vuelta en la red.

Como se indicó en # 1806, puedo solucionar esto usando Promise.all() para consultas que no necesitan ser secuenciales (la mayoría de las llamadas DB en getData/setData no necesitan ser secuenciales).

Eso funcionaría con flujos no transaccionales, ya que las consultas se pueden enviar en diferentes conexiones del grupo. Tan pronto como uso una transacción para realizar el flujo anterior, se ralentiza (alrededor de 4x) ya que las consultas se envían en una sola conexión.

Como nota al margen, estoy usando MSSQL.

Wireshark es una herramienta multiplataforma bastante común que se utiliza para analizar el tráfico de la red.

No creo que haya ninguna forma de llegar a ese nivel con node.

Una forma podría ser usar iptraf o cualquier otra cosa para medir la cantidad de datos enviados / recibidos cuando se envía la misma cantidad de consultas empaquetadas en una consulta o se pasan por separado al controlador.

Me encantaría ver una sintaxis multiQuery .

Cualquier actualización ?
¿Es posible ahora hacer multiQuery?
Si es así, ¿con qué sintaxis?

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

     // ...

eso es solo un ciclo, no múltiples solicitudes hechas en una, y es LENTO

@mscheffer la pregunta era:

Estoy enfrentando un problema en el que me gustaría ejecutar varias consultas separadas por ';' por un solo ejecutivo, ¿es eso posible?

Lo que resuelve mi respuesta. Si tiene una solución mejor y más rápida, por favor proporcione.

@VictorioBerra Dividir desde ; no funciona en el caso general, ya que puede haber ; dentro de literales de cadena y comentarios. Si está leyendo en el volcado de datos, diría que la forma más fácil es canalizar el código de volcado SQL al shell sqlite. Lamentablemente, eso no funciona para las bases de datos en memoria, porque son solo una conexión.

También en su caso, donde está creando cadenas SQL en una cadena de plantilla interna y luego dividiéndola, usando knex.schema.* constructores efectivamente lo mismo, pero más seguro.

Hasta donde yo sé, no hay forma de lograr esta característica de ejecutar múltiples consultas en un solo comando para todos los controladores de dialecto. Creo que era mysql y oracledb o mssql, que tenían ese soporte y con mysql todavía obtienes solo el resultado de la última consulta como respuesta (que podría estar bien).

¡Recuerde usar transacciones si está modificando datos usando múltiples consultas para que pueda asegurarse de que se ejecuten en sucesión!

EJECUCIÓN DE CONSULTAS MÚLTIPLES UTILIZANDO RAW SQL
Me encontré con una situación similar en la que una de nuestras migraciones de knex necesitaba ejecutar CREATE DATABASE varias veces seguidas. Esperaba separar las consultas con punto y coma, pero knex arrojó un error de sintaxis. La solución para eso es incluir "multipleStatements": true en su objeto de conexión, por lo que el resultado final puede verse así:

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

Después de eso, puede usar una declaración sin procesar como esta:

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

EJECUCIÓN DE MÚLTIPLES CONSULTAS CON KNEX QUERY BUILDER
Si necesita usar el constructor de consultas knex en lugar de escribir el SQL sin formato usted mismo, entonces debe convertir los resultados en Knex.QueryBuilder en una cadena y luego unir varias consultas. He aquí un ejemplo:

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

Esto también ayuda a reducir el error humano al escribir consultas redundantes que deben ejecutarse en sucesión. De nuevo, con algo como esto, sugeriría envolverlo en una transacción.

Creo que esto se puede cerrar ya que está limitado principalmente por controladores de base de datos. Se necesita al menos una solicitud de función adecuada.

@AksharaKarikalan incluso si realiza varias subconsultas y resta entre ellas, todavía está haciendo una sola consulta. Así que eliminé el comentario que es irrelevante para este problema. Stackoverflow es el lugar correcto para las solicitudes de uso de knex.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

legomind picture legomind  ·  3Comentarios

sandrocsimas picture sandrocsimas  ·  3Comentarios

nklhrstv picture nklhrstv  ·  3Comentarios

fsebbah picture fsebbah  ·  3Comentarios

lanceschi picture lanceschi  ·  3Comentarios