Upsert mutation / Hasura ドキュメント翻訳
TweetIntroduction
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 mutation
を upsert
に変更するためには、on_conflict
引数を指定します。こうすることで upsert mutation
へと変更されます。(訳注:つまり upsert
という mutaion があるわけではない。あくまで insert
に on_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:
published_on
カラムが更新されていない点に着目してください。なぜならば update_columns
に published_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_conflict
に where
を追加することで、コンフリクとが起きた後、かつ 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:
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.
Upsert in nested mutations
on_conflict
に nested object
を指定することも可能です。
You can specify the on_conflict clause while inserting nested objects:
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.