Como eu faço um GROUP BY complexo no MySQL?

8

Eu tenho uma tabela que contém várias chaves em outras tabelas (onde cada chave é composta de várias colunas). Gostaria de poder agrupar linhas que tenham uma chave igual, mas não quero agrupar todas elas. Não é simples GROUP BYna chave, mas quero poder fazer grupos de dizer 10. Portanto, se uma chave específica aparecesse 50 vezes, obteria 5 resultados ao fazer esse agrupamento (5 grupos de 10). Também quero que esse agrupamento ocorra aleatoriamente dentro da chave.

Eu não sabia a maneira direta de fazer isso, e o método indireto que eu criei não está funcionando como eu acho que deveria. A solução da rotatória que eu criei foi criar uma nova coluna para cada chave que seria um número inteiro, de modo que o valor irepresente a ithocorrência dessa chave (mas em ordem aleatória). Eu poderia então fazer a divisão inteira para que cada n (digamos 10) linhas na chave tivesse o mesmo valor, e eu poderia fazer GROUP BYisso nesse valor.

Existe uma maneira mais direta de realizar o que acabei de descrever? É bastante estranho e tive problemas ao criar a nova coluna de índice (como descrevi nesta pergunta ).

EDIT: Antes de mais nada, note que isto é para o MySQL. Vou adicionar um exemplo caso meu objetivo não esteja claro. Os documentos do MySQL mostram um método para chegar quase lá :

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;

Isso cria uma tabela que, embora não seja o que eu quero, se aproxima:

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

Gostaria essencialmente de GROUP BYidentificar, exceto que eu gostaria que os registros mammaltivessem um "grupo" para os IDs 1-10, outro "grupo" para os IDs 11-20, etc. No entanto, eu faria isso com uma tabela existente, e eu não gostaria necessariamente que "cachorro" aparecesse com o ID 1. Gostaria que essa ordem inicial fosse aleatória, mas determinística a partir de então.

Michael McGowan
fonte
I would want that initial ordering to be random, but then deterministic from then out.<- diz o que? Acho que não importa o que você faça, você terá que colocar os registros em uma segunda tabela de algum tipo. Com que precisão essa lógica de negócios funciona? Como é que não há nada a exigir (por exemplo) cachorro para vir em primeiro lugar. E o que você quer dizer com I would want the records from *mammal* to have one "group" for IDs 1-10, and another for IDs 11-20... você pode ilustrar isso com outra tabela, com foco em mamíferos, na descrição da pergunta acima?
jcolebrand
@jcolebrand Para cada registro que é um mamífero, quero atribuir um ID único de 1 a numMammal. Realmente não me importo com o que o ID dogrecebe, mas não quero que ele dependa do pedido de inserção original.
Michael McGowan
@jcolebrand Suponha que eu também tenha uma coluna de peso. Talvez eu queira calcular o peso médio de mamíferos com IDs de 1 a 10 e o peso médio de mamíferos com IDs de 11 a 20, etc. Esse é o sentido que eu quero GROUP BY. Talvez eu queira emparelhar grupos de 10 para encontrar a correlação entre a média. Preciso dessa ordem aleatória, porque se a ordem de inserção original fosse classificada por peso, isso me daria resultados errados. Espero estar fazendo sentido.
Michael McGowan
Ainda acho que um exemplo de TABELA na pergunta seria útil. Mas acho que vejo o que você quer. Eu simplesmente não vejo onde essas coisas são o domínio do SQL, já que não se trata realmente de conjuntos. SQL é o domínio dos conjuntos. Eu faria a lógica que você está sugerindo em um arquivo php com um único (ou dois) loops. O SQL faria um loop único eficaz para atribuir os números de qualquer maneira.
jcolebrand
@ jcolebrand Pode ser que eu não deva fazer isso no SQL, mas achei uma regra útil deixar o banco de dados fazer o trabalho por você. Ainda estou aprendendo os limites do que deve e não deve ser processado no banco de dados, mas, no passado, quando tentei extrair resultados, processá-los e depois inseri-los novamente, obtive resultados ruins de desempenho (horas e horas, porque eu provavelmente estava fazendo algo errado ao inserir os resultados novamente).
Michael McGowan

Respostas:

5

Que tal fazer um pouco de matemática com sua coluna de ID para gerar dinamicamente o grupo?

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

Isso daria a você grupos de 10 com base no ID do registro. Usei a tabela de animais acima para gerar os dados abaixo.

Dados de amostra

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

Saída de consulta

 +--------+--------+
 | grp    | id_grp |
 +--------+--------+
 | fish   |      4 |
 | fish   |      5 |
 | mammal |      1 |
 | mammal |      2 |
 | mammal |      3 |
 | bird   |      1 |
 | bird   |      3 |
 +--------+--------+
 7 rows in set (0.00 sec)
nabrond
fonte
Eu estava pensando em fazer contas semelhantes se pudesse gerar primeiro a tabela em questão. Estou tendo problemas para obter os IDs atribuídos corretamente.
22611 Michael McGowan
Isso ajuda em tudo @MichaelMcGowan? explainextended.com/2009/03/05/row-sampling ou jimlife.wordpress.com/2008/09/09/…
jcolebrand
@ jcolebrand Obrigado, ainda estou vendo o primeiro link. Eu tentei uma abordagem semelhante à do 2º link e problemas teve com ele: dba.stackexchange.com/questions/1932/...
Michael McGowan
2

No SQL, geralmente isso seria:

  • uma subseleção DISTINCT
  • VOLTAR à tabela principal com as teclas DISTINCT
  • NTILE com PARTITION BY nas teclas DISTINCT e um ORDER BY para criar buckets

Como não é um agregado, GROUP BY não é necessário

Editar:

Na verdade, NTILE é suficiente por si só para criar "n buckets por conjunto de valores distintos"

gbn
fonte
Não acredito que o MySQL suporte NTILE.
Michael McGowan
Desculpe, esse link implica que sim. Provavelmente existe uma solução / solução alternativa para o NTILE por aí.
GBN
Ótima solução Oracle.
Leigh Riffel
@ Leigh Riffel: e SQL Server. E Sybase. E PostGres ...
gbn
2
@gbn Não o MySQL foi o ponto que eu deveria ter deixado claro. O artigo faz referência ao Oracle.
Leigh Riffel
1

Ainda não estou vendo nenhuma solução completa (que realmente funcione no MySQL), então esta é a solução que provavelmente vou usar:

  1. Gere os IDs aleatórios fora do SQL inteiramente (em algum tipo de script)
  2. Aplique divisão inteira nesses IDs para agrupá-los adequadamente.

Ainda espero que alguém consiga vencer essa resposta; Não quero ter que aceitar minha própria resposta. Eu já disse isso antes, mas sabia desde o início como fazer o segundo; O número 1 está me incomodando. Se você puder responder à pergunta nº 1, também responderá a outra pergunta , mas talvez seja possível responder a essa pergunta de alguma outra maneira, para contornar a pergunta nº 1.

Michael McGowan
fonte
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;
dba4life
fonte