1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

相関サブクエリの動作イメージが掴みにくい方へ…

Posted at

【相関サブクエリの動作イメージが掴みにくい方へ…】

■前置き

こんにちは。しがないプログラマのryuji62でございます。
最近転職して、どちらかというとバックエンドを中心(今現在はがっつりフロントエンドコーディングしていますが)にコードを書いております。
バックエンドでは、Laravelを使い始めました。前職ではこういったフレームワークを使用することはなく、普通のHTMLでコーディングするというコーダーを担当しておりました。時々普通のHTMLでは具合が悪い案件に当たった場合は、タスクランナーを使用することもありましたが、それもGulpとかHexoくらいでした。

さて、今の会社ではフレームワークも使用し、バックエンドを中心にコードを書いておりますが、PHP以外でSQLも書く機会が増えてきております。前職ではPHPすら書かなかったので、驚きです。もともとPHPとSQLが言語的には好き(関心がある)だったので、少しやりがいを感じていたりします。

SQLに関しては2つ前の会社で少しテーブル連結とかをやるくらいで別段ややこしいSQLを書く機会はそれほどでもなかったのですが、今の会社では自社プロジェクトの都合で割と複雑なSQLを書く機会がありました。
どういったSQLかというと、「相関サブクエリ」でございます。熟練者の方からすると、「何をほざいておられるのですか?笑」と思われそうですが、いわゆる普通のWebサイトくらいだとシンプルなSQLで済むことが多いんですよ。

しかし、自社プロジェクトで複雑なデータ取得をするときにそこそこハマりましたね…。
そのときはタイトルにあるような相関サブクエリではなかったのですが…。

備忘録も兼ねて、勉強した内容を共有しようと思います!

もしよろしければご参考にしてやってください。

■今回の内容

今回は、『相関サブクエリの動作イメージが掴みにくい方へ…』というテーマなので、そのときのクエリの処理の追い方をご説明しようと思います。完全に正解ではないかもしれませんが、ご参考にはなるかと思われます。

〇お題

例えば、以下のようなテーブルがあったとします。
テーブル名は「sales2」です。

テーブル.png

このテーブルから以下の内容を取得しようと思います。

  1. 一行前のデータと比較して、もしsale値が同じであれば取得する。

さて、これの答えを先に書くと(先に書くんかいっ!!)、

SELECT
  s1.year,
  s1.sale
FROM sales2 AS s1
WHERE s1.sale = (
  SELECT
    s2.sale
  FROM sales2 AS s2
  WHERE s2.year = s1.year - 1
)

という感じになります。
実はこのお題自体は「達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ (CodeZine BOOKS)」という本であったお題(レコードは少し変えてますが)なんです。この本を最近よく読んでいて、「なぜそうなるの?」っていう動作のイメージがわかり辛かったんですね。

こういう「行間比較」っていうのは今時だと、ウィンドウ関数を使用して、シンプルなSQLで取得できることがだいたいだと思うのですが、使用中のサーバーにインストールされているRDBMSがウィンドウ関数をサポートしていないことがあります。そういうときに、行間比較の必要性が出てくると中々冷や汗もので、普通にPHPでループさせながら無理やりアプリケーション側でなんとかするっていうのも一つの手段ですが、できればプログラムはスマートな処理で完結させたいっていうときに、SQLでなんとかするっていう手段にたどり着くのかなと思います。

さてさて、上記SQLを読んでみて、みなさんはすぐに動作を頭の中でイメージできるでしょうか?
僕は無理です。いや、無理と言うと「諦めたらそこで~」というようなセリフが過りますよね。

このSQLの、

WHERE s2.year = s1.year - 1 ←ここがポイント①

ここなんですね。これがループクエリ(SQL)の大事なポイントで、これがないと目的は果たせなくなりますな。

これは日本語にそのまま直すと、「s2.year と s1.year - 1 が等しいとき、そのときのs1.yearとs1.saleを、sales AS s1テーブルから取得する」という意味になります。

ポイント①が2つのテーブルを紐づけ(バインドって言い方をします)して、プログラムでいうところのループ処理みたいに、1行ずつ比較するという動作をしてくれてるんですな。

この場合、2つのテーブルというのは、どっちもsalesテーブルです。スクショを貼ったテーブルです。
普通、複数のテーブルの紐づけというと、LEFT OUTER JOINとかINNER JOINとかの結合を想像される方も多いかと思います。

僕もそうでした。
まったく違うテーブルだと比較的想像しやすいのですが、ウィンドウ関数なしで行間比較しようとすると、どうしても相関サブクエリ(それも自己相関サブクエリというややこい名前)になってしまいます。

同じテーブルだと紐づけの仕方の動作イメージが付きにくいですが、これを違うテーブルと考えると良いですよ。って本には書いてあります。なるほど、確かにそうである。相関サブクエリなんていうややこい名前に縛られてる(まさにバインド)と、欲しい君が求められないっていう事態になり、今日も残業やっほい!ってなりますね。

本を読みながら、「どう考えるとわかりやすいか?」を考えるというある意味哲学的発想に至りながら、絞り出した答えが、「紙に書き出す or エディタにメモしながら」という非常に原始的な方法でした笑

今回はその方法でご説明したいと思います。

ここでもう一回お題のテーブルを貼っておきます。

テーブル.png

お題は、

  1. 一行前のデータと比較して、もしsale値が同じであれば取得する。

で、つまり求めたい結果は、

テーブル2.png

になります。

では、まずはテーブルを本当に2つ並べてみましょうか(考えるときにわかりやすくするために)。
テーブル2つ.png

この2つのテーブル(実際には1つのテーブルを自己相関してますが)で行間比較してみるということですね。

で、行間比較のイメージ図ですが、

▼1回目のループ

S1 2005 : S2 2005

でサブクエリ内のWHEWE句の条件を判定しています。

で、この1回目のループでは、「s2.year = s1.year - 1」ではないので(2005 = 2005 - 1ではない)のでこのときは何もSELECTされない。

▼2回目のループ

S1 2005 : S2 2004

このときは、「s2.year = s1.year - 1」(2004 = 2005 - 1)で条件が一致しているので、S1が2005のときはOKという意味になるので、S1テーブルから2005と71がSELECTされる。

▼3回目のループ

S1 2005 : S2 2002

このときは、「s2.year = s1.year - 1」ではないので(2002 = 2005 - 1ではない)のでこのときは何もSELECTされない。

というように、一つずつループしていますよね?
こうやって紙に書くなり、エディタにメモしながら追いかけるとわかりやすいですよ。

長々となってしまいましたが、こうやっていくと相関サブクエリも少しづつ慣れていくだろうと思います。

以上になります。
どうもありがとうございました。
(前置き長い…)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?