按字段类型分组取前几的数据详情

846 阅读2分钟

业务需求

按照article分组取每组price前5的详情(比如其他场景:统计年纪的每个班的前5名),表格(shop)如下

具体操作

select * from shop a WHERE (
select count(1) from shop b WHERE a.article = b.article and b.price > a.price
) < 5
ORDER BY a.article

个人看法:

原理:统计出同一个类型比我price高的数量,我price最高,比我高的数量为0,我第二,比我高的数量为1......我第五,比我高的数量为4,只要筛选出小于5的记录就是要统计的结果,方法很巧妙,但是也有几个问题:

  1. 性能问题,表特别大时,对每个price都要与其它price做一次对比,查询特别慢。
  2. 当存在price重复的时候,可能你第五,但是比你高的数据有n条,那么该条sql就存在问题。

二、使用GROUP_CONCAT的特性查询:

select * from shop WHERE (article,price) in (
SELECT article,SUBSTRING_INDEX(SUBSTRING_INDEX(price, ',', seq),',',-1) price
FROM
(
SELECT @rownum:=@rownum+1 AS seq FROM (SELECT @rownum:=0) r, (select * from shop limit 5) as a
) as d
CROSS JOIN 
(
SELECT article,substring_index(GROUP_CONCAT(DISTINCT price ORDER BY price DESC),",",5) as price  from shop GROUP BY article
) as b
)
ORDER BY article,price

个人看法:这条sql有点复杂,拆分来看

先看

SELECT @rownum:=@rownum+1 AS seq FROM (SELECT @rownum:=0) r, (select * from shop limit 5) as a

这条语句实际上只生成了一个临时表,算出来的是每条记录的行数 因为我只需要查询前5的数据,所以只生成了5位数,也许很多人不明白有什么用,稍后会讲到。

SELECT article,substring_index(GROUP_CONCAT(DISTINCT price ORDER BY price DESC),",",5) as price  from shop GROUP BY article

这条语句主要用了GROUP_CONCAT函数,该函数能把groupby后其它指定的值组装在一个字段里,默认以逗号分隔,我组装了去重降序的price,执行的结果 已经查询出article分组后的price去重的前5的数据,要是能把上述结果中price的值转换为列,然后在执行in操作不就能查出前5的详情了嘛。但是mysql并没有GROUP_CONCAT逆转换的函数,于是利用了substring_index的特性切割字符串,但是每条price切割只能得到一个值,要是能有5个值对应1,2,3,4,5的切割位置就好了,于是利用了前面的临时表与当前查询结果做了CROSS JOIN操作,得到了 到了这里我就释然了,在利用

SUBSTRING_INDEX(SUBSTRING_INDEX(price, ',', seq),',',-1) price

这条规则,5个seq值分别切割每条记录,最终得到 最外层用了一个in操作,sql执行结果 这也是我能想到的目前最快的方法了,查询数据量20W,耗时1.7s。 如有错误或其它方案,欢迎留言。