【SQL, Docker, Github】初心者向けのTIP集~データサイエンス100本ノック~

datascience

The-Japan-DataScientist-SocietyのSQL編を解き終わったので、回答する際に閲覧したサイトを忘備録として、まとめておきます。

1. データサイエンス100本ノック概要

以下、The-Japan-DataScientist-Societyから引用です。

  • データサイエンス100本ノックは構造化データ加工編である
  • 演習問題はSQL、Python、Rで共通
  • 言語によっては向かない設問もあるが、「この言語のときはこう書けば実現できる」という技術習得を目指すことを優先
  • 個人情報のように見える項目は全てダミーデータを利用
  • 大学、企業など組織でのご利用にあたっては、「データサイエンティスト協会スキル定義委員」の「データサイエンス100本ノック(構造化データ加工編)」を利用していることを明示いただければ自由に利用してOK
  • データサイエンス100本ノック(構造化データ加工編)の利用に関するご質問等について、個別での対応は受けかねますので予めご了承ください

演習問題を行うには、GitとDockerの準備が必要です。

以下では、引用したサイトと100本ノック内で関連のある問題に対し、私の回答を使用例として記載しています。

2. クエリ関連

2-1. With句

SQLではWith句が使用できるかで、コードの書き方がかなり異なる印象を持ちました。利用できるようにしておくとデータ処理がはかどります。

WITH句を使えば「クエリ内に新たにテーブルを作る」ことができます。サブクエリ自体もクエリの中にクエリを書くので、非常に似ているのですが、WITH句で書くとそのテーブル内で何度でも使い回すことができる点が大きく違います。

【SQL】サブクエリはWTIH句が最強。可読性こそ現場での正義。
""" S-053: 顧客テーブル(customer)の郵便番号(postal_cd)に対し、東京(先頭3桁が100〜209のもの)を1、それ以外のものを0に2値化せよ。さらにレシート明細テーブル(receipt)と結合し、全期間において買い物実績のある顧客数を、作成した2値ごとにカウントせよ。"""
%%sql
WITH customer_postal as (
    SELECT 
        customer_id,
        postal_cd,
        CASE 
            WHEN CAST(SUBSTR(postal_cd, 1, 3) AS INTEGER) >= 100
                and CAST(SUBSTR(postal_cd, 1, 3) AS INTEGER) <= 209 THEN 1
            ELSE 0
        END AS postal_flg
    FROM
        customer
),
receipt_sum as (
    SELECT customer_id, sum(amount)
    FROM receipt
    GROUP BY customer_id
)
SELECT
    cp.postal_flg, count(1)
FROM customer_postal as cp
INNER JOIN receipt_sum as rs ON cp.customer_id = rs.customer_id
GROUP BY cp.postal_flg

2-2. JOIN句

こちらもデータ結合ではよくお世話になるクエリです。データ処理では必須と思います。100本ノックでもよく使います。

複数テーブルの結合を行いたい!

【SQL】これで完璧テーブル結合!JOINの種類と使い方を一覧まとめ
""" S-036: レシート明細テーブル(receipt)と店舗テーブル(store)を内部結合し、レシート明細テーブルの全項目と店舗テーブルの店舗名(store_name)を10件表示させよ。 """
%%sql
SELECT
    re.*, st.store_name
FROM
    receipt as re
INNER JOIN store as st ON re.store_cd = st.store_cd
LIMIT 10    

2-3. CASE句

Pythonでいうif文に近いですかね。最後の”END”を忘れそうになるので要注意です。

まずCASEとは、一言で言えば「条件分岐を行うための命令」と言えるでしょう。非常に簡単に書けますし、SELECTだけでなくUPDATEでも使用することもできるため、汎用性にも長けます。

【SQL】5分でわかるCASE式!SELECTもUPDATEも自在に条件分岐させよう
""" # S-052: レシート明細テーブル(receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計の上、売上金額合計に対して2000円以下を0、2000円超を1に2値化し、顧客ID、合計金額とともに10件表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。"""
%%sql
SELECT 
    customer_id,
    sum(amount),
    CASE
        WHEN SUM(amount) >= 2000 THEN 1
        ELSE 0
    END
FROM receipt
WHERE customer_id not like 'Z%'
GROUP BY customer_id
LIMIT 10

2-4. データ読み書き(csv, tsvファイル)

csvの他にtsv処理もあるので、記載しておきます。

PostgreSQLCOPYコマンドは、テーブルDBサーバ内のファイル間でデータのやり取り(読み込み/書き出し)をするためのコマンドです。

負荷テスト用に大量データを投入する際に利用したり、テキストベースでバックアップを取ったりする際に利用することができます。

【PostgreSQL】一括でデータを入出力できるCOPYコマンド
""" S-094: 先に作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。出力先のパスは'/tmp/data'を指定することでJupyterの/'work/data'と共有されるようになっている。なお、COPYコマンドの権限は付与済みである。 """
%%sql
COPY product_full TO '/tmp/data/S_product_full_UTF-8_header.csv' WITH CSV HEADER encoding 'UTF-8'
""" S-099: 先に作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。出力先のパスは'/tmp/data'を指定することでJupyterの/'work/data'と共有されるようになっている。なお、COPYコマンドの権限は付与済みである。 """
%%sql
COPY product_full TO '/tmp/data/S_product_full_UTF-8_header.tsv' WITH CSV HEADER DELIMITER E'\t' encoding 'UTF-8'

3. データ分析関連

3-1. 標準偏差

SQLでの標準偏差の求め方です。

SQL ServerやOracleで標準偏差(Standard Deviation)を求めるにはSTDDEVもしくはSTDEVを使います。標準偏差とは、データの散らばりの度合いを表し、標準偏差の値が小さいほど、散らばり度合いが小さい、つまりデータ全体がある値に偏っている事がわかります。逆に標準偏差が大きいと、散らばり度合いが大きく、データ全体が分散している事がわかります。

【SQL集計関数】STDDEV、STDEV – 標準偏差を求める(SQL Server、Oracle)
""" S-031: レシート明細テーブル(receipt)に対し、店舗コード(store_cd)ごとに売上金額(amount)の標本標準偏差を計算し、降順にTOP5を表示せよ。 """
%%sql
SELECT store_cd,
    ROUND(STDDEV_SAMP(amount), 1) as std_sample
FROM receipt
GROUP BY store_cd
ORDER BY std_sample DESC
LIMIT 5

3-2. パーセンタイル

4分位計算などを行うときに使用します。統計処理ではよく使いました。

分析関数としては、PERCENTILE_CONTとPERCENTILE_DISCの二つがある。どちらもパーセンタイル値を計算できるようなのだが、使い方が他の分析関数と比べて微妙に違う。

SQL分析関数 PERCENTILE_CONT PERCENTILE_DISC 中央値(MEDIAN)
""" S-028: レシート明細テーブル(receipt)に対し、店舗コード(store_cd)ごとに売上金額(amount)の中央値を計算し、降順でTOP5を表示せよ。 """
%%sql
SELECT store_cd,
   PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) as median
FROM receipt
GROUP BY store_cd
ORDER BY median
LIMIT 5

3-3. NULL処理

pythonでいうnanの処理です。

coalesce は、与えられた引数のうち、NULLでない最初の引数を返してくれます。データを表示する目的で取り出すシチュエーションなどで、NULL値の代わりにデフォルト値を使っている場合に便利です。CASE や NULLIF などを用いても同様の効果が得られますが、coalesce() を使えば、より完結に記述することができます。

SQL関数coalesceの使い方と読み方
""" S-038: 顧客テーブル(customer)とレシート明細テーブル(receipt)から、各顧客ごとの売上金額合計を求めよ。ただし、買い物の実績がない顧客については売上金額を0として表示させること。また、顧客は性別コード(gender_cd)が女性(1)であるものを対象とし、非会員(顧客IDが'Z'から始まるもの)は除外すること。なお、結果は10件だけ表示させれば良い。 """
%%sql
WITH customer_amount as (
    SElECT
        customer_id,
        SUM(amount) as sum_amount
    FROM 
        receipt
    GROUP BY customer_id
)
SELECT
    cu.customer_id,
    COALESCE(ca.sum_amount, 0)
FROM 
    customer as cu
LEFT JOIN customer_amount as ca 
    ON cu.customer_id = ca.customer_id
WHERE 
    cu.customer_id not like 'Z%' and cu.gender_cd = '1'
LIMIT 10

3-4. 重複行の削除

機械学習などでダミー変数を用意するときは必須の関数ですね。

DISTINCTとは、SELECT文の実行結果の重複レコード(データ行)を1つにまとめるための便利な構文です。

【SQL入門】DISTINCTで重複行をまとめる方法をわかりやすく解説
""" S-039: レシート明細テーブル(receipt)から売上日数の多い顧客の上位20件と、売上金額合計の多い顧客の上位20件を抽出し、完全外部結合せよ。ただし、非会員(顧客IDが'Z'から始まるもの)は除外すること。 """
%%sql
WITH customer_sales_days as (
    SELECT customer_id, count(distinct sales_ymd) as count_days
    FROM receipt
    WHERE customer_id not like 'Z%'
    GROUP BY customer_id
    ORDER BY count_days DESC    
),
customer_amount as (
    SELECT customer_id, sum(amount) as sum_amount
    FROM receipt
    WHERE customer_id not like 'Z%'
    GROUP BY customer_id
    ORDER BY sum_amount DESC
)
SELECT
    COALESCE(cs.customer_id, ca.customer_id), cs.count_days, ca.sum_amount
FROM
    customer_sales_days as cs
FULL JOIN customer_amount as ca
    ON cs.customer_id = ca.customer_id
LIMIT 10

3-5. データLAG, LEAD

データ表示を指定数分だけ前後にずらすことができます。差分などを計算するときに便利そうですね。

LAG関数:前の行を持ってくる関数

LEAD関数:後ろの行を持ってくる関数

【BigQuery】LAG関数,LEAD関数の使い方
""" S-041: レシート明細テーブル(receipt)の売上金額(amount)を日付(sales_ymd)ごとに集計し、前日からの売上金額増減を計算せよ。なお、計算結果は10件表示すればよい。 """
%%sql
WITH customer_amount as (
    SELECT 
        sales_ymd, sum(amount) as sum_amount
    FROM receipt
    GROUP BY sales_ymd
)
SELECT
    ca.sales_ymd,
    LAG (ca.sales_ymd, 1) OVER (ORDER BY ca.sales_ymd) as lag_days,
    ca.sum_amount,
    LAG (ca.sum_amount, 1) OVER (ORDER BY ca.sales_ymd) as lag_amount,
    ca.sum_amount - LAG (ca.sum_amount, 1) OVER (ORDER BY ca.sales_ymd) as amount_diff
FROM customer_amount as ca
LIMIT 10

3-6. 型変換1

型変換の一つ、データ処理では方は常に意識しておくとエラーを減らせます。

TO_CHARは数値や日時を文字列へ変換するOracle SQL関数である。

TO_CHAR

文字列をタイムスタンプ型に変換

TO_TIMESTAMP、TO_TIMESTAMP_TZ
""" S-045: 顧客テーブル(customer)の生年月日(birth_day)は日付型(Date)でデータを保有している。これをYYYYMMDD形式の文字列に変換し、顧客ID(customer_id)とともに抽出せよ。データは10件を抽出すれば良い。 """
%%sql
SELECT 
    customer_id,
    TO_CHAR(birth_day, 'YYYYMMDD') as birth_day 
FROM customer 
LIMIT 10
""" S-070: レシート明細テーブル(receipt)の売上日(sales_ymd)に対し、顧客テーブル(customer)の会員申込日(application_date)からの経過日数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに表示せよ。結果は10件表示させれば良い(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。 """
%%sql
WITH receipt_distinct as (
    SELECT distinct
        customer_id,
        sales_ymd    
    FROM receipt
)
SELECT
    cu.customer_id,
    rd.sales_ymd as sales_date,
    cu.application_date as apply_date,
    EXTRACT(DAY FROM (TO_TIMESTAMP(CAST(rd.sales_ymd as VARCHAR), 'YYYYMMDD')
                    - TO_TIMESTAMP(cu.application_date, 'YYYYMMDD')))
FROM
   receipt_distinct as rd 
JOIN customer as cu ON cu.customer_id = rd.customer_id
LIMIT 10

3-7. 型変換2

型変換に使用します。文字列<=>数値などに対応できます。

CAST または CONVERT 関数を使用します。

SQL Server – 数値型(INT, DECIMALなど)を文字列(CHAR、VARCHAR、NVARCHAR)に変換する
""" S-047: レシート明細テーブル(receipt)の売上日(sales_ymd)はYYYYMMDD形式の数値型でデータを保有している。これを日付型(dateやdatetime)に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。 """
%%sql
SELECT 
    TO_DATE(CAST(sales_ymd AS varchar), 'YYYYMMDD'), 
    receipt_no, 
    receipt_sub_no 
FROM receipt 
LIMIT 10

3-8. 日付関数EXTRAC

日付データから特定の項目(YEAR, MONTH, DAYなど)を取得できます。

100本ノックでは、上記の型変換とよく併用しますね。

Oracleで、日付値から任意の日付要素(年、月、日など)を取得するにはEXTRACT関数を使います。例えば、EXTRACT関数を使えば、2008-09-22という日付値から、月を取得すると09という値を取得することができます。

【SQL日付関数】EXTRAC – 日付から任意の日付要素を取得する (Oracle)
""" S-049: レシート明細テーブル(receipt)の販売エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"年"だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。"""
%%sql
SELECT 
    TO_CHAR(EXTRACT(YEAR FROM TO_TIMESTAMP(sales_epoch)),'FM9999') as sales_year,
    receipt_no,
    receipt_sub_no
FROM receipt
LIMIT 10

3-9. 文字列の部分取得

Pythonでいうスライスです。

文字列の編集、部分文字列を取り出す


SUBSTR、SUBSTRB
""" S-053: 顧客テーブル(customer)の郵便番号(postal_cd)に対し、東京(先頭3桁が100〜209のもの)を1、それ以外のものを0に2値化せよ。さらにレシート明細テーブル(receipt)と結合し、全期間において買い物実績のある顧客数を、作成した2値ごとにカウントせよ。"""
%%sql
WITH customer_postal as (
    SELECT 
        customer_id,
        postal_cd,
        CASE 
            WHEN CAST(SUBSTR(postal_cd, 1, 3) AS INTEGER) >= 100
                and CAST(SUBSTR(postal_cd, 1, 3) AS INTEGER) <= 209 THEN 1
            ELSE 0
        END AS postal_flg
    FROM
        customer
),
receipt_sum as (
    SELECT customer_id, sum(amount)
    FROM receipt
    GROUP BY customer_id
)
SELECT
    cp.postal_flg, count(1)
FROM customer_postal as cp
INNER JOIN receipt_sum as rs ON cp.customer_id = rs.customer_id
GROUP BY cp.postal_flg

3-10. 切り捨て、切り上げ

Pythonでいう”floor, ceil”です。

小数点以下を切り捨てる、一の位を切り捨てる、十の位を・・・


TRUNC

ある数値以下の最大の整数を求める、数値以上の最小の整数を求める


CEIL、FLOOR
""" S-056: 顧客テーブル(customer)の年齢(age)をもとに10歳刻みで年代を算出し、顧客ID(customer_id)、生年月日(birth_day)とともに抽出せよ。ただし、60歳以上は全て60歳代とすること。年代を表すカテゴリ名は任意とする。先頭10件を表示させればよい。df_store.query("tel_no.str.contains('^[0-9]{3}-[0-9]{3}-[0-9]{4}$')", engine='python')"""
%%sql
SELECT 
    customer_id,
    birth_day,
    LEAST(CAST(TRUNC(age/10)*10 AS INTEGER), 60) as era
FROM
    customer
GROUP BY customer_id, birth_day
ORDER BY era desc
LIMIT 10
""" S-067: 商品テーブル(product)の各商品について、利益率が30%となる新たな単価を求めよ。今回は、1円未満を切り上げること。そして結果を10件表示させ、利益率がおよそ30%付近であることを確認せよ。ただし、単価(unit_price)と原価(unit_cost)にはNULLが存在することに注意せよ。"""
%%sql
SELECT
    product_cd,
    unit_price,
    unit_cost,
    CEIL(unit_cost / 0.7) as new_price,
    (CEIL(unit_cost / 0.7) - unit_cost) / CEIL(unit_cost / 0.7) as new_ratio
FROM product
LIMIT 10

3-11. 最大・最小値の取得

引数での最大もしくは最小値を取得します。

引数で最大の値を求める、引数で最小の値を求める

GREATEST、LEAST
""" S-056: 顧客テーブル(customer)の年齢(age)をもとに10歳刻みで年代を算出し、顧客ID(customer_id)、生年月日(birth_day)とともに抽出せよ。ただし、60歳以上は全て60歳代とすること。年代を表すカテゴリ名は任意とする。先頭10件を表示させればよい。"""
%%sql
SELECT 
    customer_id,
    birth_day,
    LEAST(CAST(TRUNC(age/10)*10 AS INTEGER), 60) as era
FROM
    customer
GROUP BY customer_id, birth_day
ORDER BY era desc
LIMIT 10

3-12. 文字列の結合

文字列を結合したい時に使用します。(concatはpython-pandasの結合のイメージが強く違和感がなかなか抜けないです)

「文字列の連結」とは,ある文字列の端に別の文字列を追加することを意味します.文字列を連結するSQLは使用するDBMSによって異なります.以下に各DBMSごとに使用可能なSQLを示します.

文字列を連結する
""" S-057: 前問題の抽出結果と性別(gender)を組み合わせ、新たに性別×年代の組み合わせを表すカテゴリデータを作成せよ。組み合わせを表すカテゴリの値は任意とする。先頭10件を表示させればよい。"""
%%sql
SELECT 
    customer_id,
    birth_day,
    CONCAT(gender_cd, '-', LEAST(CAST(TRUNC(age/10)*10 AS INTEGER), 60)) as era
FROM
    customer
GROUP BY customer_id, birth_day
ORDER BY era
LIMIT 10

3-13. LOG計算

LOG計算です。(+1するのは実数が0の時に対数も0とするためです。)

SQLで自然対数を求めるにはLOG関数を,常用対数を求めるにはLOG10関数使用します.これら関数はSQL92/99で既定されている関数ではありませんが,多くのDBMSでサポートされています.以下に各DBMSの対応状況を示します.

対数(自然対数,常用対数)を求める
""" S-061: レシート明細テーブル(receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計し、合計した売上金額を常用対数化(底=10)して顧客ID、売上金額合計とともに表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。結果は10件表示させれば良い。"""
%%sql
WITH sales_amount as (
SELECT 
    customer_id,
    sum(amount) as sum_amount
FROM receipt
WHERE customer_id not like 'Z%'
GROUP BY customer_id
)
SELECT
    customer_id,
    sum_amount,
    LOG10(sum_amount+1)
FROM sales_amount
LIMIT 10

3-14. ランダム関数

ランダム取得関数です。

SQLで自然対数を求めるにはLOG関数を,常用対数を求めるにはLOG10関数使用します.これら関数はSQL92/99で既定されている関数ではありませんが,多くのDBMSでサポートされています.以下に各DBMSの対応状況を示します.

ランダムに抽出する SQL
""" S-075: 顧客テーブル(customer)からランダムに1%のデータを抽出し、先頭から10件データを抽出せよ。"""
%%sql
SELECT * 
FROM customer 
WHERE RANDOM() <= 0.01
LIMIT 10

以上です。お疲れさまでした!

4. 所感

Python編と比べて、やたら難しく感じた。おそらく、あまり処理関数を知らないことが起因していると思われます。

やはり、日々使いこなしている言語をベースに学習したほうが効率が良さそう。

なお、Python編のTip集はこちらからアクセスできます↓

ご興味有りましたら、ご覧いただけると幸いです。

datascience

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

ABOUT US

Baran-gizagiza
経歴:浪人→理系大学院卒業→大手製造業に就職(技術職)→アメリカ赴任中 仕事は、研究・設計など上流工程の仕事に携わっています。企業勤務を継続しながら、新しいことにチャレンジしたいと思い、ブログを下記はじめました。 このブログでは、趣味である 筋トレ(健康、ダイエット) AIとデータ(高校数学、プログラミング) 読書(主に自己啓発系) を中心に、人生経験やおすすめ情報の発信しています。