ここはとある街の総合病院。
ここには通常の診療科のほかに、
何軒もの病院をたらいまわしにされた、
それがSQL緊急救命室、
そう、

ロバート
救命室部長。腕の立つエンジニアだが、(AM9:00 病院の入り口。ワイリーが扉の前をウロウロしている)

(ロバートが出勤し、
 なんだ、
なんだ、
 あっ先生……! 違いますよ。専門課程の合格通知、
あっ先生……! 違いますよ。専門課程の合格通知、
 倒れたらすぐに献体として使ってやる。それがお前の医学にできる最大の貢献だ。
倒れたらすぐに献体として使ってやる。それがお前の医学にできる最大の貢献だ。

 ふう……まったく。いい加減そのぐらいにして、
ふう……まったく。いい加減そのぐらいにして、
更新における冗長なサブクエリ
(治療室。ヘレンがいる。)
 遅いじゃないの。先にはじめてたわよ。
遅いじゃないの。先にはじめてたわよ。
 すまんな。こいつが道草を食っていたせいでな。……まったく。上の空だな。早く気持ちを切り替えろ。
すまんな。こいつが道草を食っていたせいでな。……まったく。上の空だな。早く気持ちを切り替えろ。
 すいません。どうも不安で。
すいません。どうも不安で。
 ああ、
ああ、
 はい。
はい。
カルテ1:受発注システムで利用するテーブル
 
 
 
代入式への行式の拡張
 患者のコード
患者のコード
UPDATE OrderDetails
   SET item     = (SELECT item
                     FROM EntryDetails AS ED
                    WHERE OrderDetails.entry_id = ED.entry_id
                      AND OrderDetails.entry_seq = ED.entry_seq),
       quantity = (SELECT quantity
                     FROM EntryDetails AS ED
                    WHERE OrderDetails.entry_id = ED.entry_id
                      AND OrderDetails.entry_seq = ED.entry_seq); 間違いじゃないわ。結果は正しく更新されるわ。
間違いじゃないわ。結果は正しく更新されるわ。
 ふむ。とすると問題は……。
ふむ。とすると問題は……。
 これまでの治療で見てきた症状を挙げていってみなさい。
これまでの治療で見てきた症状を挙げていってみなさい。
 ええっと、
ええっと、
 このサブクエリは明らかに冗長だな。まあ、
このサブクエリは明らかに冗長だな。まあ、
 でもどうやって直すんですか?
でもどうやって直すんですか?
 前回勉強したでしょう。行式よ
前回勉強したでしょう。行式よ
UPDATE OrderDetails
   SET (item, quantity)
           = (SELECT item, quantity
                     FROM EntryDetails ED
                    WHERE OrderDetails.entry_id = ED.entry_id
                      AND OrderDetails.entry_seq = ED.entry_seq); へえ! UPDATE文のSET句でも行式は使えるのですね。
へえ! UPDATE文のSET句でも行式は使えるのですね。
 逆に聞くけど、
逆に聞くけど、
 いやそれは……。そんないじめないでくださいよ、
いやそれは……。そんないじめないでくださいよ、
 気色悪い……。
気色悪い……。
シンプルさは常に良い
ヘレンが示したとおり、
その意味で、
---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT             |              |     6 |   126 |       3 (0)| 00:00:01 |
|   1 |  UPDATE                      | ORDERDETAILS |       |       |            | |
|   2 |   TABLE ACCESS FULL          | ORDERDETAILS |     6 |   126 |       3 (0)| 00:00:01 |
|   3 |   TABLE ACCESS BY INDEX ROWID| ENTRYDETAILS |     1 |    12 |       1 (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | SYS_C004301  |     1 |       |       0 (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| ENTRYDETAILS |     1 |    14 |       1 (0)| 00:00:01 |
|*  6 |    INDEX UNIQUE SCAN         | SYS_C004301  |     1 |       |       0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("ED"."ENTRY_ID"=:B1 AND "ED"."ENTRY_SEQ"=:B2)
   6 - access("ED"."ENTRY_ID"=:B1 AND "ED"."ENTRY_SEQ"=:B2)
※ Oracleで取得
---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT             |              |    6 |   216 |       3 (0)| 00:00:01 |
|   1 |  UPDATE                      | ORDERDETAILS |      |       |            |          |
|   2 |   TABLE ACCESS FULL          | ORDERDETAILS |    6 |   216 |       3 (0)| 00:00:01 |
|   3 |   TABLE ACCESS BY INDEX ROWID| ENTRYDETAILS |    1 |    36 |       1 (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | SYS_C004301  |    1 |       |       1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("ED"."ENTRY_ID"=:B1 AND "ED"."ENTRY_SEQ"=:B2)
※ Oracleで取得
患者の解は、
なお、
残念なお知らせ
この便利な行式ですが、
 SQL での更新は、
SQL での更新は、
 SQL がもともと更新機能をあまり重視していなかったことが影響しているのだろうな。ワイリー、
SQL がもともと更新機能をあまり重視していなかったことが影響しているのだろうな。ワイリー、
 知りません。
知りません。
 聞いたワシがバカだった……。
聞いたワシがバカだった……。
 Structured Query Language、
Structured Query Language、
 なるほど。もともとがSQLはSELECT文中心の言語だったわけですね。でもSQLってけっこう、
なるほど。もともとがSQLはSELECT文中心の言語だったわけですね。でもSQLってけっこう、
 うむ、
うむ、
 検索SQL においては、
検索SQL においては、
 というと?
というと?
 SET句の役割を勘違いすることに起因するケースだ。次の患者を見てみよう。
SET句の役割を勘違いすることに起因するケースだ。次の患者を見てみよう。
カルテ2:先ほどと同様に
 
 
 
SET句は更新対象を制限しない
 これ、
これ、
 更新結果の正しさだけを求めるなら、
更新結果の正しさだけを求めるなら、
 カルテ1と同様に解くと、
カルテ1と同様に解くと、
 えっと……
えっと……
 ブー。答えは6行。つまり全レコードよ。
ブー。答えは6行。つまり全レコードよ。
このケースにおいては、
WHERE句で更新対象を制限する
 UPDATEでもSELECTでも、
UPDATEでもSELECTでも、
UPDATE OrderDetails
   SET (item, quantity)
           = (SELECT item, quantity
                     FROM EntryDetails ED
                    WHERE OrderDetails.entry_id = ED.entry_id
                      AND OrderDetails.entry_seq = ED.entry_seq)
WHERE EXISTS (SELECT *
                 FROM EntryDetails ED
                WHERE OrderDetails.entry_id = ED.entry_id
                  AND OrderDetails.entry_seq = ED.entry_seq); まったく同じ条件を、
まったく同じ条件を、
 同じに見えるのは見た目上だけで、
同じに見えるのは見た目上だけで、
 そっか。ところで更新対象のレコードを制限する理由は、
そっか。ところで更新対象のレコードを制限する理由は、
 そうだ。今、
そうだ。今、
 最後に、
最後に、
 そこまで言うとはものものしいですね。
そこまで言うとはものものしいですね。
 まあ見ればわかる。
まあ見ればわかる。
カルテ3:ホテルの客室を管理するテーブル
 
 ちょっとパズル的な問題ですね
ちょっとパズル的な問題ですね
 主キーがないなど、
主キーがないなど、
SET句でウィンドウ関数を使えるか?
 ということはウィンドウ関数を使うんですよね。よーし、
ということはウィンドウ関数を使うんですよね。よーし、
UPDATE Hotel
   SET room_nbr = (floor_nbr * 100)
                    + ROW_NUMBER() OVER (PARTITION BY floor_nbr); いいな。
いいな。
 いいわね。
いいわね。
 ……またまた、
……またまた、
 いや、
いや、
 合ってるわね。
合ってるわね。
 え……!? え……! 本当ですか。いやっほう。今日は何だかツイている気がしてきましたよ。
え……!? え……! 本当ですか。いやっほう。今日は何だかツイている気がしてきましたよ。
 まあ、
まあ、
 そ、
そ、
SET句でのウィンドウ関数の威力
珍しくワイリーが一発で正答していますが、こんな単純なコードで可能なのか、ということに驚いた方も多いでしょう。ウィンドウ関数のおさらいになりますが、
ウィンドウ関数にORDER BYがないことが気になった人がいるかもしれません。しかし、
残念なお知らせ
またか、
- ① ウィンドウ関数でORDER BY句を指定しないことが許されない
- ② SET句に直接ウィンドウ関数を記述できない
①の問題は些細
つまり結論として、
SET句でウィンドウ関数を使う条件
 ワイリーの解は、
ワイリーの解は、
 結局ぬかよろこびですよ。
結局ぬかよろこびですよ。
 そんな卑下することはないわ。これはむしろ実装の側に責任があるのだから。
そんな卑下することはないわ。これはむしろ実装の側に責任があるのだから。
 SQLがいかに更新機能をないがしろにしているかがよくわかるだろう。さて、
SQLがいかに更新機能をないがしろにしているかがよくわかるだろう。さて、
 
 つまり、
つまり、
 そうか、
そうか、
UPDATE Hotel_2
   SET room_nbr
          = (SELECT nbr
               FROM (SELECT room_nbr,
                           (floor_nbr * 100) +
                              ROW_NUMBER() OVER(PARTITION BY floor_nbr
                                           ORDER BY room_nbr) AS nbr
                       FROM Hotel_2) TMP
              WHERE Hotel_2.room_nbr = TMP.nbr); ほう、
ほう、
 えへへ。ここで1年間しごかれたのは無駄じゃなかったですね。
えへへ。ここで1年間しごかれたのは無駄じゃなかったですね。
- レコードの一意識別子としては、OracleのROWID、 PostgreSQLのOIDのように、 テーブルが保持している擬似ID列を利用する方法もあります。しかしこれは実装依存になるため、 本稿では取り上げません。 
(PM5:00 休憩室。仕事を終えた3人がくつろいでいる)
 いやー今日もいい仕事しましたねー。
いやー今日もいい仕事しましたねー。
 今日
今日
 細かいところにこだわらないでくださいよ。いいじゃないですか。
細かいところにこだわらないでくださいよ。いいじゃないですか。
 あら、
あら、
 え、
え、
 おめでとう、
おめでとう、
 やれやれ、
やれやれ、
 へへへ、
へへへ、
 いいわねえ。もちろんロバートのおごりでね。
いいわねえ。もちろんロバートのおごりでね。
 わかった、
わかった、
 ひゃっほう。
ひゃっほう。
(1年後、 ……先生、
……先生、
 うーんむにゃむにゃ。
うーんむにゃむにゃ。
 先生、
先生、
 うーん、
うーん、
 夜中の3時です。
夜中の3時です。
 5時に起こしてって頼んだでしょ。
5時に起こしてって頼んだでしょ。
 急患なんです。
急患なんです。
 そう……そんな急がなくっても死にはしないって……。よっこらしょと。ロバート先生とヘレン先生は?
そう……そんな急がなくっても死にはしないって……。よっこらしょと。ロバート先生とヘレン先生は? 別の患者さんを見ています。人手が足りないんです。
別の患者さんを見ています。人手が足りないんです。
 そっか……よし、
そっか……よし、
 ええっ。ぼ、
ええっ。ぼ、
 つべこべ言うな。どうせすぐに1人でやらなきゃならないんだ。そら、
つべこべ言うな。どうせすぐに1人でやらなきゃならないんだ。そら、【参考資料】




