2017.04.20
第2回 縦持ちのデータを横持ちに変換する
プログラミング言語としてのSQLの特徴は、データを集合として取り扱うことです。データ分析の業務では、数万から数億レコードにものぼる大量のデータを、1行単位で確認することは非現実的です。そこで、大量のデータを集約して、少ない指標でデータ全体の特徴を捉える技術が重要となります。
1つの表(テーブル)を対象としたデータの集約や加工手法にはいろいろありますが、本項では書籍のChapter3-3「1つのテーブルに対する操作」から、3-3-3「縦持ちのデータを横持ちに変換する」を紹介します。
この項で登場するSQLは、MAX(CASE~)構文、string_agg関数、listagg関数、collect_list関数です。
SQLは行(レコード)に対して一度に処理を実行することが得意なため、データを保存する際には可能な限りデータを行に分割して保持すると、操作が容易になります。しかし、最終的な出力では、データを列に展開した方が、可読性が高い形式となることも多々あります。
本項では、行単位で保持された「縦持ち」のデータを、列やカンマ区切りの文字列などの「横持ち」の形式に変換する手法を紹介します。
行を列に変換する
最初に、行を列に変換する方法を考えてみましょう。SQLにおける列はクエリによって固定的である必要があるため、列に展開されるデータの種類や数が予め判明している場合にのみ、この手法を利用できます。例えば、下記データ例に示す日次KPIデータは、日付ごとに「インプレッション数」「セッション数」「ユーザー数」の3指標の値を1 レコードごとに保持したテーブルです。
上述の3指標の日付ごとの推移を確認しやすくするために、それぞれの指標を列に展開しましょう。
下記コード例に、行で保存された指標の値を列に変換するクエリを示します。日付ごとに1つのレコードにデータを集約するため、GROUP BY dtを指定します。さらに、「MAX(CASE ~)」構文を利用して、「インプレッション数」「セッション数」「ユーザー数」に該当するレコードのみをCASE式で抽出し、MAX関数でその値を取得しています。今回のクエリでは、1つの日付でCASE式の条件がtrueになるレコードは1 件しかないため、その1 件をMAX関数で取り出すことになります。
dt | impressions | sessions | users ------------+-------------+----------+------- 2017-01-01 | 1800 | 500 | 200 2017-01-02 | 2000 | 700 | 250
行をカンマ区切りの文字列に集約する
前述の通り、行を列に変換する方法は予め列の種類や数が分からなければ使用できません。例えば、下記のデータ例に示す商品の購入詳細ログを考えてみましょう。1つの購入注文で複数個の商品を購入している場合に、商品ごとのデータを1 レコードずつ保持しているテーブルです。ここで、購入IDごとに1レコードにデータを集約しようとしても、1つの購入注文でいくらの商品が購入されるかは予め分からないため、単純に列に展開できません。
purchase_id | product_ids | amount -------------+----------------+-------- 100001 | A001,A002,A003 | 900 100002 | D001,D002 | 800 100003 | A001 | 300
まとめ
本項で紹介した通り、システムで扱いやすいよう縦持ちで保存されたデータを、列や文字列などの横持ちのデータに変換することで、人間が直感的に理解しやすい形式で表示できます。