我想使用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的raw()
东西,可能会做类似于我对node-mysql的操作。然而,这破坏了作为DB抽象层的整个knex目的(它引入了强大的DB耦合)
所以我想用“knex-y”来做这件事。
欢迎任何想法。
答案 0 :(得分:12)
我需要在事务中执行批量更新(我不想在出现问题时进行部分更新)。 我已经通过下一个方式解决了这个问题:
// 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
});
答案 1 :(得分:10)
您很清楚每种方法的优缺点。我建议一个原始查询,批量更新多个异步更新。是的,您可以并行运行它们,但您的瓶颈将成为数据库运行每次更新所需的时间。可以找到详细信息here。
下面是使用knex.raw进行批量upsert的示例。假设记录是一个对象数组(我们想要更新的每一行的一个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);
});
执行上述显式引用的重复方式较少,但这只是一个示例。希望这有帮助!
答案 2 :(得分:6)
假设您具有给定表的有效键/值的集合:
// 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' }, [...]);
答案 3 :(得分:0)
该解决方案非常适合我!我只包含一个ID参数,以使其在具有自定义ID标签的表之间动态变化。 Chenhai,这是我的摘录,其中包括一种返回交易的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数组。