在我的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
的产品数量。我在以工作方式合并多个COUNT
和GROUP BY
语句时遇到了问题。有什么建议吗?
答案 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
因为
仅由一位用户购买的产品
允许同一用户多次购买该产品。