Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/76.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/67.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/.htaccess/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 使用GROUP BY中的LIMIT来获得每组N个结果?_Sql_Mysql_Greatest N Per Group_Ranking - Fatal编程技术网

Sql 使用GROUP BY中的LIMIT来获得每组N个结果?

Sql 使用GROUP BY中的LIMIT来获得每组N个结果?,sql,mysql,greatest-n-per-group,ranking,Sql,Mysql,Greatest N Per Group,Ranking,以下查询: SELECT year, id, rate FROM h WHERE year BETWEEN 2000 AND 2009 AND id IN (SELECT rid FROM table2) GROUP BY id, year ORDER BY id, rate DESC 收益率: year id rate 2006 p01 8 2003 p01 7.4 2008 p01 6.8 2001 p01 5.9 2007 p01 5.3 2009

以下查询:

SELECT
year, id, rate
FROM h
WHERE year BETWEEN 2000 AND 2009
AND id IN (SELECT rid FROM table2)
GROUP BY id, year
ORDER BY id, rate DESC
收益率:

year    id  rate
2006    p01 8
2003    p01 7.4
2008    p01 6.8
2001    p01 5.9
2007    p01 5.3
2009    p01 4.4
2002    p01 3.9
2004    p01 3.5
2005    p01 2.1
2000    p01 0.8
2001    p02 12.5
2004    p02 12.4
2002    p02 12.2
2003    p02 10.3
2000    p02 8.7
2006    p02 4.6
2007    p02 3.3
我想要的是每个id的前5个结果:

2006    p01 8
2003    p01 7.4
2008    p01 6.8
2001    p01 5.9
2007    p01 5.3
2001    p02 12.5
2004    p02 12.4
2002    p02 12.2
2003    p02 10.3
2000    p02 8.7

有没有一种方法可以通过使用类似于限制的修饰符来实现这一点,该修饰符在GROUP BY中起作用?

不,您不能随意限制子查询。您可以在较新的MySQL中在有限的范围内进行限制,但不能针对每个组的5个结果

这是一个groupwise最大类型查询,这在SQL中并不简单。在某些情况下,有很多方法可以更有效地解决这个问题,但对于top-n,一般来说,您需要查看前面类似的问题

与此问题的大多数解决方案一样,如果有多行具有相同的速率值,它可能会返回五行以上的数据,因此您可能仍然需要大量的后处理来检查这一点。

下面的帖子:介绍了在没有子查询的情况下实现这一点的复杂方法

它改进了此处提供的其他解决方案,包括:

在一个查询中完成所有操作 能够正确使用索引 避免子查询,众所周知在MySQL中会产生错误的执行计划 然而,它并不漂亮。一个好的解决方案是在MySQL中启用窗口函数,也就是分析函数,但事实并非如此。
在上述帖子中使用的技巧利用了GROUP_CONCAT,有时被描述为MySQL的穷人窗口函数。

这需要一系列子查询对值进行排序、限制,然后在分组时执行求和

@Rnk:=0;
@N:=2;
select
  c.id,
  sum(c.val)
from (
select
  b.id,
  b.bal
from (
select   
  if(@last_id=id,@Rnk+1,1) as Rnk,
  a.id,
  a.val,
  @last_id=id,
from (   
select 
  id,
  val 
from list
order by id,val desc) as a) as b
where b.rnk < @N) as c
group by c.id;

对于那些像我一样有时间的人。我在下面列出了一个特定的小组使用限制和其他任何东西

DELIMITER $$
CREATE PROCEDURE count_limit200()
BEGIN
    DECLARE a INT Default 0;
    DECLARE stop_loop INT Default 0;
    DECLARE domain_val VARCHAR(250);
    DECLARE domain_list CURSOR FOR SELECT DISTINCT domain FROM db.one;

    OPEN domain_list;

    SELECT COUNT(DISTINCT(domain)) INTO stop_loop 
    FROM db.one;
    -- BEGIN LOOP
    loop_thru_domains: LOOP
        FETCH domain_list INTO domain_val;
        SET a=a+1;

        INSERT INTO db.two(book,artist,title,title_count,last_updated) 
        SELECT * FROM 
        (
            SELECT book,artist,title,COUNT(ObjectKey) AS titleCount, NOW() 
            FROM db.one 
            WHERE book = domain_val
            GROUP BY artist,title
            ORDER BY book,titleCount DESC
            LIMIT 200
        ) a ON DUPLICATE KEY UPDATE title_count = titleCount, last_updated = NOW();

        IF a = stop_loop THEN
            LEAVE loop_thru_domain;
        END IF;
    END LOOP loop_thru_domain;
END $$
它在域列表中循环,然后每个域只插入200个限制

SELECT h.year, h.id, h.rate 
FROM (SELECT h.year, h.id, h.rate, IF(@lastid = (@lastid:=h.id), @index:=@index+1, @index:=0) indx 
      FROM (SELECT h.year, h.id, h.rate 
            FROM h
            WHERE h.year BETWEEN 2000 AND 2009 AND id IN (SELECT rid FROM table2)
            GROUP BY id, h.year
            ORDER BY id, rate DESC
            ) h, (SELECT @lastid:='', @index:=0) AS a
    ) h 
WHERE h.indx <= 5;
您可以使用聚合函数将所有年份合并到一列中,按id分组并按比率排序:

结果:

-----------------------------------------------------------
|  ID | GROUPED_YEAR                                      |
-----------------------------------------------------------
| p01 | 2006,2003,2008,2001,2007,2009,2002,2004,2005,2000 |
| p02 | 2001,2004,2002,2003,2000,2006,2007                |
-----------------------------------------------------------
然后你可以使用,返回第一个参数在第二个参数中的位置,例如

SELECT FIND_IN_SET('2006', '2006,2003,2008,2001,2007,2009,2002,2004,2005,2000');
1

SELECT FIND_IN_SET('2009', '2006,2003,2008,2001,2007,2009,2002,2004,2005,2000');
6
使用GROUP_CONCAT和FIND_IN_SET的组合,并根据FIND_IN_SET返回的位置进行过滤,然后可以使用此查询,该查询只返回每个id的前5年:

SELECT
  yourtable.*
FROM
  yourtable INNER JOIN (
    SELECT
      id,
      GROUP_CONCAT(year ORDER BY rate DESC) grouped_year
    FROM
      yourtable
    GROUP BY id) group_max
  ON yourtable.id = group_max.id
     AND FIND_IN_SET(year, grouped_year) BETWEEN 1 AND 5
ORDER BY
  yourtable.id, yourtable.year DESC;
请看小提琴

请注意,如果不止一行可以具有相同的速率,则应考虑在速率列上使用GROPPE-CONTATABORATION率顺序,而不是年列。

GROUP_CONCAT返回的字符串的最大长度是有限的,因此如果您需要为每个组选择一些记录,则此方法非常有效

SUBSTRING_INDEX(group_concat(col_name order by desired_col_order_name), ',', N) 
工作完美。没有复杂的问题

例如:为每组获得前1名

SELECT 
    *
FROM
    yourtable
WHERE
    id IN (SELECT 
            SUBSTRING_INDEX(GROUP_CONCAT(id
                            ORDER BY rate DESC),
                        ',',
                        1) id
        FROM
            yourtable
        GROUP BY year)
ORDER BY rate DESC;
试试这个:

SET @num := 0, @type := '';
SELECT `year`, `id`, `rate`,
    @num := if(@type = `id`, @num + 1, 1) AS `row_number`,
    @type := `id` AS `dummy`
FROM (
    SELECT *
    FROM `h`
    WHERE (
        `year` BETWEEN '2000' AND '2009'
        AND `id` IN (SELECT `rid` FROM `table2`) AS `temp_rid`
    )
    ORDER BY `id`
) AS `temph`
GROUP BY `year`, `id`, `rate`
HAVING `row_number`<='5'
ORDER BY `id`, `rate DESC;
派生表上使用的用户变量和ORDER BY;这两种怪癖的行为都不能保证。修改后的答复如下

在MySQL 5.x中,您可以使用穷人在分区上的排名来获得所需的结果。只需将表与其自身连接起来,对于每一行,计算少于它的行数。在上述情况下,较小的行是速率较高的行:

SELECT t.id, t.rate, t.year, COUNT(l.rate) AS rank
FROM t
LEFT JOIN t AS l ON t.id = l.id AND t.rate < l.rate
GROUP BY t.id, t.rate, t.year
HAVING COUNT(l.rate) < 5
ORDER BY t.id, t.rate DESC, t.year
请注意,如果费率有联系,例如:

100, 90, 90, 80, 80, 80, 70, 60, 50, 40, ...
上述查询将返回6行:

100, 90, 90, 80, 80, 80
更改为COUNTDISTINCT l.rate<5以获得8行:

100, 90, 90, 80, 80, 80, 70, 60
或者更改为ON t.id=l.id和t.ratel.pri_键,以获得5行:

 100, 90, 90, 80, 80
在MySQL 8或更高版本中,只需使用以下函数:

SELECT *
FROM (
    SELECT *, RANK() OVER (PARTITION BY id ORDER BY rate DESC) AS rnk
    FROM t
) AS x
WHERE rnk <= 5

构建虚拟列(如Oracle中的RowID)

表:

数据:

SQL如下所示:

如果删除t3中的where子句,则如下所示:


获取Topn记录->添加行数在t3的where子句的where子句中添加2000到2009之间的行数

请尝试下面的存储过程。我已经核实过了。我得到了正确的结果,但没有使用groupby


花了一些工作,但我认为我的解决方案将是一些分享,因为它似乎优雅,以及相当快

SELECT h.year, h.id, h.rate 
  FROM (
    SELECT id, 
      SUBSTRING_INDEX(GROUP_CONCAT(CONCAT(id, '-', year) ORDER BY rate DESC), ',' , 5) AS l
      FROM h
      WHERE year BETWEEN 2000 AND 2009
      GROUP BY id
      ORDER BY id
  ) AS h_temp
    LEFT JOIN h ON h.id = h_temp.id 
      AND SUBSTRING_INDEX(h_temp.l, CONCAT(h.id, '-', h.year), 1) != h_temp.l

请注意,此示例是为问题的目的而指定的,并且可以很容易地修改以用于其他类似目的。

这很好,但MySQL没有像ROW_NUMBER这样的窗口函数。在MySQL 8.0中,ROW_NUMBER是。对于按原样工作的示例,只会给行号添加一个别名:按用户划分的行号按在描述处创建的行号按行号按列a.在字段列表中键入我认为值得一提的是,关键部分是按id排序,因为id值的任何更改都将重新开始按秩计数。我为什么要运行两次以获得来自WHERE-rank的响应@Brennolal我想您忘记了SET语句,请参见第一个查询。这是必要的。在较新版本中,派生表中的ORDER BY可以而且通常会被忽略。这会使目标落空。+1您的答案重写非常有效,因为现代MySQL/MariaDB版本遵循ANSI/ISO SQL 1992/1999/2003标准,而在交付/子查询中从未真正允许使用ORDER BY。。这就是为什么现代M
ySQL/MariaDB版本在不使用限制的情况下忽略了ORDER BY in子查询,我相信ANSI/ISO SQL标准2008/2011/2016在将ORDER BY in deliverd/subquery与FETCH FIRST n ROWS ONLY结合使用时使ORDER BY in deliverd/subqueries合法。如果您对同一id具有重复的速率,那么这将不起作用,因为您的rowNum计数将增加得更高;你不会得到每行3,你可以得到0,1或2。你能想出解决这个问题的办法吗?@饥饿者改变t1.rate这可以在MySQL中完成,但它并不像添加限制子句那么简单。这里有一篇文章详细解释了这个问题:这是一篇好文章-他介绍了一个优雅但幼稚的解决方案,解决了每组前N个问题,然后逐步改进。选择*从选择年份,id,从h开始的比率,其中2000年到2009年之间的年份和从表2中选择的id按id分组,按id年排序,rate DESC LIMIT 5性能优美,相对简单,解释力强;非常感谢你。到最后一点,可以计算出合理的最大长度,可以使用SET SESSION group_concat_max_len=;在OP的情况下,由于默认值为1024,因此不存在问题,但作为示例,group_concat_max_len应至少为25:4一年字符串的最大长度+1个分隔符,乘以5前5年。字符串被截断,而不是抛出错误,因此请注意警告,例如集合中的1054行,789警告0.31秒。如果我想获取准确的2行而不是1到5行,那么我应该使用集合中的FIND_。我尝试在集合=2中查找集合,但未按预期显示结果。如果大小等于或大于5,则介于1和5之间的集合中的查找集合将占据集合集合集合的前5个位置。因此,FIND_IN_SET=2将只获取组中第二个位置的数据。获得2行,您可以在1到2之间尝试第一和第二位置,假设集合有2行要给出。对于大型数据集,此解决方案的性能比Salman的要好得多。无论如何,我对这两种聪明的解决方案都赞不绝口。谢谢您的解决方案工作得很好,但我还想从子查询中检索year和其他列,我们如何才能做到这一点?
100, 90, 90, 80, 80, 80, 70, 60
 100, 90, 90, 80, 80
SELECT *
FROM (
    SELECT *, RANK() OVER (PARTITION BY id ORDER BY rate DESC) AS rnk
    FROM t
) AS x
WHERE rnk <= 5
CREATE TABLE `stack` 
(`year` int(11) DEFAULT NULL,
`id` varchar(10) DEFAULT NULL,
`rate` float DEFAULT NULL) 
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
insert into stack values(2006,'p01',8);
insert into stack values(2001,'p01',5.9);
insert into stack values(2007,'p01',5.3);
insert into stack values(2009,'p01',4.4);
insert into stack values(2001,'p02',12.5);
insert into stack values(2004,'p02',12.4);
insert into stack values(2005,'p01',2.1);
insert into stack values(2000,'p01',0.8);
insert into stack values(2002,'p02',12.2);
insert into stack values(2002,'p01',3.9);
insert into stack values(2004,'p01',3.5);
insert into stack values(2003,'p02',10.3);
insert into stack values(2000,'p02',8.7);
insert into stack values(2006,'p02',4.6);
insert into stack values(2007,'p02',3.3);
insert into stack values(2003,'p01',7.4);
insert into stack values(2008,'p01',6.8);
select t3.year,t3.id,t3.rate 
from (select t1.*, (select count(*) from stack t2 where t1.rate<=t2.rate and t1.id=t2.id) as rownum from stack t1) t3 
where rownum <=3 order by id,rate DESC;
CREATE DEFINER=`ks_root`@`%` PROCEDURE `first_five_record_per_id`()
BEGIN
DECLARE query_string text;
DECLARE datasource1 varchar(24);
DECLARE done INT DEFAULT 0;
DECLARE tenants varchar(50);
DECLARE cur1 CURSOR FOR SELECT rid FROM demo1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    SET @query_string='';

      OPEN cur1;
      read_loop: LOOP

      FETCH cur1 INTO tenants ;

      IF done THEN
        LEAVE read_loop;
      END IF;

      SET @datasource1 = tenants;
      SET @query_string = concat(@query_string,'(select * from demo  where `id` = ''',@datasource1,''' order by rate desc LIMIT 5) UNION ALL ');

       END LOOP; 
      close cur1;

    SET @query_string  = TRIM(TRAILING 'UNION ALL' FROM TRIM(@query_string));  
  select @query_string;
PREPARE stmt FROM @query_string;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

END
SELECT h.year, h.id, h.rate 
  FROM (
    SELECT id, 
      SUBSTRING_INDEX(GROUP_CONCAT(CONCAT(id, '-', year) ORDER BY rate DESC), ',' , 5) AS l
      FROM h
      WHERE year BETWEEN 2000 AND 2009
      GROUP BY id
      ORDER BY id
  ) AS h_temp
    LEFT JOIN h ON h.id = h_temp.id 
      AND SUBSTRING_INDEX(h_temp.l, CONCAT(h.id, '-', h.year), 1) != h_temp.l