原文

Introduction

upsert query は、データベースの既存の row とコンフリクトが生じない場合には、データベースに object を insert します。しかし、コンフリクトが「一つ以上」の row に対して生じた場合には、そのコンフリクトが生じた row の field を update します。もしくはその field に対して update をしないという設定も可能です。

An upsert query will insert an object into the database in case there is no conflict with another row in the table. In case there is a conflict with one or more rows, it will either update the fields of the conflicted rows or ignore the request.

Convert insert mutation to upsert

Upsert が実行可能なのは、update パーミッションがテーブルに付与されている場合に限ります。

Note

Only tables with update permissions are upsertable. i.e. a table’s update permissions are respected before updating an existing row in case of a conflict.

insert mutationupsert に変更するためには、on_conflict 引数を指定します。こうすることで upsert mutation へと変更されます。(訳注:つまり upsert という mutaion があるわけではない。あくまで inserton_conflict を指定すると upsert になる)

To convert an insert mutation into an upsert, you need to use the on_conflict argument to specify:

on_conflict には以下を指定します。

  • constraint に 「constraint unique key」 もしくは 「constraint primary key」 を指定する(訳注:primary key を指定するケースが多い。例えば article.id が pk の場合、article_pkey と入れる。当然だが同じ article.id の記事をインサートしようとすると id は pk なので被ってはいけないため、コンフリクトが起きる。コンフリクトが起きる条件を明示的にここに入れてやればいい。)
  • update_columns に、上記で指定した constraint に反した場合、つまりコンフリクトが起きた場合に、更新するカラムを指定する。(訳注:例えば空にしたら、コンフリクとが起きた場合には何も更新しないことになる。)
  • a unique or primary key constraint using the constraint field,
  • and the columns to be updated in the case of a violation of that constraint using the update_columns field.

The value of the update_columns field determines the behaviour of the upsert request as shown via the use cases below.

Fetching Postgres constraint names

You can fetch details of unique or primary key constraints on a table by running the following SQL:

SELECT * FROM "information_schema"."table_constraints" WHERE table_name='<table>' AND table_schema='<schema>';

GraphQL engine will automatically generate constraint names as enum values for the constraint field (try autocompleting in GraphiQL). Typically, the constraint is automatically named as <table-name>_<column-name>_key.

Upsert is not a substitute for update

upsert 機能は、update 機能と同等のものだと誤解されがちですが、実際には挙動が少しだけ異なります。upsert mutation は、対象となる row がデータベースに存在しているかどうか確定していない場合に用います。もし row がデータベースに存在していることが確定しているのであれば、update を使うのが正しいでしょう。

The upsert functionality is sometimes confused with the update functionality. However, they work slightly differently. An upsert mutation is used in the case when it’s not clear if the respective row is already present in the database. If it’s known that the row is present in the database, update is the functionality to use.

upsert を実行するためには、insert に必要なカラムが埋まっている必要があります。(訳注:つまり、name カラムが required だった場合には、upsert mutation の object.name が指定されていないといけない。)

For an upsert, all columns that are necessary for an insert are required.

どのように動作するか

  • Postgres が row をインサートしようとする(ただし required が指定されているカラムが埋まっていることが前提となる)
  • constraint が理由で上記のインサートが失敗した場合には、指定された column を更新する

How it works

  • Postgres tries to insert a row (hence all the required columns need to be present)
  • If this fails because of some constraint, it updates the specified columns

Required のカラムが全て埋まっている状態ではない場合には、次のようなエラーが発生します。error like NULL value unexpected for <not-specified-column> can occur

If not all required columns are present, an error like NULL value unexpected for <not-specified-column> can occur.

Update selected columns on conflict

update_columns を用いて、コンフリクトが発生した際にどの column を更新するかを指定することができます。

The update_columns field can be used to specify which columns to update in case a conflict occurs.

以下の例は article テーブルに新しい object を insert しようとしたが、unique constraint article_title_key の部分で違反が発生したので、insert ではなく既存の article のカラムを update しています。

Example: Insert a new object in the article table or, if the unique constraint article_title_key is violated, update the content column of the existing article:

Screen Shot 2020-08-28 at 19.54.55

published_on カラムが更新されていない点に着目してください。なぜならば update_columnspublished_on を含ませなかったためです。

Note that the publishedon column is left unchanged as it wasn’t present in updatecolumns.

Update selected columns on conflict using a filter

on_conflictwhere を追加することで、コンフリクとが起きた後、かつ update する前に update を行うかどうかを指定することができます。

A where condition can be added to the on_conflict clause to check a condition before making the update in case a conflict occurs

以下の例は article テーブルに新しい object を insert しようとしたが、unique constraint article_title_key の部分で違反が発生したので、insert ではなく既存の article のカラムを update しています。update の対象カラムは published_on です。その際に、更新される前の published_on の値が新しい published_on の値よりも小さい時にだけ更新が実行されるようになっています。

Example: Insert a new object in the article table, or if the unique key constraint articletitlekey is violated, update the publishedon columns specified in updatecolumns only if the previous published_on value is lesser than the new value:

Screen Shot 2020-08-28 at 20.02.04

Ignore request on conflict

update_columns が空の場合には、コンフリクトした場合のアップデートは無視され、実行されません。

If update_columns is an empty array then on conflict the changes are ignored.

以下の例は author テーブルに新しい object を insert しようとし、unique constraint author_name_key の部分で違反が発生したので、insert ではなく update を実行するところですが、update_columns が空のため update はそもそも発生しません。

Example: Insert a new object into the author table or, if the unique constraint authornamekey is violated, ignore the request.

Screen Shot 2020-08-28 at 20.06.18

Upsert in nested mutations

on_conflictnested object を指定することも可能です。

You can specify the on_conflict clause while inserting nested objects:

Screen Shot 2020-08-28 at 20.08.45

Nested upsert caveats

Note

The process by which nested inserts/upserts are executed is documented here.

Nested upserts will fail when:

In case of an array relationship, the parent upsert does not affect any rows (i.e. updatecolumns: [] for parent and a conflict occurs), as the array relationship objects are inserted after the parent. In case of an object relationship, the nested object upsert does not affect any row (i.e. updatecolumns: [] for nested object and a conflict occurs), as the object relationship object is inserted before the parent. To allow upserting in these cases, set update_columns: []. By doing this, in case of a conflict, the conflicted column/s will be updated with the new value (which is the same values as they had before and hence will effectively leave them unchanged) and will allow the upsert to go through.