解答

CHAPTER 5

011

SQL文(クエリ)

      SELECT product_category
      , SUM(revenue) AS sum_revenue
      FROM impress_sweets.sales
      INNER JOIN impress_sweets.products
      USING (product_id)
      GROUP BY product_category
      ORDER BY 2 DESC
    

結果テーブル

結果テーブル

解説

[sales]テーブルの[product_id]と、[products]テーブルの[product_id]をキーとして結合することにより、[products]に存在する商品カテゴリ(product_category)で集計できるようになります。

[sales]テーブルの[product_id]に記録されている値がすべて、[products]テーブルの[product_id]にも存在しているため、上記のINNER JOINはLEFT OUTER JOINとしてもRIGHT OUTER JOINとしても同じ結果になります。

012

SQL文(クエリ)

      WITH total_qty AS
      (SELECT SUM(quantity) AS ttl_qty FROM impress_sweets.sales)
      
      SELECT product_id
      , SUM(quantity) AS sum_qty
      , SUM(quantity) / MAX(ttl_qty) AS sales_share
      FROM impress_sweets.sales
      CROSS JOIN total_qty
      GROUP BY product_id
      ORDER BY 3 DESC
      LIMIT 5
    

結果テーブル

結果テーブル

解説

まず、WITH句でテーブル全体の販売個数の合計(ttl_qty)を取得します。取得した仮想テーブルは[total_qty]と名付けており、1列1行、つまり1つの値で構成されています。そのうえで[sales]テーブルを[total_qty]を結合します。[sales]テーブルのすべてのレコードに[ttl_qty]の値を持ちたいので、結合のタイプにはCROSS JOINを使います。

結合してしまえば「同一レコードの別フィールド同士の計算」は簡単にできるので、[product_id]でグループ化を行い、SUM関数で[quantity]を合計した値を、販売金額の合計であるMAX(ttl_qty)で割って[sales_share]を求めます。

CROSS JOIN後、すべてのレコードに同じ値の[ttl_qty]が格納されているので、ここで利用するMAX関数は集計関数を利用して値を1つだけ取り出していることになり、MIN関数を使っても、AVG関数を使っても結果は同様になります。

別解として、以下のSQL文があります。こちらはWITH句で商品ID別の販売個数の合計と、テーブル全体の販売個数の合計という2つの仮想テーブルを作成し、メインのSQL文でシンプルにCROSS JOINと割り算のみをするという方法です。

      WITH qty_by_product AS
      (SELECT product_id, SUM(quantity) AS sum_qty
      FROM impress_sweets.sales
      GROUP BY product_id)
      , total_qty AS
      (SELECT SUM(quantity) AS ttl_qty FROM impress_sweets.sales)
      
      SELECT product_id, sum_qty
      , sum_qty / ttl_qty AS sales_share
      FROM qty_by_product
      CROSS JOIN total_qty
      ORDER BY 3 DESC
      LIMIT 5
    

013

SQL文(クエリ)

      SELECT
      CASE gender
      WHEN 1 THEN "男性"
      WHEN 2 THEN "女性"
      ELSE "不明" END AS customer_gender
      , product_category
      , SUM(revenue) AS sum_revenue
      FROM impress_sweets.sales
      LEFT JOIN impress_sweets.customers
       ON sales.user_id = customers.customer_id
      LEFT JOIN impress_sweets.products USING (product_id)
      GROUP BY gender, product_category
      ORDER BY 1, 3 DESC
    

結果テーブル

結果テーブル

解説

[sales]テーブルを左側として、顧客情報が格納された[customers]テーブルと商品情報が格納された[products]テーブルの2つを結合します。[customers]テーブルにある[gender]をCASE文で「男性」「女性」と読み替えています。

性別(gender)と商品カテゴリ(product_category)の2つのフィールドで集計するため、GROUP BY句にも[gender]と[product_catgory]を指定しています。[sales]テーブルと[customer]テーブルを結合する際、結合キーのフィールド名が異なるのでON句を利用しています。

014

SQL文(クエリ)

      WITH qty_jul_sep AS
      (SELECT product_id
      FROM impress_sweets.sales
      WHERE date_time BETWEEN "2023-07-01" AND "2023-09-30"
      GROUP BY product_id
      HAVING SUM(quantity) >= 10)
      , qty_oct_dec AS
      (SELECT product_id
      FROM impress_sweets.sales
      WHERE date_time BETWEEN "2023-10-01" AND "2023-12-31"
      GROUP BY product_id
      HAVING SUM(quantity) >= 10)
      
      SELECT product_id FROM qty_jul_sep
      EXCEPT DISTINCT
      SELECT product_id FROM qty_oct_dec
    

結果テーブル

結果テーブル

解説

2023年7~9月に販売個数(quantity)が「10」以上の商品と、2023年10~12月に販売個数(quantity)が「10」以上の商品を調べることがポイントです。日付の期間はBETWEEN句で指定します。

WITH句で条件に合致する2つの仮想テーブルを作成し、それらを集合演算のEXCEPT DISTINCTで差集合を作って商品リストを取得します。

仮に7月-9月に10個以上売れた[producrt_id]がすべて、10月~12月にも10個以上売れていれば、該当なしとなり結果テーブルは返ってこなくなります。逆にいうと、返ってきた[product_id]は10月~12月に10個以上売れなかった商品といえます。

テーブル演算を使ったほかの方法としては、「7月~9月に10個以上売れた[product_id]のリスト」と「10月~12月に10個未満しか売れなかった[product_id]のリスト」をWITH句で作成し、それらの積集合を求めることでも正解を得ることができます。

別解として以下のSQL文があります。こちらはWITH句では「10個以上売れた」という条件は付与しておらず、[pruduct_id]ごとに[quantity]を合計で集計しているだけです。そのうえでメインのクエリで7月から9月には10個以上販売され、10月から12月では10個未満にか販売されなかった[product_id]を取得しています。

      WITH qty_jul_sep AS
      (SELECT product_id, SUM(quantity) as jul_sep_qty
      FROM impress_sweets.sales
      WHERE date_time BETWEEN "2023-07-01" AND "2023-09-30"
      GROUP BY product_id)
      , qty_oct_dec AS
      (SELECT product_id, SUM(quantity) as oct_dec_qty
      FROM impress_sweets.sales
      WHERE date_time BETWEEN "2023-10-01" AND "2023-12-31"
      GROUP BY product_id
      )

      SELECT product_id FROM qty_jul_sep
      JOIN qty_oct_dec USING (product_id)
      WHERE jul_sep_qty >= 10 AND oct_dec_qty < 10
    

015

SQL文(クエリ)

      SELECT product_name
      , SUM(quantity) AS sum_quantity
      , SUM(revenue) AS sum_revenue
      , SUM(quantity) * MAX(cost) AS sum_cost
      , SUM(revenue) -
      (SUM(quantity) * MAX(cost)) AS profit
      FROM impress_sweets.sales
      INNER JOIN impress_sweets.products
      USING (product_id)
      GROUP BY product_name
      ORDER BY 5 DESC
      LIMIT 3
    

結果テーブル

結果テーブル

解説

[sales]テーブルと[products]テーブルを[product_id]をキーとして結合したうえで、[products]テーブルにある商品名(product_name)でグループ化しています。コストの合計を求めるために、[products]テーブルの[cost]を集計関数MAX(cost)で取得し、販売個数(quantity)の合計と掛け合わせるところは難しかったのではないでしょうか。

GROUP BY句でグループ化をすると、SELECT句に記述する指標はすべて集計関数で括る必要があります。[products]テーブルから取得した[cost]も例外ではありません。では、どのような集計関数で括ればよいかというと、(正解例ではMAX(cost)を使っていますが)MAXでも、MINでもAVGでもよいことになります。

なぜなら、例えば結合が完了し、かつ集計する前には[product_name]が「レアチーズケーキ」であるレコードは複数存在しますが、[cost]の値は、すべて400であるはずです。

どのレコードも同一の値なので、最大値(MAX)で集計しても、最小値(MIN)で集計しても、平均値(AVG)で集計しても必ず400となるからです。実際に正解SQL文のMAX(cost)をMINやAVGに変えて実験してみてください。集計関数には複数レコードにまたがる同一の値を、1つだけの値に集約する機能もあることを、今一度確認できるでしょう

サポートページに戻る