knex中的批量更新

Batch update in 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,它允许多语句。在使用它时,我只是构建了一个多语句查询字符串,然后通过单个 运行.

中的线路发送它

我不确定如何使用 Knex 实现同样的效果。我可以将 batchInsert 视为我可以使用的 API 方法,但就 batchUpdate 而言没有任何意义。

注:

所以我想用一些东西来做到这一点 "knex-y"。

欢迎提出任何想法。

您很清楚每种方法的优缺点。我会推荐一个原始查询,它通过多个异步更新进行批量更新。是的,您可以 运行 它们并行,但是您的瓶颈变成了数据库 运行 每次更新所花费的时间。详情可见here.

下面是使用 knex.raw 的批量更新插入示例。假设 records 是一组对象(我们要更新的每一行都有一个 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);
 }

This 答案很好地解释了两种方法之间的 运行 时间关系。

编辑:

有人要求我展示 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);
  });

以上显式引用的重复方式较少,但这只是一个例子。希望这对您有所帮助!

我需要在事务内执行批量更新(我不想进行部分更新以防出现问题)。 我已经通过下一个方式解决了它:

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

假设您有一组有效的 keys/values 给定 table:

// 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' }, [...]);

现代语法版本:

const batchUpdate({ table, column }, collection) => {
  const trx = await knex.transaction(); 
  try {
    await Promise.all(collection.map(tuple => 
      knex(table)
        .where(column, tuple[column])
        .update(tuple)
        .transacting(trx)
      )
    );
    await trx.commit();
  } catch (error) {
    await trx.rollback();
  }
}

这个解决方案非常适合我!我只是包含一个 ID 参数,以使其在具有自定义 ID 标记的表之间动态变化。 Chenhai,这是我的代码片段,其中包括 return 交易的单个 ID 值数组的方法:

function batchUpdate(table, id, collection) {
   return knex.transaction((trx) => {
       const queries = collection.map(async (tuple) => {
       const [tupleId] = await knex(table)
        .where(`${id}`, tuple[id])
        .update(tuple)
        .transacting(trx)
        .returning(id);

       return tupleId;
   });

   return Promise.all(queries).then(trx.commit).catch(trx.rollback);
   });
}

你可以使用 response = await batchUpdate("table_name", "custom_table_id", [array of rows to update]) 获取 ID 的 returned 数组。