web-dev-qa-db-ja.com

MySQLで複雑なGROUP BYを実行するにはどうすればよいですか?

他のテーブルへのいくつかのキーを含むテーブルがあります(各キーは複数の列で構成されています)。等しいキーを持つ行をグループ化したいのですが、allをグループ化したくありません。単純ではないGROUP BYキーについてですが、たとえば、10のグループを作成できるようにしたいと考えています。したがって、特定のキーが50回表示された場合、このグループ化(10の5グループ)を実行すると、5つの結果が得られます。また、このグループ化がキー内でランダムに発生するようにします。

これを行う直接的な方法を知りませんでした。私が思いついたラウンドアバウト方式は、思ったように機能しません。私が思いついたラウンドアバウトの解決策は、値iがそのキーのithオカレンスを表す(ただしランダムな順序で)ような整数である各キーの新しい列を作成することでした。次に、整数の除算を実行して、キー内のすべてのn(たとえば10)行が同じ値になるようにし、GROUP BYその値。

今説明したことを達成するためのより直接的な方法はありますか?これはかなり厄介で、新しいインデックス列を作成するときに問題が発生しました( この質問 で説明したとおり)。

編集:まず、これはMySQL用であることに注意してください。目標が明確でない場合に備えて、例を追加します。 MySQLのドキュメントには、ほとんどそこに を取得するメソッドが示されています

CREATE TABLE animals (
    grp ENUM('fish','mammal','bird') NOT NULL,
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name CHAR(30) NOT NULL,
    PRIMARY KEY (grp,id)
) ENGINE=MyISAM;

INSERT INTO animals (grp,name) VALUES
    ('mammal','dog'),('mammal','cat'),
    ('bird','penguin'),('fish','lax'),('mammal','whale'),
    ('bird','ostrich');

SELECT * FROM animals ORDER BY grp,id;

これは、私が望むものではありませんが、近くなるテーブルを作成します:

+--------+----+---------+
| grp    | id | name    |
+--------+----+---------+
| fish   |  1 | lax     |
| mammal |  1 | dog     |
| mammal |  2 | cat     |
| mammal |  3 | whale   |
| bird   |  1 | penguin |
| bird   |  2 | ostrich |
+--------+----+---------+

私は本質的にGROUP BY id、ただしmammalのレコードにID 1〜10の「グループ」を1つ、ID 11〜20の別の「グループ」を作成する場合を除きます。ただし、これは既存のテーブル、そして私は必ずしも "犬"がID 1で表示されることを望みません。その最初の順序をランダムにしたいのですが、その後は決定論的です。

8
Michael McGowan

グループを動的に生成するために、ID列に対して少し計算を行うとどうでしょうか。

SELECT grp, FLOOR(id/10) AS id_grp
FROM animals
GROUP BY grp, id_grp

これにより、レコードのIDに基づいて10のグループが得られます。上記の動物の表を使用して、以下のデータを生成しました。

サンプルデータ

 INSERT INTO animals VALUES
 ('mammal',10,'dog'),('mammal',11,'dog'),('mammal',12,'dog'),
 ('mammal',21,'cat'),('mammal',22,'cat'),('mammal',23,'cat'),
 ('mammal',24,'cat'),('mammal',25,'cat'),('mammal',26,'cat'),
 ('bird',30,'penguin'),('bird',31,'penguin'),('bird',32,'penguin'),
 ('bird',33,'penguin'),('fish',44,'lax'),('fish',45,'lax'),
 ('fish',46,'lax'),('fish',47,'lax'),('fish',48,'lax'),
 ('mammal',31,'whale'),*'fish',51,'lax'),('fish',52,'lax'),
 ('fish',53,'lax'),('fish',54,'lax'),('bird',10,'ostrich');

クエリ出力

 +--------+--------+
 | grp    | id_grp |
 +--------+--------+
 | fish   |      4 |
 | fish   |      5 |
 | mammal |      1 |
 | mammal |      2 |
 | mammal |      3 |
 | bird   |      1 |
 | bird   |      3 |
 +--------+--------+
 7 rows in set (0.00 sec)
5
nabrond

SQLでは通常、次のようになります。

  • dISTINCT副選択
  • DISTINCTキーでメインテーブルにJOINする
  • DISTINCTキーにPARTITION BYを指定したNTILEとバケットを作成するORDER BY

集合体ではないため、GROUP BYは必要ありません

編集:

実際、 [〜#〜] ntile [〜#〜] は、「個別の値のセットごとにnバケット」を作成するのに十分です

2
gbn

私はまだ完全なソリューション(実際にはMySQLで動作する)をまだ見ていません。そのため、これはおそらく私が使用するソリューションです。

  1. SQLの外部でランダムなIDを完全に生成する(ある種のスクリプトで)
  2. それらのIDに整数除算を適用して、それらを適宜グループ化します。

私はまだ誰かがこの答えを打つことができることを望んでいます。自分の答えを受け入れたくありません。私は以前にこれを言ったことがありますが、最初から2番目の方法を知っていました。 #1は私を悩ませているものです。 #1に答えることができる場合、実際には 別の質問 にも答えますが、この質問に他の方法で答えて#1をバイパスすることができる場合があります。

1
Michael McGowan
-- Change 'ValueField' to whatever provides your 'group' values

set @rownum := 0;
set @groupnum := 0;
set @lastGroup := 0;

select
    ValueField, 
    Grouping, 
    count(1) as Count
from
    (
        -- We have a row number for each record
        select
            -- Set the record number
            case when @lastGroup != ValueField 
                then @rownum := 0 else (@rownum := @rownum + 1) 
            end as Record, 

            -- Determine which group we are in
            case
                -- If the 'Group' changed, reset our grouping
                when @lastGroup != ValueField 
                    then @groupnum := 0

                -- Determines the grouping value; group size is set to 10
                when floor(@rownum / 10) != @groupnum 
                    then @groupnum := @groupnum + 1 
                else @groupnum
            end as Grouping,

            -- Track the last Group
            case 
                when @lastGroup != ValueField 
                    then @lastGroup := ValueField 
                else @lastGroup 
            end as LastGroup,

            -- Value field that will be aggregated
            ValueField 
        from 
            YourTable
        order by 
            ValueField
    ) as x
group by
    ValueField, 
    Grouping;
0
dba4life