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
而言没有任何意义。
注:
我可以进行异步迭代并分别更新每一行。这很糟糕,因为这意味着从服务器到 DB
会有很多次往返
我可以使用 Knex 的 raw()
东西,并且可能做一些类似于我用 node-mysql 做的事情。然而,这违背了 knex 作为 DB 抽象层的目的(它引入了强大的 DB 耦合)
所以我想用一些东西来做到这一点 "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 数组。
我想使用 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
而言没有任何意义。
注:
我可以进行异步迭代并分别更新每一行。这很糟糕,因为这意味着从服务器到 DB
会有很多次往返
我可以使用 Knex 的
raw()
东西,并且可能做一些类似于我用 node-mysql 做的事情。然而,这违背了 knex 作为 DB 抽象层的目的(它引入了强大的 DB 耦合)
所以我想用一些东西来做到这一点 "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 数组。