PostgreSQLで検索結果を結合する! UNIONを使ってみた

こんにちは。最近SQLを使う機会が多い関口です。
 
突然ですが、そこのアナタ! 同じような取得結果のSQLを、取得条件が違うからと言って複数作成していませんか? ……それ、1つのSQLで取得できますよ!
 
テンション高めで始まりました。今回は、検索結果を結合して取得できる、UNIONとUNION ALLの使い方を紹介していきます!

検証で使用した環境は以下の通りです。

PostgreSQL
12.3

UNIONは、複数の検索結果(SELECT)を結合して取得できる構文です。検索結果同士の項目数およびデータ型が一致している必要があります。また、UNIONでは、DISTINCTと同様に、結果から重複している行を削除して取得します。
 
今回使用するテーブルは下記の通り作成しました。

// テーブル作成SQL
CREATE TABLE public.table_a (
  user_id integer not null
  , target_date date not null
  , status integer not null
  , calc_value integer
  , primary key (user_id, target_date)
);
CREATE TABLE public.table_b (
  user_id integer not null
  , target_date date not null
  , status integer not null
  , calc_value integer
  , primary key (user_id, target_date)
);

// テーブル内データ
SELECT * FROM table_a;
 user_id | target_date | status | calc_value
---------+-------------+--------+------------
       1 | 2022-04-30  |      1 |        132
       1 | 2022-05-01  |      2 |         31
       1 | 2022-05-02  |      1 |         10
       2 | 2022-04-30  |      2 |         65
       2 | 2022-05-01  |      2 |        101
       2 | 2022-05-02  |      1 |         22
       3 | 2022-05-01  |      1 |         52
       3 | 2022-05-02  |      1 |         15
       4 | 2022-04-30  |      2 |        100
(9 行)

SELECT * FROM table_b;
 user_id | target_date | status | calc_value
---------+-------------+--------+------------
       1 | 2022-04-30  |      2 |         32
       1 | 2022-05-01  |      2 |         55
       1 | 2022-05-02  |      1 |         10
       2 | 2022-05-01  |      1 |        101
       2 | 2022-05-02  |      1 |         22
       3 | 2022-05-01  |      1 |         15
       3 | 2022-05-02  |      2 |         88
       4 | 2022-04-30  |      1 |         42
(8 行)

それでは、実際にUNIONを使ってみましょう! table_aテーブルのtarget_dateが「2022/05/02」のデータと、table_bテーブルのstatusが「1」のデータをまとめて取得してみます。
// 実行SQL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_a
WHERE
  target_date = '2022/05/02'
UNION
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_b
WHERE
  status = '1';

// 実行結果
 user_id | target_date | calc_value
---------+-------------+------------
       1 | 2022-05-02  |         10
       2 | 2022-05-01  |        101
       2 | 2022-05-02  |         22
       3 | 2022-05-01  |         15
       3 | 2022-05-02  |         15
       4 | 2022-04-30  |         42
(6 行)

ほしいデータが取得できていて、重複データは削除されていますね。内部結合(INNER JOIN)や外部結合(LEFT JOIN)とは違って、データを縦持ちで取得することが出来るので、用途によって使い分けましょう。

UNION ALLは、UNIONと同様に、複数の検索結果(SELECT)を結合して取得できる構文です。UNION ALLでは、結果から重複している行もそのまま取得されます。重複チェックを行わない分、UNIONよりも処理速度が速くなります。
 
先ほど使用したSQLを、UNION ALLに変えて実行してみます!

// 実行SQL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_a
WHERE
  target_date = '2022/05/02'
UNION ALL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_b
WHERE
  status = '1';

// 実行結果
 user_id | target_date | calc_value
---------+-------------+------------
       1 | 2022-05-02  |         10
       1 | 2022-05-02  |         10
       2 | 2022-05-01  |        101
       2 | 2022-05-02  |         22
       2 | 2022-05-02  |         22
       3 | 2022-05-01  |         15
       3 | 2022-05-02  |         15
       4 | 2022-04-30  |         42
(8 行)

今回は重複データも取得できました! 重複を削除しなくてもいい場合は、UNION ALLを使用しましょう。

では試しに、項目数の違う検索結果をUNIONで結合してみましょう。

// 実行SQL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_a
UNION ALL
SELECT
  user_id
  , target_date
FROM
  table_b;

// 実行結果
ERROR:  すべてのUNION問い合わせは同じ列数を返す必要があります
行 9:   user_id

想定通りエラーになりました。項目数だけではなく、データ型が違う場合にもエラーになってしまうので注意しましょう。

こちらは私が実際にハマったエラーになります。先ほど使用したSQLに、並び替え(ORDER BY)を追加します。

// 実行SQL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_a
WHERE
  target_date = '2022/05/02'
ORDER BY
  target_date
UNION ALL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_b
WHERE
  status = '1'
ORDER BY
  target_date;

// 実行結果
ERROR:  "UNION"またはその近辺で構文エラー
行 11: UNION ALL

エラーになってしまいました。エラー内容を見ても何が悪いのか最初はわからなかったのですが、並び替え(ORDER BY)は取得結果の最後に行わなければいけないそうです。つまり、こう!
// 実行SQL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_a
WHERE
  target_date = '2022/05/02'
UNION ALL
SELECT
  user_id
  , target_date
  , calc_value
FROM
  table_b
WHERE
  status = '1'
ORDER BY
  target_date
  , user_id;

// 実行結果
 user_id | target_date | calc_value
---------+-------------+------------
       4 | 2022-04-30  |         42
       2 | 2022-05-01  |        101
       3 | 2022-05-01  |         15
       1 | 2022-05-02  |         10
       1 | 2022-05-02  |         10
       2 | 2022-05-02  |         22
       2 | 2022-05-02  |         22
       3 | 2022-05-02  |         15
(8 行)

エラーが出ずに、並び替えてデータを取得することが出来ました! なるほど。確かに単純に考えても、検索結果を結合(UNION)する前に並び替え(ORDER BY)しても意味なさそう。勉強になりました。

いかがでしたでしょうか。
 
UNIONを知らなかった方も、そうでない方にも、お役に立てれば幸いです。UNIONの他にも、検索結果を結合できる構文がありますので、またの機会に紹介したいと思います!SQLは奥が深いですね~!
 
 
 
 
《関連記事》

記事をシェア
MOST VIEWED ARTICLES