如何在MySQL中执行复杂的GROUP BY?


8

我有一个表,其中包含几个进入其他表的键(每个键由多个列组成)。我希望能够将具有相同键的行分组在一起,但是我不想将所有行分组在一起。这不是简单GROUP BY的键,而是我希望能够使分组的数量为10。因此,如果某个特定键出现50次,则当我进行分组时(5组,共10组),我将获得5个结果。我也希望该分组在密钥内随机发生。

我不知道直接执行此操作的方法,而且我想出的环形交叉路方法无法正常工作。我想到的回旋解决方案是为每个键创建一个新列,该键将是一个整数,以使值i表示该ith键的出现(但以随机顺序)。然后,我可以进行整数除法,以使键中的每n个(例如10个)行具有相同的值,并且可以GROUP BY对该值进行a运算。

有没有更直接的方法来完成我刚刚描述的内容?这很尴尬,在创建新的索引列时遇到了问题(正如我在此问题中所述)。

编辑:首先请注意,这是针对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 BYid,除了我希望记录mammal具有一个ID为1-10的“组”,另一个ID为11-20的“组”,依此类推。但是,我将使用现有表来执行此操作,并且我并不一定希望“ dog”以ID 1出现。我希望初始顺序是随机的,但此后是确定性的。


I would want that initial ordering to be random, but then deterministic from then out.<-说什么?我认为无论您做什么工作,都必须将记录放入某种形式的第二张表中。此业务逻辑如何精确地工作?因为没有什么要求(例如)狗先出现。您的意思是I would want the records from *mammal* to have one "group" for IDs 1-10, and another for IDs 11-20...您能在上面的问题描述中用另一个专注于哺乳动物的表格来说明这一点吗?
jcolebrand

@jcolebrand对于每一个哺乳动物的记录,我都希望从1到分配一个唯一的ID numMammal。我并不在乎id dog会得到什么,但是我不希望它取决于原始的插入顺序。
Michael McGowan

@jcolebrand假设我也有一个体重专栏。我可能想获取ID为1-10的哺乳动物的平均体重和ID为11-20的哺乳动物的平均体重,等等。这就是我想要的GROUP BY。然后,我可能想配对10个一组以求平均值之间的相关性。我需要这种随机排序,因为如果原始插入顺序碰巧是按重量排序的,那么这将给我带来错误的结果。我希望我有道理。
Michael McGowan

我仍然认为问题中的示例表会有所帮助。但是我想我明白了你想要的。我只是看不到这些东西在哪里是SQL的领域,因为它实际上与集合无关。SQL是集合的域。我会用一个(或两个)循环在php文件中执行您建议的逻辑。SQL将执行有效的单循环以分配数字。
jcolebrand

@jcolebrand很有可能我不应该在SQL中执行此操作,但是我认为一个有用的经验法则是让数据库为您完成工作。我仍在学习数据库中应该处理和不应该处理的范围,但是在过去,当我尝试提取结果,对其进行处理,然后再将结果粘贴回去时,我得到了较差的性能结果(几个小时,因为我可能在插入结果时做错了一些事情)。
Michael McGowan

Answers:


5

对您的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)

如果可以先生成问题表,我正计划进行类似的数学运算。我无法正确分配ID。
Michael McGowan


@jcolebrand谢谢,我仍然在看第一个链接。我尝试了一种类似于第二个链接的方法,但遇到了问题:dba.stackexchange.com/questions/1932/…–
Michael McGowan

2

在SQL中,通常为:

  • DISTINCT子选择
  • 在DISTINCT键上联接回到主表
  • 使用DISTINCT键上的PARTITION BY的NTILE和用于创建存储桶的ORDER BY

它不是聚合的,因此不需要GROUP BY

编辑:

实际上,NTILE本身足以创建“每组不同值的n个存储桶”


我不相信MySQL支持NTILE。
Michael McGowan

抱歉,该链接暗示确实存在。那里可能有NTILE的解决方案/解决方法。
gbn

出色的Oracle解决方案。
Leigh Riffel

@Leigh Riffel:和SQL Server。和Sybase。和Postgres ...
GBN

2
@gbn我应该明确指出不是MySQL。本文引用了Oracle。
Leigh Riffel

1

我仍然没有看到任何完整的解决方案(实际上可以在MySQL中使用),所以这是我可能会使用的解决方案:

  1. 完全在SQL外部生成随机ID(使用某种脚本)
  2. 对这些ID应用整数除法,以对它们进行相应的分组。

我仍然希望有人能打败这个答案。我不想接受我自己的答案。我之前已经说过这一点,但是从一开始我就知道该怎么做。#1一直困扰着我。如果您可以回答#1,那么您实际上也可以回答另一个问题,但是也可以通过其他方式回答此问题,从而绕过#1。


0
-- 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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.