SELECT文を統合する「UNION」SQL実践講座(9)

» 2001年03月14日 00時00分 公開
[篠原光太郎@IT]

SELECT文の統合「UNION」

 今回は、問い合わせの統合(UNION)と、NULLに関連するトピックを解説し、SELECT文の最後の回とします。

 UNION句を使用すると、これまでに使用してきたSQL文の結果を統合して、1つの結果とすることができます。これは、四半期ごとの売り上げデータを別のテーブルに持っている場合に、集計した結果を1つの表として参照したい場合など、複数の問い合わせ結果のマージを実施したい場合に役立ちます。

 今回は、製品ごとに付けられた単価のパターンをすべて洗い出す、というSQL文を作成してみましょう。製品の単価は、製品マスターテーブル(Products)に基本となる単価が保存されています。また、取引ごとに製品の単価を決定しているので、注文の明細テーブル(Order Details)にも、その取引における製品の単価が登録されています。これらの2つのテーブルからの別々の問い合わせを、UNION句を使用して統合します。

 ではまず、次のSQL文を実行してみましょう。

【例1】

SELECT ProductName, UnitPrice
FROM Products
画面1 ProductsテーブルのすべてのProductNameとUnitPriceを表示してみたところ(画面をクリックすると拡大表示します) 画面1 ProductsテーブルのすべてのProductNameとUnitPriceを表示してみたところ(画面をクリックすると拡大表示します)

 例1は、Productsテーブルのすべての行を返すSQL文ですね。行数は77行であることが、クエリーアナライザウィンドウの右下に表示されているので確認できます。

 続いて、次のSQL文を実行しましょう。

【例2】

SELECT distinct prod.ProductName, ordd.UnitPrice
FROM "Order Details"ordd INNER JOIN Products prod
  ON ordd.ProductID = prod.ProductID
画面2 Order Detailsテーブル上から、すべてのProductNameとUnitPriceの組み合わせを表示してみたところ。Order DetailsにはProductNameがないので、Productsテーブルを結合している(画面をクリックすると拡大表示します) 画面2 Order Detailsテーブル上から、すべてのProductNameとUnitPriceの組み合わせを表示してみたところ。Order DetailsにはProductNameがないので、Productsテーブルを結合している(画面をクリックすると拡大表示します)

 例2では、Order Detailsテーブルから、すべてのProductNameとUnitPriceの組み合わせを返しています。Order DetailsテーブルにはProductNameが保存されていませんので、Productsテーブルと結合して参照をしています。また、同じProductNameとUnitPriceの組み合わせは省くために、distinctキーワードを指定しています。結果の行数は、156行であることが確認できます。

 では、これらの結果を、UNION句を使用して統合してみましょう。次のSQL文を実行します。

【例3】

SELECT ProductName, UnitPrice
FROM Products
UNION
SELECT distinct prod.ProductName, ordd.UnitPrice
FROM "Order Details"ordd INNER JOIN Products prod
  ON ordd.ProductID = prod.ProductID
画面3 UNIONを使って、例1と例2の結果をマージしてみたところ(画面をクリックすると拡大表示します) 画面3 UNIONを使って、例1と例2の結果をマージしてみたところ(画面をクリックすると拡大表示します)

 例1のSQL文と、例2のSQL文の間に、「UNION」句を追加しているのが分かりますね。UNION句により、それぞれのSELECT文の結果がマージされ、1つの結果として返されるようになります。

 UNION句でマージする複数のSELECT文は、2つの条件を満たしている必要があります。

(1)SELECT句に指定するカラムのデータ型が同じか、もしくは、変換可能であること

(2)SELECT句のカラム数が同じこと

 これらの条件が満たされていれば、2つ以上のSELECT文をマージすることも可能です。

 ところで、例3の結果の行数を確認すると159行ですね。例1は77行、例2は156行でしたので、単純に足した結果ではないことが分かります。これは、UNION句は標準では、同じ行は結果から省く操作が行われるためです。よって、実は2つ目のSELECT文にdistinctキーワードが含まれなくとも、例3の結果は同一となります。

 同じ行も結果に含めたい場合(行数が意味を持つ場合など)は、UNIONにALLキーワードを追加します。

【例4】

SELECT ProductName, UnitPrice
FROM Products
UNION ALL
SELECT distinct prod.ProductName, ordd.UnitPrice
FROM "Order Details"ordd INNER JOIN Products prod
  ON ordd.ProductID = prod.ProductID

 結果の行数を確認すると233行になっており、例1の77行と例2の156行を足した行数と等しくなっているのが確認できますね。

 結果を並べ替えたい場合には、最後にORDER BY句を指定します。

【例5】

SELECT ProductName, UnitPrice
FROM Products
UNION
SELECT distinct prod.ProductName, ordd.UnitPrice
FROM "Order Details"ordd INNER JOIN Products prod
  ON ordd.ProductID = prod.ProductID
ORDER BY ProductName, UnitPrice
画面4 ORDER BY句でUNIONによるマージ結果を並べ替えてみたところ(画面をクリックすると拡大表示します) 画面4 ORDER BY句でUNIONによるマージ結果を並べ替えてみたところ(画面をクリックすると拡大表示します)

 結果が並べ替えられたのが確認できます。ORDER BY句は、最初のSELECT文に指定するとエラーとなりますので注意が必要です。

NULLの取り扱い

 次回以降で必要になってくる概念として「NULL」があります。これまでは「外部結合」の解説の際に一部取り上げました。「NULL」について、もう少し詳しく見てみましょう。

 ところで、「NULL」とは何でしょうか? これは、「データがない」もしくは「未定義」であることを表す単語です。例題として、次のSQL文を実行してみましょう。

【例6】

SELECT CustomerID, ContactName, Phone, Fax
FROM Customers
画面5 Customersテーブルの表示結果。全部で91件表示される(画面をクリックすると拡大表示します) 画面5 Customersテーブルの表示結果。全部で91件表示される(画面をクリックすると拡大表示します)

 顧客マスターであるCustomersテーブルの一部を表示しています。ここで、Fax列をながめてみると、NULLと表示されている行が何行かあるのが確認できますね。これは、NULLというデータを表しているわけではなく、「データがない」もしくは「未登録」であることを表しています。ただし、注意しなくてはいけないのは、本当に文字列として「NULL」と登録されているのか、「未登録」であることの「NULL」であるのかは、SQL文の結果からだと見分けがつかないということです。

 NULLは「値ではない」ということには、十分注意する必要があります。例えば、SELECT文の条件にNULLが含まれる列を指定する場合を見てみましょう。次のSQL文を実行します。

【例7】

SELECT CustomerID, ContactName, Phone, Fax
FROM Customers
WHERE Fax like '(0%'
画面6 画面5の結果から「(0〜」という文字列で始まるデータを条件抽出してみた結果。2件のデータがあることがわかる(画面をクリックすると拡大表示します) 画面6 画面5の結果から「(0〜」という文字列で始まるデータを条件抽出してみた結果。2件のデータがあることがわかる(画面をクリックすると拡大表示します)

【例8】

SELECT CustomerID, ContactName, Phone, Fax
FROM Customers
WHERE Fax not like '(0%'
画面7 画面6のとは逆に、「(0〜」という文字で始まらないデータを抽出してみたところ。条件の否定にNOTキーワードを使用している。68件のデータが表示された(画面をクリックすると拡大表示します) 画面7 画面6のとは逆に、「(0〜」という文字で始まらないデータを抽出してみたところ。条件の否定にNOTキーワードを使用している。68件のデータが表示された(画面をクリックすると拡大表示します)

 ここで確認していただきたいのは、結果の行数です。例7は2行、例8は68行ですね。例7のWHERE句の条件式をNOTで否定したのが例8ですので、本来であれば例7の結果行数と例8の結果行数を足すと、Customersの全体の行数になるはずですが、例6をもう一度確認すると、Customersは91行あることが確認できます。つまり、21行分が例7例8の検索から漏れていることが分かります。

 この21行が何かを調べるために、次のSQL文を実行しましょう。

【例9】

SELECT CustomerID, ContactName, Phone, Fax
FROM Customers
WHERE Fax is Null
画面8 CustomersテーブルのFax列でNullを含む行を抜き出したところ。全部で21件の結果が表示された(画面をクリックすると拡大表示します) 画面8 CustomersテーブルのFax列でNullを含む行を抜き出したところ。全部で21件の結果が表示された(画面をクリックすると拡大表示します)

 例9のSELECT文は、WHERE句に「Fax is Null」と指定して、Fax列がNullの行を検索しています。ここで、「Fax = Null」ではなく「is」ステートメントを使用しているところが重要です。つまり、Nullは値ではないために、通常の検索条件で使用する「=」などのステートメントは使えないということなのです。

 結果の行数を確認すると21行で、先ほどの例7例8とこの例9を合わせると、Customers全体になることが分かります。例7例8では、「like」ステートメントによる検索結果には、Nullは含まれないため、結果の行数を足し合わせても全体の行数にはならなかったということです。

 is Nullは、notで否定することが可能です。次のSQL文を実行してみましょう。

【例10】

SELECT CustomerID, ContactName, Phone, Fax
FROM Customers
WHERE Fax is not Null

 Nullを含む列をOrder By句に指定した場合は、どんな値よりも小さい値としてNullは扱われます。次のSQL文を実行してみましょう。

【例11】

SELECT CustomerID, ContactName, Phone, Fax
FROM Customers
Order By Fax
画面9 Fax列をキーにCustomersの並べ替えを行ったところ。Nullはどんな値よりも小さい値として扱われていることがわかる(画面をクリックすると拡大表示します) 画面9 Fax列をキーにCustomersの並べ替えを行ったところ。Nullはどんな値よりも小さい値として扱われていることがわかる(画面をクリックすると拡大表示します)

 Nullを含む列をGroup By句に指定すると、Nullも1つのデータとして集計されます。次のSQL文を実行してみましょう。

【例12】

SELECT Fax
FROM Customers
Group By Fax
画面10 Nullは、Group By句による集計でデータの1つとして扱われていることがわかる(画面をクリックすると拡大表示します) 画面10 Nullは、Group By句による集計でデータの1つとして扱われていることがわかる(画面をクリックすると拡大表示します)

今回のまとめ

9回にわたって、SELECT文の解説をしてきました。SELECT文には、さらに複雑な使用方法もありますが、よく使うSELECT文は一通り解説をすることができました。次回からは、データの更新(UPDATE)、データの挿入(INSERT)、テーブルの作成(CREATE)の順で、解説をしていきます



「SQL実践講座」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。