web-dev-qa-db-ja.com

knexでのバッチ更新

Knex.js を使用してバッチ更新を実行したい

例えば:

_'UPDATE foo SET [theValues] WHERE idFoo = 1'
'UPDATE foo SET [theValues] WHERE idFoo = 2'
_

値付き:

_{ name: "FooName1", checked: true } // to `idFoo = 1`
{ name: "FooName2", checked: false } // to `idFoo = 2`
_

以前は node-mysql を使用していたため、複数のステートメントを使用できました。それを使用しながら、複数ステートメントのクエリ文字列を作成し、それを1回の実行でネットワーク経由で送信しました。

Knexで同じことを行う方法がわかりません。私はbatchInsertを私が使用できるAPIメソッドとして見ることができますが、batchUpdateに関する限りは何もありません。

注意:

  • 非同期反復を実行して、各行を個別に更新できます。これは悪い原因です。サーバーからDBへのラウンドトリップが大量に発生することを意味します

  • Knexのraw()を使用して、おそらくnode-mysqlで行うのと同じようなことを行うことができます。ただし、これはDB抽象化層であるというknexの目的全体を無効にします(強力なDB結合が導入されます)

だから私は何か「knex-y」を使ってこれをしたいと思います。

どんなアイデアも歓迎します。

11
Nik Kyriakides

トランザクション内でバッチ更新を実行する必要がありました(何か問題が発生した場合に備えて、部分的な更新を行いたくありませんでした)。私はそれを次の方法で解決しました:

// I wrap knex as 'connection'
return connection.transaction(trx => {
    const queries = [];
    users.forEach(user => {
        const query = connection('users')
            .where('id', user.id)
            .update({
                lastActivity: user.lastActivity,
                points: user.points,
            })
            .transacting(trx); // This makes every update be in the same transaction
        queries.Push(query);
    });

    Promise.all(queries) // Once every query is written
        .then(trx.commit) // We try to execute all of them
        .catch(trx.rollback); // And rollback in case any of them goes wrong
});
14
A Bravo Dev

あなたはそれぞれのアプローチの長所と短所をよく理解しています。いくつかの非同期更新を介して更新を一括処理する生のクエリをお勧めします。はい、それらを並行して実行できますが、ボトルネックは、dbが各更新を実行するのにかかる時間になります。詳細は こちら をご覧ください。

以下は、knex.rawを使用したバッチアップサートの例です。 recordsがオブジェクトの配列(更新する行ごとに1つのobj)であり、その値が、更新するデータベースの列と一致するプロパティ名であるとします。

var knex = require('knex'),
    _ = require('underscore');

function bulkUpdate (records) {
      var updateQuery = [
          'INSERT INTO mytable (primaryKeyCol, col2, colN) VALUES',
          _.map(records, () => '(?)').join(','),
          'ON DUPLICATE KEY UPDATE',
          'col2 = VALUES(col2),',
          'colN = VALUES(colN)'
      ].join(' '),

      vals = [];

      _(records).map(record => {
          vals.Push(_(record).values());
      });

      return knex.raw(updateQuery, vals);
 }

これ 回答は、2つのアプローチの実行時の関係を説明する素晴らしい仕事をします。

編集:

この例でrecordsがどのように見えるかを示すことを要求されました。

var records = [
  { primaryKeyCol: 123, col2: 'foo', colN: 'bar' },
  { // some other record, same props }
];

recordにクエリで指定したプロパティ以外のプロパティがある場合、次のことはできないことに注意してください。

  _(records).map(record => {
      vals.Push(_(record).values());
  });

レコードごとにクエリに渡す値が多すぎるため、knexは各レコードのプロパティ値とクエリ内の?文字を照合できません。代わりに、配列に挿入する各レコードの値を次のように明示的にプッシュする必要があります。

  // assume a record has additional property `type` that you dont want to
  // insert into the database
  // example: { primaryKeyCol: 123, col2: 'foo', colN: 'bar', type: 'baz' }
  _(records).map(record => {
      vals.Push(record.primaryKeyCol);
      vals.Push(record.col2);
      vals.Push(record.colN);
  });

上記の明示的な参照を行う繰り返しの少ない方法がありますが、これは単なる例です。お役に立てれば!

13
Patrick Motard

与えられたテーブルの有効なキー/値のコレクションがあると仮定します:

// abstract transactional batch update
function batchUpdate(table, collection) {
  return knex.transaction(trx => {
    const queries = collection.map(Tuple =>
      knex(table)
        .where('id', Tuple.id)
        .update(Tuple)
        .transacting(trx)
    );
    return Promise.all(queries)
      .then(trx.commit)    
      .catch(trx.rollback);
  });
}

それを呼び出すには

batchUpdate('user', [...]);

残念ながら、従来とは異なる列名の影響を受けますか?心配しないで、私はあなたに有名になりました:

function batchUpdate(options, collection) {
  return knex.transaction((trx) => {
    const queries = collection.map(Tuple =>
      knex(options.table)
        .where(options.column, Tuple[options.column])
        .update(Tuple)
        .transacting(trx)
    );
    return Promise.all(queries)
      .then(trx.commit)    
      .catch(trx.rollback);
  });
}

それを呼び出すには

batchUpdate({ table: 'user', column: 'user_id' }, [...]);
6
jiminikiz