Feedforce Developer Blog

フィードフォース開発者ブログ

cross join を知ると join が書きやすくなるよ、という話

SQL 大好き! id:kano-e です!

こないだの FFLT (まだ続いてます! 3/15 で 16 回目!)で SQL の join について話をしたところ、思ったより好評だった(自画自賛!)ので、記事にまとめました。

outer join とか innner join とか書き方はわかるんだけど、どういうことかイマイチぴんときてない」

「こういう結果が見たい時に、多分 join を使うんだろうと思うけど、どう書き始めて良いかがわからない」

みたいな悩みに向けて書いています。

join てなんだっけ

join についての話は

  • inner join があって
  • outer join には leftlight があって full outer join もあって
  • 複数のテーブル(表)から情報をまとめて取得する時に使うよ

みたいな感じで説明されることが多いかなという印象です。

日本語だと「結合」とか言います。

例えばこんな使い方

実際に outer join の使い方を見てみましょう。

users テーブル

 id |   name   
----+----------
  1 | アーサー
  2 | カイン
  3 | クッキー
  4 | トンヌラ
(4 rows)

items テーブル

 owner_id |    name    
----------+------------
        1 | やくそう
        2 | せいすい
        3 | やくそう
        1 | ぬののふく
(4 rows)

items.owner_idusers.id が入っている、こんなテーブルがあったとします。

この時に、アイテムとその持ち主をまとめて一覧したい場合、こんな感じに left outer join を使います。

select
    items.name as item
    , users.name as user
from items
    left outer join users
        on items.owner_id = users.id
;

結果はこうなります。

    item    |   user   
------------+----------
 やくそう   | アーサー
 ぬののふく | アーサー
 せいすい   | カイン
 やくそう   | クッキー
(4 rows)

right outer join だとこんな感じ。

select
    items.name as item
    , users.name as user
from items
    right outer join users
        on items.owner_id = users.id
;

結果はこう。

    item    |   user   
------------+----------
 やくそう   | アーサー
 ぬののふく | アーサー
 せいすい   | カイン
 やくそう   | クッキー
            | トンヌラ
(5 rows)

今回のデータで見る leftright の違いは、 トンヌラ という item を持たない user が結果に含まれるかどうかになっています。

さて、これで join が使えるようになりました!

……でも、「結合」ってどういうこと? ってもやもやが残っています。

突然だけど cross join のこと

cross join て普段あんまり使わないし見かけないやつですよね。

でも、ここであえて cross join の話をしたい。

以下のような 2 つのテーブルを用意します。

chars テーブル

 name 
------
 a
 b
 c
(3 rows)

nums テーブル

 name 
------
    1
    2
    3
(3 rows)

この 2 つのテーブルを cross join すると SQL はこうなります。

select
    chars.name as char
    , nums.name as num
from chars
    cross join nums
;

結果は、こう。

 char | num 
------+-----
 a    |   1
 b    |   1
 c    |   1
 a    |   2
 b    |   2
 c    |   2
 a    |   3
 b    |   3
 c    |   3
(9 rows)

全部の組み合わせが返ってきます。

直積とかも言いますね。

これで cross join も使えるようになった!

……でも、正直「使い道がわからない」ですよね。

もう一度 cross join のこと

最初の usersitems の例を cross join でやってみましょう。

users テーブル

 id |   name   
----+----------
  1 | アーサー
  2 | カイン
  3 | クッキー
  4 | トンヌラ
(4 rows)

items テーブル

 owner_id |    name    
----------+------------
        1 | やくそう
        2 | せいすい
        3 | やくそう
        1 | ぬののふく
(4 rows)

これを素直に cross join してみましょう。

select
    items.name as item
    , users.name as user
from items
    cross join users
;

結果はこうなります。

    item    |   user   
------------+----------
 やくそう   | アーサー
 やくそう   | カイン
 やくそう   | クッキー
 やくそう   | トンヌラ
 せいすい   | アーサー
 せいすい   | カイン
 せいすい   | クッキー
 せいすい   | トンヌラ
 やくそう   | アーサー
 やくそう   | カイン
 やくそう   | クッキー
 やくそう   | トンヌラ
 ぬののふく | アーサー
 ぬののふく | カイン
 ぬののふく | クッキー
 ぬののふく | トンヌラ
(16 rows)

想像通り、全部の組み合わせが返ってきました。

でも、こんなの欲しかった結果じゃないですよね。

ここで、今書いた cross join に、こんな where 句を付け加えてみます。

select
    items.name as item
    , users.name as user
from items
    cross join users
where items.owner_id = users.id
;

そうすると、結果はこう。

    item    |   user   
------------+----------
 やくそう   | アーサー
 ぬののふく | アーサー
 せいすい   | カイン
 やくそう   | クッキー
(4 rows)

めでたく欲しかった結果になりました!!

つまり cross join てなんなの?

全ての join は cross join から考えることができます。

cross join の結果から特定の条件に沿うものを取り出したのが、 inner joinouter join の結果。

直接 cross join してなくても、 join を使ってるならみんな cross join を使っているも同然。

SQL 書くときも cross join から考えるとわかりやすい

と、思ってます。

「全部の組み合わせを作ってから、欲しいものだけを拾う」

ということを考えると

「この結果ってどんな SQL 書いたら良いんだろう」

って時に、役に立ちます。

つまりどういうこと?

  1. 必要そうな情報があるテーブルを 2 つ用意して
  2. それらのテーブルの直積をイメージする
  3. 直積の結果から、必要な情報と不要な情報を仕分けるための条件を考える

条件が出てくれば、あとは left なのか right なのか inner なのか、 on に何を書いたら良いのか、わかるはず!

繰り返しになりますが、全ての joincross join がベースにあると考えると SQL がイメージしやすくなります。

つまり「結合」とは?

直積でもって全く新しい集合を作るのが「結合」……というのが自分の理解です。

表を二つ並べるイメージで考えてしまうと、「結合」って「こっちの表とこっちの表をくっつける」という足し算的なイメージになってしまいます。

実際には join は足し算ではなく掛け算だとイメージしておくと、 SQL の実行結果にあまりびっくりしなくなります。


まとめ

  • cross join を知ると join の理解が深まるよ
  • 「結合」は掛け算で新しい集合を作るよ

おまけ

今回使ったテーブルの作成とデータ投入の SQL を置いておきますので、記事中の SQL を試したい方はどうぞ。

drop table if exists users;
create table users (id integer, name varchar);
insert into users (id, name) values (1, 'アーサー'), (2, 'カイン'), (3, 'クッキー'), (4, 'トンヌラ');

drop table if exists items;
create table items (owner_id integer, name varchar);
insert into items (owner_id, name) values (1, 'やくそう'), (2, 'せいすい'), (3, 'やくそう'), (1, 'ぬののふく');

drop table if exists chars;
create table chars (name varchar);
insert into chars (name) values ('a'), ('b'), ('c');

drop table if exists nums;
create table nums (name integer);
insert into nums (name) values (1), (2), (3);