具有多个表连接的每个组的前N个

时间:2011-03-16 00:23:40

标签: mysql sql group-by greatest-n-per-group

根据我的研究,这是一个非常普遍的问题,通常有一个相当简单的解决方案。我的任务是将获取所有结果的几个查询更改为每组获得前3个。起初这很顺利,我使用了几个建议和本网站的答案来实现这一目标(观看次数最多的产品)。然而,由于多次加入,我的最后一个“畅销产品”遇到了困难。

基本上,我需要按每个产品的最高销售额#按顺序获得所有产品,其中每个供应商的最大产品数为3 我已经加入多个表来创建原始查询,并且每次我尝试使用变量生成排名时,都会产生无效结果。以下内容应有助于更好地理解该问题(为简洁起见,我删除了不必要的字段):

产品表

productid | vendorid | approved | active | deleted

供应商表

vendorid | approved | active | deleted

订单表

orderid | `status` | deleted

订单商品表

orderitemid | orderid | productid | price

现在,我对获取所有结果的原始查询如下:

SELECT COUNT(oi.price) AS `NumSales`, 
       p.productid, 
       p.vendorid
FROM products p
INNER JOIN vendors v ON (p.vendorid = v.vendorid)
INNER JOIN orders_items oi ON (p.productid = oi.productid)
INNER JOIN orders o ON (oi.orderid = o.orderid)
WHERE (p.Approved = 1 AND p.Active = 1 AND p.Deleted = 0)
AND (v.Approved = 1 AND v.Active = 1 AND v.Deleted = 0)
AND o.`Status` = 'SETTLED'
AND o.Deleted = 0
GROUP BY oi.productid
ORDER BY COUNT(oi.price) DESC
LIMIT 100;

最后,(这就是我难倒的地方),我试图改变上面的陈述,这样我每个供应商只收到前3个产品(#sold)。我到目前为止添加了我所拥有的内容,但我很尴尬,这个问题已经是一面文字了。我已经尝试过变量,但一直收到无效的结果。任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:10)

即使你指定了LIMIT 100,这种类型的查询也需要建立一个完整的扫描和表格,然后检查每个记录并对行进行编号,最后过滤掉你要显示的100个。

select
    vendorid, productid, NumSales
from
(
    select
        vendorid, productid, NumSales,
        @r := IF(@g=vendorid,@r+1,1) RowNum,
        @g := vendorid
    from (select @g:=null) initvars
    CROSS JOIN 
    (
        SELECT COUNT(oi.price) AS NumSales, 
               p.productid, 
               p.vendorid
        FROM products p
        INNER JOIN vendors v ON (p.vendorid = v.vendorid)
        INNER JOIN orders_items oi ON (p.productid = oi.productid)
        INNER JOIN orders o ON (oi.orderid = o.orderid)
        WHERE (p.Approved = 1 AND p.Active = 1 AND p.Deleted = 0)
        AND (v.Approved = 1 AND v.Active = 1 AND v.Deleted = 0)
        AND o.`Status` = 'SETTLED'
        AND o.Deleted = 0
        GROUP BY p.vendorid, p.productid
        ORDER BY p.vendorid, NumSales DESC
    ) T
) U
WHERE RowNum <= 3
ORDER BY NumSales DESC
LIMIT 100;

这里的方法是

  1. 分组获取NumSales
  2. 使用变量对每个供应商/产品的销售额进行排序
  3. 过滤编号数据集以允许每个供应商最多3个
  4. 按NumSales DESC订购剩余部分,仅返回100

答案 1 :(得分:0)

我喜欢这个优雅的解决方案,但是当我在我的开发机器上运行一个改编但相似的查询时,我得到了一个非确定性的结果集。我相信这是由于MySql优化器处理在同一语句中分配和读取用户变量的方式。

来自the docs

  

作为一般规则,您不应该为用户变量赋值并在同一语句中读取值。您可能会得到您期望的结果,但这不能保证。涉及用户变量的表达式的评估顺序是未定义的,可能会根据给定语句中包含的元素进行更改;此外,MySQL服务器版本之间的订单不保证相同。

只是在这里添加此注释,以防其他人遇到这种奇怪的行为。

答案 2 :(得分:0)

@RichardTheKiwi给出的答案很有效,让我99%的路程!我正在使用MySQL,并且只获得每个组的第一行标记行号,而其余行保持为NULL。这导致查询仅返回每个组的最高命中而不是前三行。要解决此问题,我必须在@r子查询中初始化initvars。我改变了,

from (select @g:=null) initvars

from (select @g:=null, @r:=null) initvars

您也可以将@r初始化为0,它的工作原理相同。对于那些不太熟悉这种语法的人,附加部分是读取每个已排序的组,如果一行与前一行具有相同的vendorid,则使用@g变量进行跟踪,增加行号,该行号存储在变量@r中。当此过程使用新的vendorid到达下一个组时,IF语句将不再评估为true,@r变量(以及RowNum)将重置到1。