SQL电子商务数据库 - 只计算一个用户购买的产品数量

时间:2017-03-31 21:46:05

标签: sql postgresql join aggregate

在我的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中执行此操作?我想我需要使用两个计数 - 一个用于给定产品的不同user_id个数量的shopping_carts(让我们称之为计数user_count),然后user_count = 1的产品数量。我在以工作方式合并多个COUNTGROUP BY语句时遇到了问题。有什么建议吗?

1 个答案:

答案 0 :(得分: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因为

  

仅由一位用户购买的产品

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