9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【SQL EXISTS句】利用した全てのECモールで1ヶ月当たりに使用される金額が5000円以上のユーザーを調べる

9
Last updated at Posted at 2022-12-05

まずは、テーブルを用意

それぞれのユーザーが利用したECモールで1ヶ月当たりに使用した金額を表すec_mallsテーブルを用意します

風土jp
・A
・R

3つのECモールがあるとして、それぞれ利用した場合の金額が格納されます

SELECT * FROM ec_malls;
 user_id | mall_name | amount_spent_per_month
---------+-----------+----------------------
       1 | 風土jp    |                30000
       1 | A         |                 8000
       1 | R         |                 1000
       2 | 風土jp    |                 5000
       2 | A         |                 5000
       3 | 風土jp    |                10000
       3 | R         |                 3000
       4 | 風土jp    |                25000

全てのECモールで5000円以上利用したユーザーを表現するには?

「全ての〜〜」をSQLで簡単に表現できれば良いのですが、
SQLには、全称量化子に対応する述語が存在しないみたいです。

なので、二重否定文へ変更することでSQLでは表現出来るようです

全てのECモールで5000円以上利用したユーザー

全てのECモールで5000未満の利用のユーザーが存在しない

実際のSQL

SELECT DISTINCT user_id
FROM ec_malls ecm1
WHERE NOT EXISTS (
    SELECT *
    FROM ec_malls ecm2
    WHERE ecm1.user_id = ecm2.user_id AND ecm2.amount_spent_per_month < 5000
);
 user_id
---------
       4
       2

今回欲しいのはuser_idだけで、DISTINCTで重複行を除きます

WHERE NOT EXISTS():1行ずつ評価していきますが、条件を満たさない行が見つかった時点で処理は止まります

HAVING句を使用した書き方でも書ける

SELECT DISTINCT user_id
FROM ec_malls
GROUP BY user_id
HAVING SUM(
    CASE WHEN amount_spent_per_month >= 5000
         THEN 1
         ELSE 0
    END
) = COUNT(user_id);

user_id 毎にグループ化したものに対して、それぞれ条件指定しています

COUNT(user_id)はGROUPごとのレコード数の合計を表しています。そのため、amount_spent_per_month >= 5000の条件を全て満たしていればSUM() = COUNT(user_id)は成立します

これは、EXISTSとは違って全ての行をなめる必要があります

そのため、パフォーマンスの観点で言うとEXISTSが勝るようです

今度は、風土jpで12000円以上、Rで5000円以下利用したユーザーを調べる

今度はモールを風土jp と Rに絞ったクエリを考えてみます

SELECT DISTINCT user_id
FROM ec_malls ecm1
WHERE mall_name in ('風土jp', 'R') AND NOT EXISTS (
    SELECT *
    FROM ec_malls ecm2
    WHERE ecm1.user_id = ecm2.user_id AND 1 =
        CASE WHEN mall_name = '風土jp' AND amount_spent_per_month < 12000 THEN 1
             WHEN mall_name = 'R' AND amount_spent_per_month > 5000 THEN 1
             ELSE 0
        END
);
 user_id
---------
       1
       4

WHERE mall_name in ('風土jp', 'R')でまず絞ります

そして、今回の条件である、「風土jpで12000円以上、Rで5000円以下利用した」は

風土jpで12000円以上、Rで5000円以下利用した

風土jpで12000円未満、Rで5000円より大きい額の利用を満たさない

で表すことが出来ます

ここでは、CASE式を利用して条件を満たす場合は1、満たさない場合は0として比較します

風土jpもしくはRが存在しない場合は除外する

GROUP BY user_id HAVING count(mall_name) = 2を追加します

ユーザごとに、絞った2つのモールが存在しない場合は除外しています

SELECT user_id
FROM ec_malls ecm1
WHERE mall_name in ('風土jp', 'R') AND NOT EXISTS (
    SELECT *
    FROM ec_malls ecm2
    WHERE ecm1.user_id = ecm2.user_id AND 1 =
        CASE WHEN mall_name = '風土jp' AND amount_spent_per_month < 12000 THEN 1
             WHEN mall_name = 'R' AND amount_spent_per_month > 5000 THEN 1
             ELSE 0
        END
)
GROUP BY user_id
HAVING count(mall_name) = 2;
 user_id
---------
       1

終わりに

帆立がたべたい
[pc]
https://www.hoodo.jp/pc/store/katsumaru/02300020

[スマホ]
https://www.hoodo.jp/sp/store/katsumaru/02300020

9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?