myusuf
myusuf

Reputation: 12240

Upsert in KnexJS

I have an upsert query in PostgreSQL like:

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

I need to use knex to this upsert query. How to go about this?

Upvotes: 26

Views: 25788

Answers (5)

Danniel Lee
Danniel Lee

Reputation: 87

very simple.

Adding onto Dorad's answer, you can choose specific columns to upsert using merge keyword.

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

Upvotes: 3

Dorad
Dorad

Reputation: 3713

As of [email protected]+ a new method onConflict was introduced.

Official documentation says:

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().

So in your case, the implementation would be:

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

Upvotes: 19

myusuf
myusuf

Reputation: 12240

So I solved this using the following suggestion from 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
});

Hope this helps someone.

Upvotes: 12

Tim
Tim

Reputation: 6661

I've created a function for doing this and described it on the knex github issues page (along with some of the gotchas for dealing with composite unique indices).

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

Example usage:

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

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

A note about composite nullable indices

If you have a composite index (a,b) and b is nullable, then values (1, NULL) and (1, NULL) are considered mutually unique by Postgres (I don't get it either).

Upvotes: 8

Abdul Kader Jeelani
Abdul Kader Jeelani

Reputation: 97

Yet another approach I could think of!

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

Upvotes: 2

Related Questions