【问题标题】:SQL ecommerce database - getting count of products purchased by only one userSQL 电子商务数据库 - 获取仅由一位用户购买的产品数量
【发布时间】:2017-03-31 21:46:04
【问题描述】:

在我的 Rails 应用程序中,我在 Postgres 9.6 数据库中有一个典型的电子商务模式。这是它的简化版本:

users table
  :name

products table
  :name

shopping_carts table
 :user_id

line_items table
  :price
  :qty
  :product_id
  :shopping_cart_id

我有一个有效的查询来返回每个用户购买的不同产品的数量:

SELECT COUNT(distinct p.*), u.name FROM products p
INNER JOIN line_items l ON p.id = l.product_id
INNER JOIN shopping_carts sc ON l.shopping_cart_id = sc.id
INNER JOIN users u ON sc.user_id = u.id
GROUP BY u.name

但我还想要为每个用户计算只有该特定用户购买的产品。 Ruby 中的一种可能的方法(使用 ActiveRecord 设置所有内容后)可能类似于:

def unique_prod(user)
  user.products.select { |p| p.users.length == 1 }.count
end

但是如何在 SQL 中做到这一点?我想我需要使用两个计数来做 - 一个用于给定产品的 shopping_carts 中不同的user_ids 的数量(我们称之为计数user_count),然后是user_count = 1 的产品计数。我无法以工作方式合并多个 COUNTGROUP BY 语句。有什么建议吗?

【问题讨论】:

    标签: sql postgresql join aggregate


    【解决方案1】:

    在一个查询中完成所有操作:

    SELECT scl.user_id, u.name, ct_dist_prod, ct_dist_prod_exclusive
    FROM  (
       SELECT sc.user_id
            , count(DISTINCT l.product_id) AS ct_dist_prod
            , count(DISTINCT l.product_id)
                    FILTER (WHERE NOT EXISTS (
                               SELECT 1
                               FROM   shopping_carts sc1
                               JOIN   line_items     l1 ON l1.shopping_cart_id = sc1.id
                               WHERE  l1.product_id = l.product_id 
                               AND    sc1.user_id  <> sc.user_id)) AS ct_dist_prod_exclusive
       FROM   shopping_carts sc
       JOIN   line_items     l ON l.shopping_cart_id = sc.id
       GROUP  BY 1
       ) scl
    JOIN   users u ON u.id = scl.user_id;
    

    我将user_id 添加到结果中,因为我不能假设name 被定义为唯一(这会使您的原始查询稍微不正确)。

    聚合 FILTER 子句需要 Postgres 9.4 或更高版本:

    如何?

    假设参照完整性由 FK 约束强制执行,您不需要为该查询加入表 products

    一开始,users 表也不行。基本查询归结为:

    SELECT sc.user_id, count(DISTINCT l.product_id)
    FROM   shopping_carts sc
    JOIN   line_items     l ON l.shopping_cart_id = sc.id
    GROUP  BY 1;
    

    将第二个计数添加到这个更便宜的查询中,其中包含产品的所有行都被排除在外,因为存在具有相同产品和不同用户的另一行(即也被不同的用户购买)。

    然后加入users以添加name。更便宜。


    只计算独占计数更简单。示例:

    SELECT sc.user_id, count(DISTINCT l.product_id) AS ct_dist_prod_exclusive
    FROM   shopping_carts  sc
    JOIN   line_items      l ON l.shopping_cart_id = sc.id
    LEFT   JOIN (
                 shopping_carts sc1
           JOIN  line_items     l1 ON l1.shopping_cart_id = sc1.id
           ) ON l1.product_id = l.product_id 
        AND sc1.user_id <> sc.user_id
    WHERE  l1.product_id IS NULL
    GROUP  BY 1;
    

    注意必要的括号。

    相关:

    或者(回应您的评论):

    SELECT user_id, count(*) AS ct_dist_prod_exclusive
    FROM  (
       SELECT max(user_id) AS user_id, l1.product_id
       FROM   line_items l1
       INNER  JOIN shopping_carts sc1 ON l.shopping_cart_id = sc1.id
       GROUP  BY l1.product_id
       HAVING COUNT(DISTINCT sc1.user_id) = 1  -- DISTINCT!
       ) p1
    GROUP  BY user_id;
    

    HAVING COUNT(DISTINCT sc1.user_id) = 1 因为

    只有一位用户购买的产品

    允许同一用户多次购买产品。

    【讨论】:

    • 谢谢。在您的回答中,您似乎依赖 line_items 表中的 user_id 外键,但我的架构中没有。
    • @TDB:对,我相应地调整了查询​​。
    • 我创建了这个查询(基于您的原始答案),这给了我不同的结果。想法? (仅供参考,我的 sql 技能相当初级) SELECT sc.user_id, count(DISTINCT l.product_id) FROM shopping_carts sc INNER JOIN line_items l on l.shopping_cart_id = sc.id INNER JOIN ( SELECT l1.product_id FROM line_items l1 INNER JOIN shopping_carts sc1 ON l1.shopping_cart_id = sc1.id GROUP BY l1.product_id HAVING COUNT(sc1.user_id) = 1 ) one_user ON one_user.product_id = l.product_id GROUP BY sc.user_id
    • @TDB:好方法。但它必须是HAVING COUNT(DISTINCT sc1.user_id),你可以简化一些。我添加了另一个查询。
    猜你喜欢
    • 2021-03-17
    • 2021-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 2019-01-08
    • 1970-01-01
    • 2020-08-16
    相关资源
    最近更新 更多