Knex: Como executar várias consultas em uma corrida?

Criado em 29 abr. 2014  ·  40Comentários  ·  Fonte: knex/knex

Estou enfrentando um problema que gostaria de executar várias consultas separadas por ';' por um único executivo, isso é possível?

Meu código de teste que está falhando se parece com:

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

O erro:

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

Existe uma maneira de desativar as declarações preparadas para essas consultas?

feature request

Comentários muito úteis

Qualquer atualização ?
Agora é possível fazer multiQuery?
Em caso afirmativo, usando qual sintaxe?

Todos 40 comentários

Não, você terá que executá-los como duas instruções.

Bem, esta é uma solução um pouco limitante

Você poderia tentar chamar toString() na consulta e executar isso, mas eu realmente não o recomendo.

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

Bem, verei o que fazer com isso, basicamente, gostaria apenas de desligar as instruções preparadas e manter a capacidade de ter string de consulta e ligações, mas não sei se é possível, não encontrei nenhuma documentação relacionada a isso.

Enfim, eu tentei

knex.raw(query, bindings) + ''

mas joga - O objeto Object não tem nenhum método 'clone'.

Se você quiser experimentar o branch 0.6.0, sei que funcionará lá, que deve ser lançado assim que eu terminar os testes de algumas coisas.

Por que, entretanto, você precisaria executar duas instruções de consulta usando uma única string como essa? Isso não anula o propósito de usar o pacote?

Pessoalmente, preciso fazer vários INSERT ... ON DUPLICATE KEY UPDATE uma vez. Não consigo fazer uma inserção com vários valores porque quero saber qual linha foi atualizada e qual linha foi criada (verificando o valor da linha afetada).

Tenho brincado com vários construtores de consulta para node.js depois que encontramos as limitações do Knex (que resultaram basicamente no uso de consultas brutas na maioria das vezes). No final, eu mesmo codifiquei o construtor sql - QSql Demo . Não está completo ou pronto para produção, mas basicamente resume a maneira como estou pensando.

Demonstração muito legal, eu estava planejando adicionar algo assim após o próximo lançamento - curioso, você experimentou o branch 0.6?

Eu me pergunto se há algum espaço para colaboração aqui, onde o QSQL pode ser capaz de preencher a necessidade de um construtor de consultas mais robusto, enquanto o Knex lida com o pooling de conexões e suavizando as falhas entre os diferentes dialetos.

Enfim, bom trabalho, vou dar uma olhada!

Bem, não, nós não tentamos o 0.6, pois tínhamos muito código usando knex em um lugar e, em seguida, fazendo algumas consultas brutas em outro lugar. Depois de alguma pesquisa, decidi substituir toda a pilha por algo que pode ser mantido e onde recursos podem ser adicionados instantaneamente.

Bem, acho que definitivamente há um espaço para colaboração, no entanto, levará algum tempo para estabilizar o QSql primeiro. Pretendo definir uma boa API que corresponda a 99% das necessidades e casos de uso (como você pode ver, algumas construções ainda são um pouco feias) e implementar a abstração adequada para que outros back-ends possam ser adicionados.

talvez a capacidade de passar uma série de consultas e os resultados também forem uma matriz? algo simples como isso seria legal, poderia usar algo como async.parallel para fazer isso

O @niftylettuce Bluebird já vem com o recurso "async.parallel" pronto para uso. Mas esse não é o problema. Nós realmente precisamos de uma maneira de executar consultas separadas por vírgulas em uma corrida.

Isso não é possível apenas marcando

    multipleStatements: true

no bloco de conexão de sua configuração:
por exemplo

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

Ok, então se alguém aqui estiver interessado - estou trabalhando em algumas coisas novas no próximo refatorador para tornar isso possível.

Estou me perguntando qual seria a api ideal para isso ... queremos uma única cadeia?

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

  })

ou algo mais 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) => {

})

Também examinando a possibilidade de fazer o caso knex.raw funcionar automaticamente dividindo-se no ponto-e-vírgula.

Dividir em ponto-e-vírgula em consultas brutas seria um ótimo começo.

Estou tendo um problema agora em que minha exclusão não é concluída a tempo antes de minha inserção, então estou recebendo erros de chave duplicada sendo lançados.

Estou fazendo algo como

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

você pegou a essência ..

A delação não termina a tempo.

@tgriesser meu voto é para knex.multiQuery

@tgriesser alguma atualização sobre a capacidade de executar várias instruções em uma única consulta? Eu preferiria:

.update ('records_raw')
.set ({title: x}). where ({id: 1})
.fim()
.update ('records_raw')
.set ({title: y}). where ({id: 2})
.então (função (resultado) {
resultado [0] // resultado 1
resultado [1] // resultado 2
})
.catch (function (err) {
console.log (errar);
});

+1

Para fazer a divisão adequada em ponto-e-vírgulas, precisaríamos analisar toda a consulta bruta usando um analisador específico de dialeto. Especificamente, se as strings contêm ; , elas devem ser ignoradas. Aqui estão dois exemplos de SQL válido que não seriam triviais de analisar.

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

Observe também que, no primeiro caso, terminaríamos com 2 instruções, de modo que o código precisaria levar em consideração os resultados vazios da divisão.

Honestamente, no geral, acho que é uma má ideia. Há muitas chances de injeção de SQL e não dividir as consultas corretamente.

Além disso, ao executar várias instruções, quase sempre você deseja que isso aconteça na mesma transação. Knex já resolve este problema muito bem com a sintaxe .transacting(function(transaction) { /* code */ }) . No caso raro em que você não precisa de uma transação, você pode simplesmente usar o bluebird para juntar suas declarações knex e obter os resultados.

Portanto, por causa desses problemas, minha opinião é que isso não deveria acontecer.

: -1:

Além disso, ao executar várias instruções, quase sempre você deseja que isso aconteça na mesma transação. Knex já resolve este problema muito bem com a sintaxe .transacting(function(transaction) { /* code */ }) . No caso raro em que você não precisa de uma transação, você pode simplesmente usar o bluebird para juntar suas declarações knex e obter os resultados.

Posso dizer por experiência própria que, em certos casos, há uma _ enorme_ diferença de desempenho entre essas duas abordagens e o envio em lote de vários valores para o servidor como um comando.

Eu entendo que é muito difícil implementar de forma limpa, e é por isso que ainda não foi feito, mas esse problema limitou muito meu uso do knex para um projeto.

Tive um problema semelhante e consegui resolvê-lo. Veja aqui. https://github.com/tgriesser/knex/issues/1075

Não estou conseguindo ver nenhum caso de uso para esse recurso. Adoraria fechar este tíquete porque não vai consertar. Se um estiver dependendo da ordem das consultas sendo executadas na mesma conexão, você provavelmente deseja usar transações.

A única coisa que vejo que torna esse uso de 'consulta múltipla' algo que gostaríamos de ter é que é um ganho potencial de desempenho em alguns casos. Requer menos viagens de ida e volta para comunicar todas as consultas e os resultados de e para o servidor.

Mas não é como se eu tivesse um caso de uso mensurável disponível. Falando apenas da experiência / memória passada ...

@ jurko-gospodnetic Pode ser o caso quando você não tem pool de conexão. Com o pool, o envio de várias consultas é basicamente apenas colocar dados em um soquete TCP já criado. Além disso, se o seu desempenho depende de os buffers TCP não estarem preenchidos o suficiente, então o knex já é muito lento para você :) Nesse caso, é melhor usar o driver diretamente.

@ jurko-gospodnetic comentou em 20 de maio

A única coisa que vejo que torna esse uso de 'consulta múltipla' algo que gostaríamos de ter é que é um ganho potencial de desempenho em alguns casos. Requer menos viagens de ida e volta para comunicar todas as consultas e os resultados de e para o servidor.

Estou usando node-mysql (mysqljs) em um projeto de nível corporativo por este motivo específico. Queremos migrar todo o projeto para knex.js por uma série de razões

Há uma melhoria de desempenho considerável nessas operações que aproveito ao aproveitar as vantagens do recurso de consultas de várias instruções do driver node-mysql (mysqljs) .

Pode ser um empecilho para mim se Knex não apoiar isso. Então, vou reativar este tópico para perguntar exatamente isso.

Agora, consultas múltiplas em uma instrução são suportadas em Knex.js?

@nicholaswmin você pode nos dizer um pouco mais especificamente o que você entende por "considerável melhoria de desempenho"? Como enviar várias consultas dessa forma é consideravelmente mais eficiente do que apenas enviar várias consultas pela mesma conexão? Quaisquer benchmarks?

@elhigu Sem benchmarks, mas logo de

Uma única chamada para o banco de dados (empacotado com todas as instruções) elimina as viagens de ida e volta da rede server-db.

Por outro lado, enviar várias consultas pela mesma conexão não é uma solução mágica. Esta solução só funcionaria com fluxos não transacionais, consulte este problema

@nicholaswmin Você está enviando uma grande quantidade de pequenas consultas e, em sua maioria, ignorados ou resultados pequenos? Nesse caso, suponho que a diferença pode ser perceptível, uma vez que permite ao driver empacotar várias consultas para cada pacote TCP, caso contrário, cada pacote TCP teria principalmente cabeçalhos e uma quantidade realmente pequena de carga útil.

Esse tipo de diferença de desempenho deve ser medido facilmente, mesmo pela quantidade de tráfego de rede.

Não sei se os drivers suportam o envio de várias consultas de chamadas connection.query separadas para o banco de dados sem primeiro esperar pelos resultados das chamadas anteriores. Se eles fizerem isso, não deve haver muita diferença no tráfego TCP entre o envio de múltiplas connection.query ou uma única consulta múltipla que é suportada pelo driver mysql.

@elhigu Na verdade, são consultas pequenas, mas seus resultados são necessários para que possam ser usados ​​posteriormente na cadeia de transações / consultas.

Existe alguma maneira de ver os pacotes TCP sendo enviados por meio de uma depuração ou opção semelhante? Não as próprias consultas sendo enviadas, mas os pacotes reais.

Um caso de uso:

Ao atualizar os dados de um usuário em meu sistema, gostaria de computar a trilha de auditoria (o que mudou) para esse usuário.

// # 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 uma das chamadas acima faz várias chamadas de banco de dados, portanto, como você pode imaginar, essas são muitas viagens de ida e volta da rede.

Conforme declarado em # 1806, posso contornar isso usando Promise.all() para consultas que não precisam ser sequenciais (principalmente as chamadas de banco de dados em getData/setData não precisam ser sequenciais).

Isso funcionaria com fluxos não transacionais, uma vez que as consultas podem ser enviadas em conexões diferentes do pool. Assim que eu uso uma transação para realizar o fluxo acima, ela fica mais lenta (cerca de 4x), pois as consultas são enviadas em uma única conexão.

Como observação lateral, estou usando MSSQL.

Wireshark é uma ferramenta de plataforma cruzada bastante comum usada para analisar o tráfego de rede.

Não acredito que haja alguma maneira de chegar a esse nível com o nó.

Uma forma poderia ser usar iptraf ou qualquer coisa para medir a quantidade de dados enviados / recebidos ao enviar a mesma quantidade de consultas empacotadas em uma consulta ou passadas separadamente para o driver.

Eu adoraria ver uma sintaxe multiQuery .

Qualquer atualização ?
Agora é possível fazer multiQuery?
Em caso afirmativo, usando qual sintaxe?

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

     // ...

isso é apenas um loop, não várias solicitações feitas em uma, e é LENTO

@mscheffer, a pergunta era:

Estou enfrentando um problema que gostaria de executar várias consultas separadas por ';' por um único executivo, isso é possível?

Que minha resposta resolve. Se você tiver uma solução melhor e mais rápida, forneça-a.

@VictorioBerra A divisão de ; não funciona no caso geral, pois pode haver ; dentro de comentários e literais de string. Se você estiver lendo no dump de dados, eu diria que a maneira mais fácil é canalizar o código de dump do SQL para o shell sqlite. Infelizmente, isso não funciona para bancos de dados na memória, porque eles são apenas uma conexão única.

Também no seu caso, em que você está criando strings SQL em uma string de modelo interna e, em seguida, dividindo-a, usando knex.schema.* builders efetivamente a mesma coisa, mas mais seguros.

Pelo que eu sei, não há como alcançar esse recurso de executar várias consultas em um único comando para todos os drivers de dialeto. Acho que foi mysql e oracledb ou mssql, que tinha esse suporte e com o mysql você ainda obtém apenas o resultado da última consulta como uma resposta (que pode ser ok embora).

Lembre-se de usar transações se você estiver modificando dados usando várias consultas para que você possa ter certeza de que são executadas em sucessão!

EXECUÇÃO DE MÚLTIPLAS CONSULTAS USANDO RAW SQL
Eu me deparei com uma situação semelhante em que uma de nossas migrações de knex precisava executar CREATE DATABASE várias vezes consecutivas. Eu esperava separar as consultas com ponto e vírgula, mas o knex gerou um erro de sintaxe. A solução para isso é incluir "multipleStatements": true em seu objeto de conexão, de modo que o resultado final possa ser assim:

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

Depois disso, você pode usar uma instrução bruta como:

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

EXECUÇÃO DE MÚLTIPLAS CONSULTAS USANDO O KNEX QUERY BUILDER
Se você precisar usar o construtor de consultas knex em vez de escrever o SQL bruto você mesmo, terá que converter os resultados em Knex.QueryBuilder em uma string e, em seguida, juntar várias consultas. Aqui está um exemplo:

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

Isso também ajuda a reduzir o erro humano ao escrever consultas redundantes que precisam ser executadas em sucessão. Novamente, com algo assim, eu sugeriria envolvê-lo em uma transação.

Acho que isso pode ser fechado, pois é limitado principalmente por drivers de banco de dados. É necessária pelo menos a solicitação de recurso adequada.

@AksharaKarikalan mesmo se você fizer várias subconsultas e subtrações entre elas, ainda estará fazendo apenas uma única consulta. Portanto, removi o comentário que é irrelevante para esse problema. Stackoverflow é o local correto para solicitações de uso de knex.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

marianomerlo picture marianomerlo  ·  3Comentários

nklhrstv picture nklhrstv  ·  3Comentários

legomind picture legomind  ·  3Comentários

lanceschi picture lanceschi  ·  3Comentários

sandrocsimas picture sandrocsimas  ·  3Comentários