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.


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)

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


@ 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"


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.


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;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.