467

次のクエリ:

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内で機能する修飾子のようなある種のLIMITを使用してこれを行う方法はありますか?

4

13 に答える 13

147

GROUP_CONCAT集計関数を使用して、すべての年を単一の列に取得し、グループ化してid並べ替えることができrateます。

SELECT   id, GROUP_CONCAT(year ORDER BY rate DESC) grouped_year
FROM     yourtable
GROUP BY id

結果:

-----------------------------------------------------------
|  ID | GROUPED_YEAR                                      |
-----------------------------------------------------------
| p01 | 2006,2003,2008,2001,2007,2009,2002,2004,2005,2000 |
| p02 | 2001,2004,2002,2003,2000,2006,2007                |
-----------------------------------------------------------

そして、FIND_IN_SETを使用できます。これは、2 番目の引数内の最初の引数の位置を返します。

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

と の組み合わせを使用し、 find_in_set によって返された位置でフィルタリングするGROUP_CONCATFIND_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;

ここでフィドルを参照してください。

複数の行が同じレートを持つ可能性がある場合は、列ではなく列で使用GROUP_CONCAT(DISTINCT rate ORDER BY rate)することを検討する必要があることに注意してください。rateyear

によって返される文字列の最大長GROUP_CONCATは制限されているため、グループごとにいくつかのレコードを選択する必要がある場合は、これがうまく機能します。

于 2013-03-23T09:47:24.877 に答える
22

私にとっては

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;
于 2013-10-04T15:35:28.027 に答える
13

いいえ、サブクエリを任意に LIMIT することはできません (新しい MySQL では制限付きで実行できますが、グループごとに 5 つの結果に対しては実行できません)。

これは groupwise-maximum 型のクエリであり、SQL で行うのは簡単ではありません。いくつかのケースではより効率的である可能性があることに取り組むにはさまざまな方法がありますが、一般的にトップ n については、同様の以前の質問に対する Bill の回答を参照することをお勧めします。

この問題に対するほとんどの解決策と同様に、同じ値を持つ行が複数ある場合は 5 行を超える行が返されるrate可能性があるため、それを確認するために多くの後処理が必要になる場合があります。

于 2010-01-25T01:37:08.147 に答える
10

これには、値をランク付けし、それらを制限し、グループ化しながら合計を実行する一連のサブクエリが必要です

@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;
于 2012-11-02T00:11:19.733 に答える
10
SELECT year, id, rate
FROM (SELECT
  year, id, rate, row_number() over (partition by id order by rate DESC)
  FROM h
  WHERE year BETWEEN 2000 AND 2009
  AND id IN (SELECT rid FROM table2)
  GROUP BY id, year
  ORDER BY id, rate DESC) as subquery
WHERE row_number <= 5

サブクエリはクエリとほぼ同じです。変更のみが追加されます

row_number() over (partition by id order by rate DESC)
于 2012-11-29T00:04:32.177 に答える
9

これを試して:

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;
于 2013-01-05T09:37:40.427 に答える
5

仮想列(Oracle の RowID など)を構築します</p>

テーブル:

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);

次のような SQL:

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;

t3 の where 句を削除すると、次のようになります。

ここに画像の説明を入力

GET "TOP N Record" --> rownum <=3inwhere句 (t3 の where 句) を追加します。

「年」を選択 --> BETWEEN 2000 AND 2009inwhere句 (t3 の where 句) を追加します。

于 2016-05-09T10:16:02.497 に答える
3

いくつかの作業が必要でしたが、私の解決策はエレガントで非常に高速であるように見えるので、共有するものになると思いました.

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

この例は質問の目的のために指定されており、他の同様の目的のために非常に簡単に変更できることに注意してください。

于 2016-10-25T13:20:14.150 に答える
2

次の投稿: sql: selcting top N record per groupは、サブクエリなしでこれを達成する複雑な方法を説明しています。

ここで提供される他のソリューションを次のように改善します。

  • 単一のクエリですべてを行う
  • インデックスを適切に活用できるようになる
  • MySQL で不適切な実行計画を作成することが知られているサブクエリを回避する

ただし、きれいではありません。MySQL で Window 関数 (別名、Analytic Functions) が有効になっていれば、適切な解決策を実現で​​きますが、そうではありません。上記の投稿で使用されているトリックは、「MySQL の貧弱なウィンドウ関数」と呼ばれることがある GROUP_CONCAT を利用しています。

于 2012-07-06T17:10:04.957 に答える
1

私のようにクエリがタイムアウトした人のために。特定のグループによる制限などを使用するために、以下を作成しました。

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 の制限のみを挿入します

于 2012-12-17T17:14:52.773 に答える
1

以下のストアドプロシージャをお試しください。すでに確認済みです。私は適切な結果を得ていますが、使用しませんgroupby

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
于 2016-07-15T12:39:33.477 に答える
1

これを試して:

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;
于 2014-12-24T08:07:44.157 に答える