第2回 縦持ちのデータを横持ちに変換する|Tech Book Zone Manatee

マナティ

ビッグデータ分析・活用のためのSQLレシピ

第2回 縦持ちのデータを横持ちに変換する

プログラミング言語としてのSQLの特徴は、データを集合として取り扱うことです。データ分析の業務では、数万から数億レコードにものぼる大量のデータを、1行単位で確認することは非現実的です。そこで、大量のデータを集約して、少ない指標でデータ全体の特徴を捉える技術が重要となります。
1つの表(テーブル)を対象としたデータの集約や加工手法にはいろいろありますが、本項では書籍のChapter3-3「1つのテーブルに対する操作」から、3-3-3「縦持ちのデータを横持ちに変換する」を紹介します。
この項で登場するSQLは、MAX(CASE~)構文、string_agg関数、listagg関数、collect_list関数です。

 SQLは行(レコード)に対して一度に処理を実行することが得意なため、データを保存する際には可能な限りデータを行に分割して保持すると、操作が容易になります。しかし、最終的な出力では、データを列に展開した方が、可読性が高い形式となることも多々あります。
 本項では、行単位で保持された「縦持ち」のデータを、列やカンマ区切りの文字列などの「横持ち」の形式に変換する手法を紹介します。

行を列に変換する

 最初に、行を列に変換する方法を考えてみましょう。SQLにおける列はクエリによって固定的である必要があるため、列に展開されるデータの種類や数が予め判明している場合にのみ、この手法を利用できます。例えば、下記データ例に示す日次KPIデータは、日付ごとに「インプレッション数」「セッション数」「ユーザー数」の3指標の値を1 レコードごとに保持したテーブルです。

●データ:日次KPIデータ(daily_kpi)テーブル
     dt     | indicator   | val
------------+-------------+------
 2017-01-01 | impressions | 1800
 2017-01-01 | sessions    |  500
 2017-01-01 | users       |  200
 2017-01-02 | impressions | 2000
 2017-01-02 | sessions    |  700
 2017-01-02 | users       |  250
 

 上述の3指標の日付ごとの推移を確認しやすくするために、それぞれの指標を列に展開しましょう。
 下記コード例に、行で保存された指標の値を列に変換するクエリを示します。日付ごとに1つのレコードにデータを集約するため、GROUP BY dtを指定します。さらに、「MAX(CASE ~)」構文を利用して、「インプレッション数」「セッション数」「ユーザー数」に該当するレコードのみをCASE式で抽出し、MAX関数でその値を取得しています。今回のクエリでは、1つの日付でCASE式の条件がtrueになるレコードは1 件しかないため、その1 件をMAX関数で取り出すことになります。

●コード:行で保存された指標の値を列に変換するクエリ
SELECT
    dt
  , MAX(CASE WHEN indicator = 'impressions' THEN val END) AS impressions
  , MAX(CASE WHEN indicator = 'sessions' THEN val END) AS sessions
  , MAX(CASE WHEN indicator = 'users' THEN val END) AS users
FROM daily_kpi
GROUP BY dt
ORDER BY dt
;

     dt     | impressions | sessions | users
------------+-------------+----------+-------
 2017-01-01 |        1800 |      500 |   200
 2017-01-02 |        2000 |      700 |   250

行をカンマ区切りの文字列に集約する

 前述の通り、行を列に変換する方法は予め列の種類や数が分からなければ使用できません。例えば、下記のデータ例に示す商品の購入詳細ログを考えてみましょう。1つの購入注文で複数個の商品を購入している場合に、商品ごとのデータを1 レコードずつ保持しているテーブルです。ここで、購入IDごとに1レコードにデータを集約しようとしても、1つの購入注文でいくらの商品が購入されるかは予め分からないため、単純に列に展開できません。

●コード:行を集約してカンマ区切りの文字列に変換するクエリ
SELECT
    purchase_id

    -- 商品IDを配列に集約し、カンマ区切りの文字列に変換
    -- ■ PostgreSQL, BigQueryの場合はstring_aggを用いる
  , string_agg(product_id, ',') AS product_ids

    -- ■ Redshiftの場合はlistaggを用いる
    -- , listagg(product_id, ',') AS product_ids

    -- ■ Hive, SparkSQLの場合はcollect_listとconcat_wsを用いる
    -- , concat_ws(',', collect_list(product_id)) AS product_ids
  , SUM(price) AS amount
FROM purchase_detail_log
GROUP BY purchase_id
ORDER BY purchase_id
;

 purchase_id | product_ids    | amount
-------------+----------------+--------
      100001 | A001,A002,A003 |    900
      100002 | D001,D002      |    800
      100003 | A001           |    300

まとめ
 本項で紹介した通り、システムで扱いやすいよう縦持ちで保存されたデータを、列や文字列などの横持ちのデータに変換することで、人間が直感的に理解しやすい形式で表示できます。

著者プロフィール

加嵜 長門(著者)
株式会社DMM.comラボ所属。慶應義塾大学大学院 政策・メディア研究科修士課程修了。大学院や学生ベンチャーにて、マルチメディアデータベースを対象とした検索やレコメンドアルゴリズムの研究およびサービス開発に従事し、現在DMM.comラボではビッグデータ活用基盤の構築に携わり、SparkやSQL on Hadoopを用いたレコメンド機能、ビッグデータ活用の研究開発を担当。 共著に『詳解Apache Spark』(技術評論社)。
田宮 直人(著者)
データコンサルタント。エンジニアとして大手新聞社の関連サービス、求人サービス、コミュニティサービスの開発に携わり、株式会社サイバーエージェント在籍時にデータアナリストへ転身、株式会社DMM.comラボではマーケティング開発部マネージャーとしてビッグデータ部を立ち上げる。現在はフリーランスとして、データの解析のみならず、データ解析環境の設計・構築、ログの設計、レコメンドAPIの作成など、データに関連する業務全般を担当している。