【问题标题】:Upsert in KnexJS在 KnexJS 中插入
【发布时间】:2016-12-04 04:35:14
【问题描述】:

我在 PostgreSQL 中有一个 upsert 查询,例如:

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

我需要对这个 upsert 查询使用 knex。这个怎么办?

【问题讨论】:

  • 也许最好的方法是 2 次查询:首先选择日期是否存在,如果不存在则插入数据。

标签: sql postgresql knex.js


【解决方案1】:

所以我使用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
});

希望这对某人有所帮助。

【讨论】:

  • 来自@HasFiveVowels 链接,他们谈论了很多关于使用 toString 方法如何无助于防止 sql 注入的问题。我想知道格式化的字符串是否有任何不同?否则,您可能会考虑做出更好的转换或更新您的答案。
  • 这是否适用于要插入的对象数组?
【解决方案2】:

我为此创建了一个函数和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 可以为空,那么 Postgres 认为值 (1, NULL) 和 (1, NULL) 是相互唯一的(我也不明白)。

【讨论】:

  • 虽然您的链接确实有帮助,但如果您也在这里分享答案会更好。谢谢。
【解决方案3】:

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

Official documentation 说:

为 PostgreSQL、MySQL 和 SQLite 数据库实施。一种 用于指定替代行为的插入查询的修饰符 发生冲突的情况。当表具有 PRIMARY 时会发生冲突 列上的 KEY 或 UNIQUE 索引(或一组上的复合索引) 列)并且被插入的行具有与该行相同的值 已存在于这些列的表中。默认行为 如果发生冲突,则引发错误并中止查询。使用 此方法您可以将此行为更改为静默忽略 通过使用 .onConflict().ignore() 或更新现有的错误 使用新数据行(执行“UPSERT”) .onConflict().merge().

所以在你的情况下,实现将是:

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

【讨论】:

    【解决方案4】:

    我能想到的另一种方法!

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

    【讨论】:

      【解决方案5】:

      很简单。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-02-06
        • 1970-01-01
        • 2017-07-07
        • 2017-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-29
        相关资源
        最近更新 更多