6

I have following table:

CREATE TABLE IF NOT EXISTS categories
(
    id SERIAL PRIMARY KEY,
    title CHARACTER VARYING(100) NOT NULL,
    description CHARACTER VARYING(200) NULL,
    category_type CHARACTER VARYING(100) NOT NULL
);

I am using pg-promise, and I want to provide optional update of columns:

categories.update = function (categoryTitle, toUpdateCategory) {
  return this.db.oneOrNone(sql.update, [
          categoryTitle,
          toUpdateCategory.title, toUpdateCategory.category_type, toUpdateCategory.description,
        ])
}
  • categoryName - is required
  • toUpdateCategory.title - is required
  • toUpdateCategory.category_type - is optional (can be passed or undefined)
  • toUpdateCategory.description - is optional (can be passed or undefined)

I want to build UPDATE query for updating only provided columns:

UPDATE categories
SET title=$2,
// ... SET category_type=$3 if $3 is no NULL otherwise keep old category_type value
// ... SET description=$4 if $4 is no NULL otherwise keep old description value
WHERE title = $1
RETURNING *;

How can I achieve this optional column update in Postgres?

Mureinik
  • 297,002
  • 52
  • 306
  • 350
Alexey Vol
  • 1,723
  • 1
  • 16
  • 20

2 Answers2

8

You could coalesce between the old and the new values:

UPDATE categories
SET title=$2,
    category_type = COALESCE($3, category_type),
    description = COALESCE($4, description) -- etc...
WHERE title = $1
Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • 1
    OK. Thanks. I have never thought that `category_type` in this expression `COALESCE($3, category_type)` will have existing column value. Is it documented somehow? – Alexey Vol Dec 21 '18 at 17:16
  • 1
    `coalesce` returns the first not-null argument it gets. So if `$3` is null, you essentially get `category_type = categroy_type`, and the value is unchanged. – Mureinik Dec 21 '18 at 17:18
2

The helpers syntax is best for any sort of dynamic logic with pg-promise:

/* logic for skipping columns: */
const skip = c => c.value === null || c.value === undefined;

/* reusable/static ColumnSet object: */
const cs = new pgp.helpers.ColumnSet(
    [
        'title',
        {name: 'category_type', skip},
        {name: 'description', skip}
    ],
    {table: 'categories'});

categories.update = function(title, category) {
   const condition = pgp.as.format(' WHERE title = $1', title);
   const update = () => pgp.helpers.update(category, cs) + condition;
   return this.db.none(update);
}

And if your optional column-properties do not even exist on the object when they are not specified, you can simplify the skip logic to just this (see Column logic):

const skip = c => !c.exists;

Used API: ColumnSet, helpers.update.

See also a very similar question: Skip update columns with pg-promise.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138