PREPARATION
受験対策

オススメ!OSS-DB情報

第20回 RDBMSの種類によるUPDATE文の仕様の違い

SQLはANSI/ISOで標準化されているため、どのRDBMSでもほぼ同じように使うことができます。ただし、機能によってはRDBMSの種類によって仕様に大きな違いがあるものもあります。
データ操作に使うSELECT/INSERT/UPDATE/DELETEのいわゆるDMLのうち、UPDATEについては、他のDMLに比べてRDBMSによる仕様の違いが大きいので注意が必要です。
オープンソースのRDBMSとして広く使われるPostgreSQL、MySQLと、商用RDBMSで最もシェアの大きいOracleについて、UPDATE文の違いをまとめてみました。

UPDATE文の最も簡単な使い方は
UPDATE table_name
SET column_name = new_value
[WHERE condition];
という形式です。
これにより、WHERE句で指定した条件に合致したすべての行(省略した場合はテーブル内のすべての行)について、column_nameで指定した列の値をnew_valueで指定した値に更新します。

では、複数の列を更新したい時はどうすれば良いでしょうか。実は2通りの表記方法があります。

UPDATE table_name
SET column1 = value1, column2 = value2
[WHERE condition];

UPDATE table_name
SET (column1, column2) = (value1, value2)
[WHERE condition];

PostgreSQLとOracleでは、どちらの方法でも更新できますが、MySQLでは、上の方法しか使えません(下のUPDATE文はエラーになります)。

UPDATE文で複数の列を更新する、という作業は日常的に行われることですが、こんな単純なところでRDBMSの種類によるプログラムの非互換が発生する可能性がありますので注意してください。

UPDATE文のSET句の中で他のテーブルを参照したい場合、通常はサブクエリー(副問い合わせ)を使うことになります。
更新対象の列が1つだけなら
UPDATE table1 t1
SET column1 = (SELECT columnX FROM table2 t2 WHERE …)
[WHERE condition];
という形式になります。

この場合、サブクエリーが1行も返さなかったときは、該当する行のcolumn1の値がNULLに更新されます(エラーにはならない)。これが望む動作であれば良いのですが、それが不都合な場合は、UPDATE文本体のWHERE句に適切な条件を記述して、更新されないように注意しなければなりません。このために、サブクエリー内のWHEREとUPDATE本体のWHEREの両方によく似た条件式が書いてあることも非常に多いです。
一方、サブクエリーが複数行を返したときは、UPDATE文がエラーになります。このとき、PostgreSQLやOracleでは1行もデータが更新されませんが、MySQLでMyISAMというストレージエンジンを使っていると、一部の行だけが更新されるということが起きるので注意が必要です。MySQLでもInnoDBを使っていれば1行も更新されません。

サブクエリーを使って複数の列を更新する場合は、どのようなUPDATE文になるでしょうか。サブクエリーを使わずに複数の列を更新する場合の表記を応用すると、以下の2つの方法が考えられます。

UPDATE table1 t1
SET column1 = (SELECT columnX FROM table2 t2 WHERE …),
column2 = (SELECT columnY FROM table2 t2 WHERE …)
[WHERE condition];

UPDATE table1 t1
SET (column1, column2) = (SELECT columnX, columnY FROM table2 t2 WHERE …)
[WHERE condition];

PostgreSQLおよびMySQLでは、このうち上の方法しか使えません(下の方法はエラーになります)。Oracleではいずれの方法も使えます。

このような構文で複数のサブクエリーを書く場合、SELECT句の列名を除けば全く同じSELECT文が並ぶのが大半のケースでしょうから、上の方法は冗長な感じがしますね。更新対象の列を追加するときやサブクエリーを部分的に修正したい時など、SQLの修正部分が多くなる、部分的な修正漏れにより思わぬ不具合が発生する、といった欠点もあります。サブクエリーが複数あるのでパフォーマンスが悪くなるかもしれません。
回避策というわけではありませんが、PostgreSQLではサブクエリーを使わずに済む便利な構文があります。

UPDATE table1 t1
SET (column1, column2) = (t2.columnX, t2.columnY)
FROM table2 t2
WHERE … ;

というように、FROM句に結合対象のテーブル、WHERE句に結合条件を記述することで、UPDATE文の中で表結合をすることができます。UPDATE文の記述が単純になる、パフォーマンス上、有利になる場合がある、などの利点があります。
もちろん、同じ構文を他のRDBMSで利用できませんから、複数のデータベースでの移植性を考えるときには注意して使う必要があります。
また、更新対象の表(上の例ではtable1)の1行に対し、結合される表(table2)の複数の行がマッチする場合、サブクエリーを使っていればUPDATEがエラーになって異常が検出できますが、表結合を使うと、エラーにならずに更新されてしまいます。悪いことに、table2のどの行を使って更新されるのかは予測できません。
逆に、結合される表にマッチする行がない場合、サブクエリーを使うとtable1の更新対象列がNULLになりますが、表結合を使うとその行は更新対象とならず、元の値のままになります。もちろん、これが希望の動作かどうかはケースバイケースなのですが、サブクエリーを使ったUPDATE文と、表結合を使ったUPDATE文が単純に置き換えられるとは限らない、ということには注意してください。

MySQLのUPDATE文にFROM句はありませんが、UPDATE句に複数の表を指定することで、PostgreSQLと同じように表結合を使えます。
UPDATE table1 t1, table2 t2
SET t1.column1 = t2.columnX, t1.column2 = t2.columnY
WHERE …;
という感じのSQLになります。動作に関する注意(マッチする行が0行の場合、2行以上の場合)はPostgreSQLでFROM句を使う場合と同じです。

最後に、UPDATE文の中でサブクエリーや表結合を使うときの小さな仕様の違いについて1つ触れておきます。
UPDATE table1 t1
SET column1 = ...
と記述するのは、どのデータベースでも動作しますが、
UPDATE table1 t1
SET t1.column1 = …
というように、SET句で更新対象列の前に”表名(表別名).”を付加すると、PostgreSQLでは構文エラーとなります。もちろん、サブクエリーやWHERE句では”表名(表別名).列名”の形式で列を指定できますが、SET句ではつけることができません。更新対象の表は決まっているので、そもそも表名を指定できる必要はないのですが、Oracleでは指定してもエラーになりません。MySQLのようにUPDATE句で複数の表を指定できるデータベースでは、もちろん、SET句で表名を指定できます(というより、指定できる必要がありますね)。

解説:松田神一

LPI-Japan
Platinum Sponsors

関連資格