uni farm

BrainPad SQLForBeginners2019をbigqueryでやってみた

BrainPad SQLForBeginners2019をbigqueryでやってみた

brain padの新卒研修で行われているらしいデータ集計のための教材GitHub - BrainPad/SQLForBeginners2019をgoogle bigqueryでやってみた

一部temporary table作るのを端折っていたりする

selectから始まるのでsql全く知らない人でも始められそう。最後の方までやると、サブクエリ、window関数も出てくるので基本的な抽出方法を一通り学ぶことができた

テーブル作成

SQLForBeginners2019/data at master · BrainPad/SQLForBeginners2019 · GitHub から各種.csvをbqのコンソールから適当なプロジェクトのデータセットにてテーブルを作成する

参考 データをローカル データソースから読み込む  |  BigQuery  |  Google Cloud

類題解答

データの説明や問題はこちらのスライドにのっているデータハンドリングのためのSQL / SQL for data handling - Speaker Deck

2-1-2

/*
2-1-2. SELECT① 特定フィールドの抽出
receipt_item からid, user_id, receipt_id, priceのデータを2件取得する
*/

select
  id
  ,user_id
  ,receipt_id
  ,price
from `test.dataset.receipt_item`
limit 2
;

2-1-3

/*
2-1-3. SELECT② COUNT, DISTINCT, AS
receipt_itemテーブルのreceipt_idフィールドに対して
「重複を許した場合の件数」
「重複を除いた場合の件数」をそれぞれ取得し、列名を別名に変更する 
*/

select
  count(receipt_id) as receipt
  ,count(distinct receipt_id) as dis_receipt
from `test.dataset.receipt_item`
limit 2
;

2-1-4

/*
2-1-4. SELECT③ ORDER BY, SELECT *
receiptテーブルの全てのフィールドに5件分、データ取得する
ただし、user_idの昇順、read_atの降順に並び替える
*/

select
  *
from `test.dataset.receipt`
order by
  user_id asc, read_at desc
;

2-2-1

/*
2-2-1. WHERE① 比較条件
userテーブルで、誕生日が2006年1月1日以降の人のbirthday, genderの
2つのフィールドについて5件分データ取得し、誕生日の昇順に並び替えて表示する。
*/

select
  birthday
  ,gender
from `test.dataset.user`
where
  birthday >= '2006-01-01'
order by
  birthday asc
limit 5
;

2-2-2

/*
2-2-2. WHERE② 論理条件
userテーブルで、「性別が2(女性)」かつ「state_codeが'13'(東京)または'27'(大阪)」
である人の総数を取得する
*/

select
  count(*)
from `test.dataset.user`
where
  gender = 2
and
  (
    state_code = 13
    or
    state_code = 27
  )
;

2-2-3

/*
2-2-3. WHERE③ パターンマッチングLIKE
receiptテーブルで、shop_nameが文字列「B」で終わるデータについて、
全てのフィールドを5件分取得する
*/

select
  *
from `test.dataset.receipt`
where
  shop_name like '%B'
limit 5
;

2-2-4

/*
2-2-4. WHERE④ 範囲条件BETWEEN
receiptテーブルで、paid_atが2010年1月1日から2010年12月31日までであるデータについて、
id, user_id, paid_atを5件分取得する。ただし、paid_atの昇順に並び替える。
*/

select
  id
  ,user_id
  ,paid_at
from `test.dataset.receipt`
where
  paid_at between '2010-01-01' and '2010-12-31'
order by
  paid_at asc
limit 5
;

2-2-5

/*
2-2-5. WHERE⑤ IN条件
receipt_itemテーブルで、nameが文字列「まぐろ」「りんご」であるデータについて、
id, user_id, receipt_id, nameを10件取得する。
*/

select
  id
  ,user_id
  ,receipt_id
  ,name
from `test.dataset.receipt_item`
where
  name in ('まぐろ', 'りんご')
order by
  id asc
limit 5
;

2-3-1 GROUP BY

/*
2-3-1. GROUP BY① 集計関数
receipt_itemテーブルで、receipt_id毎にグループ化したレコードの金額priceの
最小値を求め、5件分表示する。
*/

select
  receipt_id
  ,min(price) as min_price
from `test.dataset.receipt_item`
group by
  receipt_id
order by
  receipt_id asc
limit 5
;

2-3-2

2-3-2. GROUP BY② HAVING 集計結果に条件指定 receipt_itemテーブルで、receipt_id毎にグループ化し、レコードの金額priceに対して最大値を求め、最大値が1000以上のものを5件取得する。 ただし、求めた最大値の昇順に並び替えて表示する。

並び替えの条件がパワポと異なる(githubのanswerに従った)

/*
2-3-2. GROUP BY② HAVING 集計結果に条件指定
receipt_itemテーブルで、receipt_id毎にグループ化し、レコードの金額priceに対して最大値を求め、最大値が1000以上のものを5件取得する。
ただし、求めた最大値の昇順に並び替えて表示する。
*/

select
  receipt_id
  ,max(price) as max_price
from `test.dataset.receipt_item`
group by
  receipt_id
having
  max_price >= 1000
order by
  max_price asc
limit 5
;

3-1-1

/*
3-1-1. 条件式① CASE
userテーブルでstate_codeが’27’(大阪)であるユーザーの総数を求める。
(state_codeが’27’であるとき1、他は0のフラグを作成して合計する) 
*/

select
  sum(
    case
      when state_code = 27 then 1
      else 0
    end
  ) as sum_osaka_user
from `test.dataset.user`
;

3-1-2

select
  NULL as `null` -- NULLの入ったカラムnullができる
  ,coalesce(NULL, 0) as co_null  -- NULLだったら0が入る
;

3-2-2

/*
3-1-2. 条件式② COALESCE
NULLをもつフィールド`null`と、NULLを整数0に置換したフィールド`co_null`を1件取得する。(テーブルは不要なので、FROM句も不要)
*/

select
  user_id
  ,create_date
  ,cast(create_date as date) as cast_create_user
from `test.dataset.user`
order by user_id asc
limit 5
;

3-3-1

/*
3-2-2. 型変換 CAST
userテーブルで、create_dateをtimestamp型→date型に変換する。
・抽出フィールド:id, create_date, 型変換後のcreate_date
・idの昇順で5件分表示
*/

select
  user_id
  ,cast(last_login as date) as last_login
  ,substr(cast(last_login as string), 6, 2) as last_login_month
from `test.dataset.user`
order by user_id asc
limit 5
;

3-3-2

/*
3-3-2. 文字列関数② LENGTH, REPLACE
receipt_itemテーブルでnameの文字数を取得する(length_nameとする)。また、nameの ’スナック’ を ’ポテトチップス’ に置換する(replace_nameとする)。
・抽出フィールド:id, name, length_name, replace_name
・idの昇順で5件分表示
*/

select
  id
  ,name
  ,length(name) as length_name
  ,replace(name, 'スナック', 'ポテチ') as replace_name
from `test.dataset.receipt_item`
order by id asc
limit 5
;

3-4-1

/*
3-4-1. 日付関数 CURRENT_DATE, DATEDIFF
userテーブルで、birthdayの日付から現在の日付までの日数を計算する(date_diffとする)。
・抽出フィールド:id, birthday, current_date, date_diff
・idの昇順で5件分表示
*/

select
  user_id
  ,birthday
  ,current_date() as current_date
  ,date_diff(
    current_date(), birthday, day
  ) as date_diff
from `test.dataset.user`
order by user_id asc
limit 5
;

3-4-2

/*
3-4-2.年齢
userテーブルのbirthdayから、最終ログインの日付におけるユーザーの年齢を求める。
・抽出フィールド:id, last_login, birthday, age_at_last_login
・idの昇順で5件分表示
*/

select
  user_id
  ,cast(last_login as date) as last_login
  ,birthday
  ,date_diff(
    cast(last_login as date), birthday, year
  ) as age_at_last_login
from `test.dataset.user`
order by user_id asc
limit 5
;

4-1-2

/*
4-1-2.サブクエリ パターン①
receipt_itemテーブルで、user_id, priceとpriceの中央値を並べて表示する。
ただし、user_id(integer型に変換)の昇順で5件表示とし、中央値はMEDIAN()を使用せよ。
*/

select
  user_id
  ,price
  ,(select stddev_pop(price) from  `test.dataset.receipt_item`) as stddev_price
from `test.dataset.receipt_item`
order by user_id asc, price asc
limit 5
;

4-1-3

/*
4-1-3. サブクエリ パターン②
receipt_itemテーブルとuserテーブルを用いて、name(商品名)が’カップラーメンA‘を
買ったことのある人を性別にカウントする。
*/

select
  gender
  ,count(user_id) as count_user_id
from `test.dataset.user`
where
  user_id in (select distinct
                user_id
              from `test.dataset.receipt_item`
              where
                name = 'カップラーメンA'
             )
group by gender
;

4-1-4

/*
4-1-4. サブクエリ パターン③
receipt_itemテーブルとuserテーブルを用いて、1ユーザーあたりの累計合計金額の平均値を求める。
ヒント
① サブクエリでuser_idでグループ化し、合計金額を求める 
② サブクエリの結果をもとに平均値を求める
*/

select
  avg(sum_price) as avg_sum_price_user
from
  (select
     sum(price) as sum_price
   from `test.dataset.receipt_item`
   group by user_id
  )
;

4-1-5

/*
4-1-5. WITH句 サブクエリの代用
receiptテーブルを用いて、「1ユーザーあたりのレシート登録数」の平均値を求める。
ヒント
① 先ずはWITH句を使って、ユーザーIDごとのレシート登録数を集計する。
(ユーザーID,レシート登録数の2列からなる表を作成する)
② ①で作成した表から、レシート登録数の平均を計算する。
*/

with rc as (
  select
    user_id
    ,count(id) as receipt_count
   from `test.dataset.receipt`
   group by user_id
)
select
  avg(receipt_count) as avg_receipt_count
from
  rc
;

4-2-2

/*
4-2-2. INNER JOIN
receipt_itemテーブル(左表)とuserテーブル(右表)を、userテーブルのidとreceipt_itemテーブルのuser_idを用いて内部結合する。
・receipt_itemテーブルのidをinteger型に変換し、昇順で5件分表示
・抽出フィールド:receipt_item テーブルid, name / userテーブル id, gender
*/

select
  ri.id as receipt_id
  ,u.user_id as user_id
  ,ri.name
  ,u.gender
from `test.dataset.receipt_item` as ri
inner join
  `test.dataset.user` as u
on ri.user_id = u.user_id
order by ri.receipt_id asc
limit 5
;

4-2-3

/*
4-2-3. LEFT JOIN
receipt_itemテーブル(左表)とuserテーブル(右表)を、userテーブルのidとreceipt_itemテーブルのuser_idを用いて外部結合する。※receipt_itemテーブルidの昇順で5件分表示
・抽出フィールド:receipt_item テーブルid, name / userテーブル id, gender
・receipt_itemテーブルのid(integer型に変換) >= 135を対象とし、昇順で5件分表示する。
*/

select
  ri.id as receipt_id
  ,u.user_id as user_id
  ,ri.name
  ,u.gender
from `test.dataset.receipt_item` as ri
left join
  `test.dataset.user` as u
on ri.user_id = u.user_id
where
  ri.receipt_id >= 135
order by ri.receipt_id asc
limit 5
;

4-3-1

bigqueryではtemporary tableを作るのにカラム定義する必要がある

/*
4-3-1. 一時テーブルの作成
userテーブルからcreate_dateが2013年2月のものを抽出し、一時テーブルuser_201302として保存せよ。
また、確認のため作成した一時テーブルuser_201302からデータを5件取得せよ。
*/

create temp table user_201302
(
  id int64
  ,gender int64
  ,birthday date
  ,state_code int64
  ,last_login timestamp
  ,create_date timestamp
);
-- insert
insert user_201302
with a as (
  select
    *
  from `test.dataset.user`
  where
    create_date between '2013-02-01' and '2013-02-28'
)

select
  *
from a
;

-- check data
select
  *
from user_201302
order by id asc
limit 5
;

5-1-2

/*
5-1-2. ウィンドウ関数 集計関数群
receiptテーブルで、user_idで区切ったパーティション毎にCOUNTで集計を行う。
・user_idが3以下のデータを対象とする。
*/

select
  user_id
  ,count(user_id) over(partition by user_id)  as count_user_id
from `test.dataset.receipt`
where
  user_id <= 3
;

5-1-3

/*
5-1-3. ウィンドウ関数② ランク付け関数群
receipt_itemテーブルで、receipt_id毎にpriceの最大値を求める。
・receipt_idが10以下のデータを対象
*/

select
  receipt_id
  ,price as max_price
  ,name
from 
  (
    select
      *
      -- price高い順のranking取得
      ,row_number() over(partition by receipt_id order by price desc) as rank
    from `test.dataset.receipt_item`
  )
where
  receipt_id <= 10
  and
  -- 1番高いもののみに絞る
  rank = 1
order by receipt_id asc
;

5-1-4

/*
5-1-4. ウィンドウ関数③ ラグ・リード関数群
receipt_itemテーブルで、user_idで区切ったパーティション毎にidの昇順に並び替え、priceについて1行後の値をlead1_price、2行後の値をlead2_priceとして取得する。
・抽出フィールド:user_id, id, price, lead1_price, lead2_price
・user_idが3以下のデータを対象
*/

select
  user_id
  ,id
  ,price
  ,lead(price, 1) over(partition by user_id order by id asc) as lead1_price
  ,lead(price, 2) over(partition by user_id order by id asc) as lead2_price
from `test.dataset.receipt_item`
where
  user_id <= 3
order by user_id asc
;

5-2-1

/*
5-2-1. EXISTS
userテーブルのレコードで、idがreceiptテーブルのuser_idとして存在していないものをカウントする。
*/

select
  count(*) as count
from `test.dataset.user` as u
where not exists (
  select
    1
  from `test.dataset.receipt` as r
  where
    r.user_id = u.user_id 
)
;

5-3-2

/*
5-3-2. 横持ち→縦持ち
receiptテーブルのpaid_at, read_at, update_atを、
それぞれのフィールド名をxxx_at、値をdateとした縦持ちに変換する。
*/

-- create tempが面倒なのでwithにした
with tidy as (
  select
    id
    ,'paid_at' as xxx_at
    ,paid_at as date
  from `test.dataset.receipt`
  
  union all
  
  select
    id
    ,'read_at' as xxx_at
    ,read_at as date
  from `test.dataset.receipt`
  
  union all
  
  select
    id
     ,'update_at' as xxx_at
     ,update_at as date
  from `test.dataset.receipt`
)
select
  *
from tidy
order by id, date
limit 10
;

5-3-3

/*
5-3-3.縦持ち→横持ち
user, receiptテーブルを用いて、
都道府県(state_code)ごとの3〜5月の購入件数を表示する。
*/

with ms as (
  select
    substr(cast(r.paid_at as string), 6, 2) as paid_at_month
    ,u.state_code
  from `test.dataset.receipt` as r
  inner join
    `test.dataset.user` as u
  on r.user_id = u.user_id
)
select
   state_code
   ,count(
      case
        when paid_at_month = '03' then paid_at_month
      end
    ) as march
    ,count(
      case
        when paid_at_month = '04' then paid_at_month
      end
    ) as april
    ,count(
      case
        when paid_at_month = '05' then paid_at_month
      end
    ) as may
from ms
group by state_code
order by state_code
limit 10
;
2023, Built with Gatsby. This site uses Google Analytics.