アンケートご協力のお願いgihyo.jpでは,2010年度に向けて豪華プレゼントが当たる読者属性アンケートを実施しております。ご協力ください。

gihyo.jp » DEVELOPER STAGE » 連載 » SQLアタマ養成講座 » 第2回 SQL流条件分岐(2) 列の交換

SQLアタマ養成講座

第2回 SQL流条件分岐(2) 列の交換

列の交換:順列と組み合わせ

2問目は,「列の切り替え」問題の発展版を練習してみましょう。

ある店舗で,売れた商品の明細を顧客ごとに記録する表2のようなテーブルがあるとします。列持ち形式のため,定義されている列数以上の商品が売れたときは記録できないという欠点を抱える設計ですが,最初はその点には目をつぶります(あとでこの欠点に対する解決策も示します)。

表2 Perm2テーブル

cust_id
(顧客ID)
item_1
(商品1)
item_2
(商品2)
001時計浄水器
002携帯電話携帯電話
003浄水器時計
004携帯電話携帯電話
005インクメガネ

さて,このテーブルを使って,一緒に売れた商品のペアを単位としていろいろな分析を行うことを考えます。その際,このテーブルのままだと不都合なことが起きます。顧客IDが001と003の行を見ればわかるように,この2人は,商品の組み合わせとしては {時計,浄水器} という同じペアを買っています。しかしおそらく,テーブルに記録されるときは買った順番で記録したためでしょう,テーブル上では順序を入れ替えた,異なるペアとして存在しています。これでは,単純にitem_1,item_2を「SELECT DISTINCT」で選択したり,GROUP BY句で集約したとしても,正しい商品のペアが求められません。

そこで,表2のようなテーブルから,表3のように商品の並び順を無視した組み合わせを求めましょう。

表3 商品の並び順を無視した組み合わせ

item_1
(商品1)
item_2
(商品2)
時計浄水器
携帯電話携帯電話
インクメガネ

言い方を変えると,順序を意識した「順序集合」を,順序を無視した「非順序集合」へ変換する,ということです。あるいは,学校で習った馴染み深い表現を使うなら,順列(Permutation)を組み合わせ(Combination)へ変換するのです。

CASE式を使えばこんなことも朝飯前です。リスト3のクエリを見てください(図3は実行結果)。

リスト3 組み合わせ⇒順列(重複行排除前)

SELECT 
       CASE WHEN item_1 < item_2 THEN item_1
            ELSE item_2 END AS c1,
       CASE WHEN item_1 < item_2 THEN item_2
            ELSE item_1 END AS c2
  FROM Perm2;

図3 リスト3の実行結果

   c1        c2
--------- ---------
時計       浄水器
携帯電話   携帯電話
時計       浄水器
携帯電話   携帯電話
インク     メガネ

item_1とitem_2は文字列ですので,不等号で比較可能な順序を持っています。ということは,最初の列「c1」に小さいほう,2番目の列「c2」に大きいほうを配置してやれば,同じ要素を持っていて順序だけ異なる順序集合同士も,同じ並び順に配置し直すことができるわけです。あとは,重複行を排除してできあがり(リスト4図4)。

リスト4 組み合わせ⇒順列(重複行排除後)

SELECT DISTINCT 
       CASE WHEN item_1 < item_2 THEN item_1
            ELSE item_2 END AS c1,
       CASE WHEN item_1 < item_2 THEN item_2
            ELSE item_1 END AS c2
  FROM Perm2;

図4 リスト4の実行結果

   c1        c2
--------- ---------
インク     メガネ
携帯電話   携帯電話
時計       浄水器

この方法は,比較したい列が文字型でも数値型でも日付型でも,とにかく順序づけられるデータ型ならば適用できる汎用性の高い方法です。

ではこの問題を一般化します。商品列を3列に増やした場合(表4)はどうなるでしょう。

表4 Perm3テーブル

cust_id
(顧客ID)
item_1
(商品1)
item_2
(商品2)
item_3
(商品3)
001時計浄水器ティッシュ
002ティッシュ浄水器時計
003カレンダーノート時計
004カレンダーノートインク
005文庫本ゲームソフトメガネ
006文庫本メガネゲームソフト

やりたいことは同じなので,求めたい結果は表5のようなものになります。

表5 求めたい結果

item_1
(商品1)
item_2
(商品2)
item_3
(商品3)
インクカレンダーノート
カレンダー ノート時計
ゲームソフトメガネ文庫本
ティッシュ時計浄水器

この場合も,原理的には2列の場合と同じように,不等号の比較条件をCASE式で記述することができるのですが,条件がかなり複雑になりますし,4列,5列…と増えていった場合にはさらに厳しくなります。ちょっと読むに堪えないクエリになるでしょう。

こういう一般化したケースをなんとかしたいという相談が持ち込まれてきたとしたら,私ならリスト5図5のように列持ちの形式を行持ちの形式に直す方法を奨めます。

リスト5 列持ち⇒行持ち

CREATE VIEW CustItems (cust_id, item) AS
SELECT cust_id, item_1
  FROM Perm3
UNION ALL
SELECT cust_id, item_2
  FROM Perm3
UNION ALL
SELECT cust_id, item_3
  FROM Perm3;

図5 リスト5の実行結果

cust_id  item
-------- -----------
001      浄水器
001      時計
001      ティッシュ
002      浄水器
002      ティッシュ
002      時計
003      ノート
003      カレンダー
003      時計
004      ノート
004      カレンダー
004      インク
005      ゲームソフト
005      文庫本
005      メガネ
006      文庫本
006      ゲームソフト
006      メガネ

いったんこの形式に直してしまえば,あとはリスト6のクエリで組み合わせへ変換できます(実行結果は図6)。

リスト6 組み合わせ⇒順列(3列拡張版)

SELECT DISTINCT MIN(CI1.item) AS c1, 
       MIN (CI2.item) AS c2,
       MIN (CI3.item) AS c3
  FROM CustItems CI1
           INNER JOIN CustItems CI2
                   ON CI1.cust_id = CI2.cust_id
                  AND CI1.item < CI2.item
                 INNER JOIN CustItems CI3
                         ON CI2.cust_id = CI3.cust_id
                        AND CI2.item < CI3.item
 GROUP BY CI1.cust_id;

図6 リスト6の実行結果

     c1           c2       c3
------------- ---------- -------
インク        カレンダー ノート
カレンダー    ノート     時計
ゲームソフト  メガネ     文庫本
ティッシュ    時計       浄水器

やはり不等号を使って商品ごとに順序づけを行う,という点はCASE式のときと同じですが,MIN関数を併用することで比較条件を非常に簡潔なものに抑えることができるのです。

このクエリの意味は,次のようなものです。

  • ① まず1人の顧客について,3つの商品の中から最小値を選択する(MIN(CI1.item))
  • ② 次に,その最小値を除いた集合から最小値を選択する(MIN(CI2.item))
  • ③ 最後に,その値も除いた集合から最小値(最後なので1つしか残っていないが)を選択する(MIN(CI3.item))

これなら,商品数が何列に増えても対応できます(ただし,この方法では,同じ商品の組み合わせは選択できなくなります。これに対応する方法は,みなさんも考えてみてください)。このことからテーブル設計においては,例外的なケースを除いて,なるべく列持ちよりは行持ちの形式を採用したほうが拡張的な要件にも耐える,という教訓が得られます。安易に配列やフラットファイルをそのままテーブルの「列」に写し取る無芸な設計をしてしまうと,あとあとまで大きな禍根を抱え込むことになるので,注意が必要です。

著者プロフィール

ミック

SI企業に勤務するDBエンジニア。主にデータウェアハウス業務に従事している。自身のサイト「リレーショナル・データベースの世界」でデータベースとSQLについての技術情報を公開している。『Web+DB Press』で「SQLアタマアカデミー」を連載中。

著書:『達人に学ぶ SQL徹底指南書』(翔泳社、2008)訳書:J.セルコ『SQLパズル 第2版』(翔泳社、2007)

SQLアタマアカデミー:サポートページ

コメント

コメントの記入

パスサポ

多数の情報処理技術者試験対策書籍の発行実績を誇る技術評論社がお届けする,資格試験合格サイト「めざせ! 情報処理試験 パスサポ」が開設されました。

ピックアップ

サクセスストーリーに続く,快適サーバー運用管理のヒント!

データの増大,煩雑な管理,システムダウン,セキュリティなど,迫りくる課題からシステム管理者の負担を軽くするポイントを解説します。

gihyo.jp インフラエンジニア情報局

ネットワークやITにかかわるあらゆる業種で必要とされるインフラエンジニアに向けた技術情報や心構え,その魅力について多角的に紹介。

テストエンジニア ステーション

いま,ITに関わるあらゆる開発業務で注目されつつあるテスト系エンジニアをターゲットにしたコンテンツサイトを展開します。

一行クイックアンケート

gihyo.jpで取り上げてほしいネタは?

※検索はページ右上の検索ボックスをご利用ください。

その他の連載

読むウェブ ~本とインタラクション

ディスプレイで読む活字とそのインタラクション(interaction:相互作用)について,最新Webを紹介しながら読み解いていく。

いま,見ておきたいウェブサイト

この連載では,国内外の最新のウェブサイトを隔週更新で取り上げ,これら最新サイトの特徴や素晴らしい部分を,さまざまな角度から解説していきます。

Windows phoneアプリケーション開発入門

Windows Marcketplace for Mobileがサービス開始され,作成したアプリケーションを個人でも世界をターゲットに公開できる環境が整ってきました。これを機にWindows phoneアプリケーションの開発をしてみませんか?

ここは知っておくべき!Windows Server 2008技術TIPS

5年ぶりのサーバOSとなったWindows Server 2008が出荷されて早2年。2009年にはR2が出荷され,再び注目を集めています。発売前から実施したトレーニングによって感じた,インフラエンジニアの方々に知っておいていただきたい機能を中心にご紹介します。

キーパーソンが見るWeb業界

本連載はWeb Site Expert/gihyo.jpとの連動企画です。阿部淳也, 長谷川敦士, 森田雄のお三方による,Web業界をテーマにした座談会です。

きたみりゅうじの聞かせて珍プレー

ソフトウェア開発の現場で体験したトホホな失敗,思わずうなる珍プレーをきたみりゅうじ氏が四コママンガで紹介。みなさんからの投稿もお待ちしてます!

ActionScript 3.0で始めるオブジェクト指向スクリプティング

野中文雄氏が,簡単なスクリプトは書いたことがあるという初級者を対象に,ActionScript 3.0の基本からクラス定義までを解説します。

まだ間に合う「ITパスポート」受験対策 原山先生の短期合格塾

この連載では,4月18日のITパスポート試験の受験に向けて,短い期間で効率良く受験対策を行う方法や,確実に得点するための裏ワザなどを伝授していきます。

連載一覧

gihyo.jp

  • DEVELOPER STAGE
  • ADMINISTRATOR STAGE
  • WEB+DESIGN STAGE
  • LIFESTYLE STAGE
  • SCIENCE STAGE
  • NEWS & REPORT

書籍案内

  • 新刊書籍
  • 書籍ジャンル一覧
  • 書籍シリーズ一覧
  • 新刊ピックアップ
  • ロングセラー
  • 電脳会議

定期刊行物一覧

  • Software Design
  • WEB+DB PRESS
  • Web Site Expert
  • 組込みプレス