読者です 読者をやめる 読者になる 読者になる

ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

PDCA(Plan, Do, Check, Action)サイクルのActionとは何か?

マネジメント

疑問

PDCA(Plan, Do, Check, Action)サイクルについて、これまで何度も講義やセミナーで説明を受けてきましたが、どうしてもDo(実行)とAction(改善)の違いを理解できませんでした。
また、PDCAは「サイクル」であり、P→D→C→A→P→…のように繰り返すと説明されます。この「改善」の行動をした後に「計画」するということに違和感がありました。

解決

そんな悩みに対し、こう考えてはどうかとアドバイスがありました。
P : 計画
D : 実行
C : 評価(計画と実績の差異確認)
A : 改善(差異に対して行うアクションの決定)

改善(A)フェーズで計画と実績の差異の是正のために行うアクション(行動)を決定します。そして、決定したアクションを次の計画(P)フェーズで計画に組み込み、実行(D)フェーズで行動します。確かにこれであればA→Pへのつながりも自然なものとなります。

また、WEB+DB PRESS(プログラミング技術情報誌)の記事でPDCAの"A"は「Action(行動)」ではなく「Adjust(調整)」だと解説していた、というエントリもありました。
PDCAの「A」がActionじゃなくてAdjustって聞いてめっちゃしっくりきた話 - SUKEMATSU.NET
こちらのほうがしっくりくる方もいるかもしれません。

最後に

PDCAサイクルは行動することが最も大切です。そして、PDCAサイクルの定義について腹落ちしていれば、行動したときの納得感が変わってきます。今回のエントリがその一助となれば幸いです。

SQLのWHERE句で用いられる相関サブクエリを理解する

プログラミング

はじめに

相関サブクエリもしくはEXISTS述語の使い方として、以下のようなSQLがよく取り上げられます。

SELECT *
  FROM item i
 WHERE EXISTS
  (SELECT *
     FROM stock s
    WHERE i.id = s.id);

これに対して以下のデータが与えられたとします。

◆itemテーブル

id name
1 みかん
2 バナナ
3 リンゴ
4 ぶどう

◆stockテーブル

id quantity
1 10
3 30

この時、EXISTS述語内のサブクエリ"SELECT * FROM stock s WHERE i.id = s.id"だけを見て、以下のように考えてしまうことは無いでしょうか?
※補足:このサブクエリ単体では"i"を定義していないため、エラーとなります。実行するにはFROM句に"item i"の追加が必要です。以下同様です。

EXISTS述語内のサブクエリが2行返す
  ↓
EXISTS述語の評価がTRUEとなる
  ↓
WHERE句の評価がTRUEとなる
  ↓
WHERE句がTRUEのためitemテーブルの全行を返す

ですが、最終的な結果としては2行しか返ってきません。
どこで勘違いが起きてしまったのでしょうか?本エントリではこの疑問について説明します。

理解のポイントはWHERE句の処理にある

先日、WHERE句について解説するエントリをアップしました。
SQLのWHERE句の処理を理解する - ぱと隊長日誌
このエントリでSQLのWHERE句を「集合から条件に合致する行を選択する」処理であり、WHERE句の評価は1行ずつ行われる、と説明しました。
これを理解できればWHERE句の相関サブクエリを理解できます。冒頭の例について、処理の流れに沿いながら確認していきましょう。

(1)
FROM句でitemテーブルが指定されているため、対象となる集合は以下となります。

i.id i.name
1 みかん
2 バナナ
3 リンゴ
4 ぶどう

(2)
WHERE句は1行ずつ処理されるため、まずは先頭の行を評価します。

i.id i.name
1 みかん

(3)
ここでEXISTS述語内のサブクエリを実行します。

SELECT *
  FROM stock s
 WHERE i.id = s.id;

WHERE句が評価しているのは"i.id = 1"ですので、このクエリは以下のように動きます。

SELECT *
  FROM stock s
 WHERE 1 = s.id;

このクエリの結果を示します。

s.id s.quantity
1 10

(4)
サブクエリが1行返したため、EXISTS述語はTRUEとなります。
結果、WHERE句はTRUEとなり、WHERE句の評価していた行が選択されます。

i.id i.name
1 みかん

(5)
以降、対象となる集合の全ての行を処理するまで(3)から繰り返します。
結果として以下の行が選択されます。

i.id i.name
1 みかん
3 リンゴ

WHERE句が常にTRUEとなると勘違いをしてしまった方は、相関サブクエリがitemテーブルとstockテーブルの各集合に対して実行されると考えたのではないでしょうか。
処理を正しく理解するためには、WHERE句はitemテーブルの各行に対して評価されるため、itemテーブルのWHERE句で評価されている行とstockテーブルの集合に対してサブクエリが実行されると理解することが必要です。

単なる(相関でない)サブクエリであればどうなるのか?

このようなSQLであればどうでしょうか?

SELECT *
  FROM item i
 WHERE i.id = (SELECT max(s.id)
                 FROM stock s);

先ほどの例と同様にitemの各行毎にサブクエリが実行されると考えても構いませんが、サブクエリだけが先に実行されると考えても同じ結果となります。なぜなら、外側のクエリで使われているitemテーブルの列をサブクエリで参照していないからです。WHERE句がitemテーブルのどの行を評価していたとしても、サブクエリの結果は変わりません。

サブクエリを先に実行すると以下のSQLとなります。

SELECT *
  FROM item i
 WHERE i.id = 3;

結果は以下の通りです。

i.id i.name
3 リンゴ

まとめ

SQLを理解するためには処理の流れを理解することが大切です。
今回の例でいえば、WHERE句の処理を理解せずに()で囲まれた箇所を先に評価すると理解してしまうと失敗します。

この誤った理解でも以下のSQLは正しく評価できます。

SELECT *
  FROM item i
 WHERE i.id = (SELECT max(s.id)
                 FROM stock s);

でも、別のSQLは正しく評価できません。

SELECT *
  FROM item i
 WHERE EXISTS (SELECT *
                 FROM stock s
                WHERE i.id = s.id);

このエントリがSQLを理解するための一歩となれば幸いです。

参考資料

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

SQLが途中どのように処理されて最終結果に至るかを細かく解説しています。この途中処理こそがSQLを理解するためのカギとなります。
かなりの分量ですので通読は難しくとも、SQLを書いていて気になったことを調べるときに必要な箇所を参照してみてはいかがでしょうか。

達人に学ぶSQL徹底指南書

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

EXISTS述語を述語論理の観点から解説しています。また、相関サブクエリの応用例についても説明されています。

SQLのWHERE句の処理を理解する

プログラミング

はじめに

SQLのWHERE句は「集合から条件に合致する行を選択する」処理です。
ですが、SQLの様々な書き方を学ぶうちにWHERE句の役割について混乱してしまうことがあるかもしれません。本エントリではこの混乱の解消を目標に説明します。

SQLの実行順序

SQLの実行順序は以下の通り決まっています。
FROM→WHERE→GROUP BY→HAVING→SELECT

このうち、本エントリで取り上げるのはSELECT, FROM, WHEREです。
SELECTは集合から列を選択します。
FROMは対象とする集合を指定します。
WHEREは集合から条件に合致する行を選択します。

この後の説明を読み進めるときはこの順番を意識してみてください。

WHERE句の評価は1行ずつ行われる

ここにmember表があります。

name sex_code
あきら 1
ゆみこ 2
けんじ 1

これに対して"sex_code=1"を条件にSQLを発行します。

SELECT name
  FROM member
 WHERE sex_code = 1;

このSQLのWHERE句は対象となる集合のmemberに対して1行ずつ評価を行います。
WHERE句の評価の結果として以下の行が選択されました。

name sex_code
あきら 1
けんじ 1

選択リストに従い、"name"列が選択されます。

name
あきら
けんじ

見出しにも挙げましたが、「WHERE句の評価は1行ずつ行われる」というのがポイントです。
あなたがmember表とSQLを見比べて手作業で処理をするとき、member表が小さいこともあり、全体を俯瞰して一度に処理している感覚になるかもしれません(頭の中では1行ずつ処理しているとしても)。ですが、DBは素直に1行ずつ処理を行います。

これは推測ですが(裏付けとなる資料が見つかりませんでした)、「WHERE句の評価は1行ずつ行われる」ことがWHERE句で集約関数を使えないことにつながっているのかもしれません。
WHERE句に集約関数を使えたとしても、WHERE句の評価は1行ずつのため、集約関数に渡される集合の値は1つのみです。これでは集約関数の意味がありません。

例えば、WHERE句にMAX関数を含んだSQLを書けたとします。

SELECT name
  FROM member
 WHERE sex_code = MAX(sex_code);

WHERE句の評価は1行ずつです。member表の先頭行を評価するとします。

name sex_code
あきら 1

この時、MAX関数にsex_code=1が渡されても、それはSQL実装者の意図と異なるでしょう。
WHERE句に集約関数を使えないのは、このような意味のない記述を避けるためだったのかもしれません。

結合条件を含んだ行選択

先ほどのmember表に加え、sex表を用意します。

sex_code sex_jp
1
2
SELECT m.name,
       s.sex_jp
  FROM member m,
       sex s
 WHERE m.sex_code = s.sex_code
   AND m.sex_code = 1;

このSQLでイメージするのはmember表とsex表をsex_code列の値に応じて結合し、その結果からsex_code=1の行を選択することではないでしょうか?
ですが、これは「集合から条件に合致する行を選択する」を超えて、テーブルの結合処理まで行っています。WHERE句しかないのにJOIN句まで指定されたかのようにふるまうのはなぜでしょうか?

そこでまずは選択リスト、WHERE句を除いて考えてみます。

SELECT *
  FROM member m,
       sex s;

この結果はmember表とsex表のクロス結合(CROSS JOIN)となります。

m.name m.sex_code s.sex_code s.sex_jp
あきら 1 1
ゆみこ 2 1
けんじ 1 1
あきら 1 2
ゆみこ 2 2
けんじ 1 2

ここで改めて選択リスト、WHERE句を戻してみます。

SELECT m.name,
       s.sex_jp
  FROM member m,
       sex s
 WHERE m.sex_code = s.sex_code 
   AND m.sex_code = 1;

このWHERE句は以下の条件に合致する行を選択するものです。
「m.sex_code = s.sex_code」かつ「m.sex_code = 1」
先ほどのクロス結合の表からこの条件に合致する行を選択します。

m.name m.sex_code s.sex_code s.sex_jp
あきら 1 1
けんじ 1 1

選択リストで指定しているのはm.name, s.sex_jp列ですので、以下の結果となります。

m.name s.sex_jp
あきら
けんじ

このように処理を追って考えると、WHERE句は「集合から条件に合致する行を選択する」ことに変わりないとわかります。
実際の内部処理はここで示したようなクロス結合を作ることなく行われますが、結果は同様となります。

結合条件と検索条件を区別する

結合条件を含んだWHERE句がわかりづらいと感じるのであれば、標準SQLに則って"JOIN"で書き直すとわかりやすくなります。

SELECT m.name,
       s.sex_jp
  FROM member m,
       sex s
 WHERE m.sex_code = s.sex_code
   AND m.sex_code = 1;

これは"INNER JOIN"で以下のSQLに書き直せます。

SELECT m.name,
       s.sex_jp
  FROM member m INNER JOIN sex s
    ON m.sex_code = s.sex_code
 WHERE m.sex_code = 1;

ここでもまずは選択リスト、WHERE句を除外して考えてみます。

SELECT *
  FROM member m INNER JOIN sex s
    ON m.sex_code = s.sex_code;
m.name m.sex_code s.sex_code s.sex_jp
あきら 1 1
ゆみこ 2 2
けんじ 1 1

選択リスト、WHERE句を戻してみます。

SELECT m.name,
       s.sex_jp
  FROM member m INNER JOIN sex s
    ON m.sex_code = s.sex_code
 WHERE m.sex_code = 1;

WHERE句の"m.sex_code = 1"に合致する行を選択します。

m.name m.sex_code s.sex_code s.sex_jp
あきら 1 1
けんじ 1 1

選択リストで指定しているのはm.name, s.sex_jp列ですので、以下の結果となります。

m.name s.sex_jp
あきら
けんじ

先ほどのクロス結合にしてから考えた場合と同じ結果となりました。

まとめ

SQLを理解し辛く感じた時は実行順序に立ち返り、まずはFROMでどのような集合を対象にしているか考えるとわかりやすいかもしれません。WHEREはその集合に対して行を選択するものです。
WHERE句に結合条件があればテーブルの結合だ、と形から直截に理解することもよいと思うのですが、様々なSQLの書き方を学ぶうちにその表現力の豊かさから混乱してしまう時があるかもしれません。そんなときは今回のように立ち返って見直していただければと思います。

参考資料

達人に学ぶSQL徹底指南書

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

SQLの実行順序及び"JOIN"での書き換えを参考にさせていただきました。また、SQLのフォーマットも本エントリの参考にしています。
この本はSQLを使い始めて半年から一年程度の経験があることを想定しており、なじみのない記述は難しく感じますので、まずは興味のある個所からつまみ読みするのが楽しいと思います。

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

SQLの教科書といえる本です。著者の序文にある通り、入門書ではなく1年程度の実務経験のある方を対象としています。
ただ、今回のエントリでまとめた基礎レベルの話も解説されています(例えば「24 単純なSELECT文」)。WHERE句とは何か、という一見当たり前のことでも、その概念モデルに立ち戻って確認するのに良い本だと思います。

更新情報

2017/01/28

  • SQLのフォーマットを整えました。
  • 見出しの「単純な行選択」を「WHERE句の評価は1行ずつ行われる」に変更し、内容の見直しを行いました。