在 KnexJS 中更新插入

Upsert in KnexJS

我在 PostgreSQL 中有一个更新插入查询,例如:

INSERT INTO table
  (id, name)
values
  (1, 'Gabbar')
ON CONFLICT (id) DO UPDATE SET
  name = 'Gabbar'
WHERE
  table.id = 1

我需要对这个更新插入查询使用 knex。怎么办?

所以我使用 Dotnil's answer on Knex Issues Page 的以下建议解决了这个问题:

var data = {id: 1, name: 'Gabbar'};
var insert = knex('table').insert(data);
var dataClone = {id: 1, name: 'Gabbar'};

delete dataClone.id;

var update = knex('table').update(dataClone).whereRaw('table.id = ' + data.id);
var query = `${ insert.toString() } ON CONFLICT (id) DO UPDATE SET ${ update.toString().replace(/^update\s.*\sset\s/i, '') }`;

return knex.raw(query)
.then(function(dbRes){
  // stuff
});

希望这对某人有所帮助。

我已经创建了一个函数来执行此操作和 described it on the knex github issues page(以及处理复合唯一索引的一些陷阱)。

const upsert = (params) => {
  const {table, object, constraint} = params;
  const insert = knex(table).insert(object);
  const update = knex.queryBuilder().update(object);
  return knex.raw(`? ON CONFLICT ${constraint} DO ? returning *`, [insert, update]).get('rows').get(0);
};

用法示例:

const objToUpsert = {a:1, b:2, c:3}

upsert({
    table: 'test',
    object: objToUpsert,
    constraint: '(a, b)',
})

关于复合可为空索引的说明

如果你有一个复合索引 (a,b) 并且 b 可以为空,那么值 (1, NULL) 和 (1, NULL) 被 Postgres 认为是相互唯一的(我也不明白)。

我能想到的另一种方法!

exports.upsert = (t, tableName, columnsToRetain, conflictOn) => {
    const insert = knex(tableName)
        .insert(t)
        .toString();
    const update = knex(tableName)
        .update(t)
        .toString();
    const keepValues = columnsToRetain.map((c) => `"${c}"=${tableName}."${c}"`).join(',');
    const conflictColumns = conflictOn.map((c) => `"${c.toString()}"`).join(',');
    let insertOrUpdateQuery = `${insert} ON CONFLICT( ${conflictColumns}) DO ${update}`;
    insertOrUpdateQuery = keepValues ? `${insertOrUpdateQuery}, ${keepValues}` : insertOrUpdateQuery;
    insertOrUpdateQuery = insertOrUpdateQuery.replace(`update "${tableName}"`, 'update');
    insertOrUpdateQuery = insertOrUpdateQuery.replace(`"${tableName}"`, tableName);
    return Promise.resolve(knex.raw(insertOrUpdateQuery));
};

knex@v0.21.10+ 开始,引入了一种新方法 onConflict

Official documentation 说:

Implemented for the PostgreSQL, MySQL, and SQLite databases. A modifier for insert queries that specifies alternative behaviour in the case of a conflict. A conflict occurs when a table has a PRIMARY KEY or a UNIQUE index on a column (or a composite index on a set of columns) and a row being inserted has the same value as a row which already exists in the table in those column(s). The default behaviour in case of conflict is to raise an error and abort the query. Using this method you can change this behaviour to either silently ignore the error by using .onConflict().ignore() or to update the existing row with new data (perform an "UPSERT") by using .onConflict().merge().

因此,在您的情况下,实施方式为:

knex('table')
  .insert({
    id: id,
    name: name
  })
  .onConflict('id')
  .merge()

很简单。

添加到 Dorad 的答案中,您可以使用合并关键字选择要更新的特定列。

knex('table')
  .insert({
    id: id,
    name: name
  })
  .onConflict('id')
  .merge(['name']); // put column names inside an array which you want to merge.