142
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

【SQL】サブクエリ入門

サブクエリとは、簡単にいうとクエリ内のクエリのこと。もしくはそれを利用したSQLのこと。
考え方は難しくはないが、サブクエリが使える場所は複数あるため、整理しておきたい。

サブクエリの一種である相関サブクエリについては別の記事に書いた。

動作確認環境とテーブル

PostgreSQL 9.4でクエリの動作を確認した。
また、使用したテーブルの定義は下記。

CREATE TABLE students (
  id   INTEGER PRIMARY KEY,
  name VARCHAR(10) NOT NULL,
  age  INTEGER
);

CREATE TABLE courses (
  id   INTEGER PRIMARY KEY,
  name VARCHAR(10) NOT NULL
);

CREATE TABLE enrollments (
  courseId  INTEGER REFERENCES courses(id),
  studentId INTEGER REFERENCES students(id),
  PRIMARY KEY(courseId, studentId)
);

データは適当にINSERTする。

INSERT INTO students VALUES(1,'Sato',14);
INSERT INTO students VALUES(2,'Suzuki', 15);
INSERT INTO students VALUES(3,'Yamada',12);
INSERT INTO students VALUES(4,'Tanaka',14);

INSERT INTO courses VALUES(1,'Math');
INSERT INTO courses VALUES(2,'Japanese');
INSERT INTO courses VALUES(3,'History');

INSERT INTO enrollments VALUES(1,1);
INSERT INTO enrollments VALUES(1,2);
INSERT INTO enrollments VALUES(3,1);
INSERT INTO enrollments VALUES(3,3);

SELECTするとこのようになっているはず。

# select * from students ;
 id |  name  | age 
----+--------+-----
  1 | Sato   |  14
  2 | Suzuki |  15
  3 | Yamada |  12
  4 | Tanaka |  14
(4 rows)



# select * from courses ;
 id |   name   
----+----------
  1 | Math
  2 | Japanese
  3 | History
(3 rows)


# select * from enrollments ;
 courseid | studentid 
----------+-----------
        1 |         1
        1 |         2
        3 |         1
        3 |         3
(4 rows)

いろいろなサブクエリ

FROM句で使う

使い捨てのビューを作成して使うイメージ。

下記のSQLは、生徒テーブルから「生徒の年齢」と「その年齢をもつ生徒の人数」を問い合わせる。

SELECT age, age_count
  FROM (SELECT age, COUNT(age) as age_count
          FROM students
         GROUP BY age) as s1;

 age | age_count 
-----+-----------
  15 |         1
  14 |         2
  12 |         1
(3 rows)

これは、下記のようなビューを考えるとわかりやすい。

CREATE VIEW s1 AS
    SELECT age, COUNT(age) as age_count
      FROM students
     GROUP BY age;

select age, age_count from s1;
 age | age_count 
-----+-----------
  15 |         1
  14 |         2
  12 |         1
(3 rows)

サブクエリ内で集約を用いているが、集約関数を使用した項目に対して絞りこみを行いたい場合はHAVINGではなくWHEREを使う。(ビューを使用した場合と同じ)

SELECT s1.age, s1.age_count
  FROM (SELECT age, COUNT(age) as age_count
          FROM students
         GROUP BY age) as s1
 WHERE age_count = 2;

 age | age_count 
-----+-----------
  14 |         2
(1 row)

WHERE句で使う

これはINといっしょに使われるケースが多い。

下記は、14歳の生徒が受講するコースをすべて表示するSQLである。
SQLでは重複する要素もそのまま重複した状態で出力されるため、"DISTINCT"を指定している。

SELECT DISTINCT courseId
  FROM enrollments
 WHERE studentId IN (SELECT id
                       FROM students
                      WHERE age = 14); 
 courseid 
----------
        1
        3
(2 rows)

これは結合を使って書き換えられる。

SELECT DISTINCT courseId
  FROM enrollments, students
 WHERE enrollments.studentId = students.id
   AND students.age = 14;

 courseid 
----------
        1
        3
(2 rows)

SELECT文の指定列の中で使う

まずは実行例を見る。
下記のSQLは、生徒テーブルの内容を生徒の平均年齢とともに表示する。

SELECT name,
       age,
       (SELECT AVG(age)
          FROM students)
  FROM students;

  name  | age |         avg         
--------+-----+---------------------
 Sato   |  14 | 13.7500000000000000
 Suzuki |  15 | 13.7500000000000000
 Yamada |  12 | 13.7500000000000000
 Tanaka |  14 | 13.7500000000000000
(4 rows)

ポイントは、このサブクエリは単一行を返すことが保証されたクエリでなければならない点だ。
もしも複数行を返しうるクエリがこの場所に書けてしまったら、そのうちどれを表示していいか不明だからだ。

このようなサブクエリを「スカラ・サブクエリ」と呼ぶらしい。(詳細はミック氏の『SQL ゼロからはじめるデータベース操作』を参照)

WHERE句において、INではなく不等号と一緒にサブクエリを使いたい場合も、スカラサブクエリであることが要求される。

多段のサブクエリ

ここまで見てきた1つのSQL文の中にサブクエリが1つ含まれるシンプルなものだったが、サブクエリを重ねて多段にすることもできる。

ただし可読性は低く、使用するケースも多くはないのでここでは割愛する。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
142
Help us understand the problem. What are the problem?