【问题标题】:Finding average ratings of a product that customers have not bought查找客户尚未购买的产品的平均评分
【发布时间】:2015-03-08 17:12:18
【问题描述】:

我正在尝试为每位客户查找他们尚未购买的产品,以及其中哪些产品的评分最高。

例如,在下表中,John 购买了商品 1 和 2,但没有购买商品 3、4 或 5。商品 3 和 5 没有评分,因此不会包含在内,但商品 4 应该被包括在内,因为它是 John 没有购买的评分最高的商品。

这是我的表结构和一些示例数据:

客户

id  | customer
----|---------
1   | john
2   | jenkins
3   | jane
4   | janet

产品

id  | description
----|---------
1   | deoderant
2   | soap
3   | shampoo
4   | razor
5   | sponge

订单

customer_id  | product_id
-------------|---------
1            | 1
1            | 2
2            | 3
2            | 4
3            | 5

评分

customer_id  | product_id | rate
-------------|------------|-------
1            | 1          | 3
2            | 2          | 2
2            | 4          | 3
4            | 2          | 4

【问题讨论】:

  • 您想为所有客户提供所有此类产品的列表,还是只为指定客户提供所有此类产品的列表?
  • 适用于客户尚未购买但已评分的所有产品。
  • 您想为所有客户提供所有此类产品的列表,还是只为指定客户提供所有此类产品的列表?
  • 是的,所有客户和所有此类产品。
  • 是否需要评分>2,还是他无论如何都没有购买的物品的最高评分?

标签: mysql sql aggregate-functions


【解决方案1】:

我首先编写了一些子查询,然后将它们拼凑在一起。我个人的建议是,在寻求整个解决方案之前,始终将问题分解为更小的部分。

例如,我需要知道的一件事是每个客户尚未购买的所有产品。我通过交叉连接客户和产品表(以获取所有配对)并删除订单表中已经存在的配对来做到这一点,如下所示:

-- Get all customer/product pairings where customer_product
-- does not exist in orders table
SELECT c.id, p.id
FROM customer c
CROSS JOIN product p
WHERE (c.id, p.id) NOT IN (SELECT * FROM orders)
ORDER BY c.id;

我还编写了一个子查询来获取每个产品的平均评分。如果产品没有评分,此查询将返回 null:

SELECT p.id, AVG(r.rate) AS averageRating
FROM product p
LEFT JOIN rate r ON r.product_id = p.id
GROUP BY p.id;

现在,我可以将这两个作为子查询包含在内,并选择客户 ID、产品 ID 以及他们尚未购买的每种产品的评分:

SELECT t1.customerID, t1.productID, t2.averageRating
FROM(
  SELECT c.id AS customerID, p.id AS productID
  FROM customer c
  CROSS JOIN product p
  WHERE (c.id, p.id) NOT IN (SELECT * FROM orders)
  ORDER BY c.id) t1
JOIN(
  SELECT p.id AS productID, AVG(r.rate) AS averageRating
  FROM product p
  LEFT JOIN rate r ON r.product_id = p.id
  GROUP BY p.id) t2 ON t2.productID = t1.productID;

那是最难的部分。剩下要做的唯一事情是进行一些聚合,以从每个客户未购买的商品中获得最大评分,然后在最大评分与平均评分匹配的条件下将该聚合查询与上面的查询连接起来。所以,这是我汇总的可怕查询:

SELECT t1.customerID, t1.productID, t1.averageRating
FROM(
  SELECT t1.customerID, t1.productID, t2.averageRating
  FROM(
    SELECT c.id AS customerID, p.id AS productID
    FROM customer c
    CROSS JOIN product p
    WHERE (c.id, p.id) NOT IN (SELECT * FROM orders)
    ORDER BY c.id) t1
  JOIN(
    SELECT p.id AS productID, AVG(r.rate) AS averageRating
    FROM product p
    LEFT JOIN rate r ON r.product_id = p.id
    GROUP BY p.id) t2 ON t2.productID = t1.productID) t1
JOIN(
  SELECT t1.customerID, MAX(t2.averageRating) AS maxRating
  FROM(
    SELECT c.id AS customerID, p.id AS productID
    FROM customer c
    CROSS JOIN product p
    WHERE (c.id, p.id) NOT IN (SELECT * FROM orders)
    ORDER BY c.id) t1
  JOIN(
    SELECT p.id AS productID, AVG(r.rate) AS averageRating
    FROM product p
    LEFT JOIN rate r ON r.product_id = p.id
    GROUP BY p.id) t2 ON t2.productID = t1.productID
  GROUP BY t1.customerID) t2 ON t2.customerID = t1.customerID AND t2.maxRating = t1.averageRating
ORDER BY t1.customerID;

下面是 MySQL 工作台结果的快照:

需要注意的重要一点是,我没有消除联系。因此,例如,客户 2 没有购买产品 1 或 2,并且他们的评分相同,因此返回两行。

我在 MySQL 中进行了测试,因为 SQL Fiddle 不工作,但我让它工作了,所以这里有一个 Fiddle 示例,如果你愿意的话。

【讨论】:

  • 尝试对此进行测试,但我不断收到错误消息,指出“操作数应包含 2 列”。
  • @Dino 在哪一行?您的真实订单表是否有超过两列?这将搞砸我在其中编写 select * from orders 的子查询。您可能需要更改它以从订单中选择客户、产品。
  • 啊订单上也有价格,让我查一下。我确实也更改了它,包括 customer_id 和 product_id 并且它有效,因为我对价格不感兴趣。
  • 我没有仔细检查结果。早上会做,因为我现在很累。
  • 我得到相同的结果,但是查询非常非常大。我做了类似的事情,我搜索了没有订购任何东西的客户,然后我分别查询了产品的平均价格,但不知道如何将它们加入到一个查询中。
【解决方案2】:

如果您想为一位客户执行此操作,只需使用 order bylimit

select c.*, r.*
from customers c cross join
     (select r.product_id, avg(rating) avgr
      from rating r
      group by r.product_id
     ) r left join
     orders o
     on o.customer_id = c.customer_id and
        o.product_id = r.product_id 
where c.customer_id = @customerid and o.product_id is null
order by r.avgr desc
limit 1;

如果您想同时为所有客户使用它,那就有点复杂了。一种方法是使用substring_index()/group_concat() 技巧:

select c.*,
       substring_index(group_concat(r.product_id order by avgr desc), ',', 1) as product_id
from customers c cross join
     (select r.product_id, avg(rating) avgr
      from rating r
      group by r.product_id
     ) r left join
     orders o
     on o.customer_id = c.customer_id and
        o.product_id = r.product_id 
where c.customer_id = @customerid and o.product_id is null
group by c.customer_id;

【讨论】:

  • 我认为您在 from 子句中命名 orders 表之前已经省略了 LEFT JOIN。而且您的第二个查询可能不应该在where 子句中对c.customer_id 进行过滤?
  • @eggyal 是对的,您缺少左连接关键字。但是,我使用左连接在工作台中对此进行了测试,结果很好。
  • 当我在订单前进行左连接时,我没有得到任何结果:(
  • @Dino 。 . .是否可以设置 SQL Fiddle?
  • @GordonLinoff 这里是link 我认为我做得对,但不确定。 (编辑 - 我刚刚测试过,它似乎工作)
猜你喜欢
  • 1970-01-01
  • 2013-09-05
  • 1970-01-01
  • 2020-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多