Comment faire un GROUP BY complexe dans MySQL?

8

J'ai une table qui contient plusieurs clés dans d'autres tables (où chaque clé est composée de plusieurs colonnes). Je voudrais pouvoir regrouper des lignes qui ont une clé égale, mais je ne veux pas les regrouper toutes ensemble. Ce n'est pas simple GROUP BYsur la clé, mais je veux plutôt pouvoir faire des groupes de disons 10. Donc, si une clé particulière apparaissait 50 fois, j'obtiendrais 5 résultats lorsque je fais ce regroupement (5 groupes de 10). Je souhaite également que ce regroupement se fasse de manière aléatoire dans la clé.

Je ne connaissais pas la façon directe de le faire, et la méthode du rond-point que j'ai trouvée ne fonctionne pas comme je le pense. La solution de rond-point que j'ai trouvée était de créer une nouvelle colonne pour chaque clé qui serait un entier tel que la valeur ireprésente l' ithoccurrence de cette clé (mais dans un ordre aléatoire). Je pourrais alors faire une division entière afin que toutes les n (disons 10) lignes de la clé aient la même valeur, et je pourrais faire un GROUP BYsur cette valeur.

Existe-t-il un moyen plus direct d'accomplir ce que je viens de décrire? C'est assez gênant et j'ai rencontré des problèmes lors de la création de la nouvelle colonne d'index (comme je l'ai décrit dans cette question ).

EDIT: Tout d'abord, notez que c'est pour MySQL. J'ajouterai un exemple au cas où mon objectif ne serait pas clair. Les documents MySQL montrent une méthode pour y arriver presque :

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;

Cela crée une table qui, bien que ce n'est pas ce que je veux, se rapproche:

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

Je voudrais essentiellement GROUP BYid, sauf que je voudrais que les enregistrements avec mammalaient un "groupe" pour les ID 1-10, un autre "groupe" pour les ID 11-20, etc. Cependant, je le ferais avec une table existante, et je ne voudrais pas nécessairement que "dog" apparaisse avec l'ID 1. Je voudrais que cet ordre initial soit aléatoire, mais ensuite déterministe.

Michael McGowan
la source
I would want that initial ordering to be random, but then deterministic from then out.<- dites quoi? Je pense que peu importe ce que vous ferez, vous devrez mettre les enregistrements dans un deuxième tableau. Comment fonctionne précisément cette logique métier? En l'état, il n'y a rien à exiger (par exemple) que le chien passe en premier. Et que voulez-vous dire par I would want the records from *mammal* to have one "group" for IDs 1-10, and another for IDs 11-20... pouvez-vous illustrer cela avec un autre tableau, axé sur les mammifères, dans la description de la question ci-dessus?
jcolebrand
@jcolebrand Pour chaque enregistrement qui est un mammifère, je veux attribuer un identifiant unique de 1 à numMammal. Je ne me soucie pas vraiment de ce que l'ID dogobtient, mais je ne veux pas que cela dépende de l'ordre d'insertion d'origine.
Michael McGowan
@jcolebrand Supposons que j'avais également une colonne de poids. Je pourrais vouloir prendre le poids moyen des mammifères avec des ID de 1 à 10 et le poids moyen des mammifères avec des ID de 11 à 20, etc. C'est le sens que je veux GROUP BY. Je pourrais alors vouloir jumeler des groupes de 10 pour trouver la corrélation entre la moyenne. J'ai besoin de cet ordre aléatoire, car si l'ordre d'insertion d'origine était trié en fonction du poids, cela me donnerait de mauvais résultats. J'espère que j'ai du sens.
Michael McGowan
Je pense toujours qu'un exemple de TABLE dans la question serait utile. Mais je pense que je vois ce que tu veux. Je ne vois tout simplement pas où ces choses sont le domaine de SQL, car il ne s'agit pas vraiment d'ensembles. SQL est le domaine des ensembles. Je ferais la logique que vous proposez dans un fichier php avec une (ou deux) boucles. SQL ferait une boucle simple efficace pour attribuer les numéros de toute façon.
jcolebrand
@jcolebrand Il se pourrait bien que je ne devrais pas faire cela en SQL, mais j'ai pensé qu'une règle d'or utile était de laisser la base de données faire le travail pour vous. J'apprends toujours les limites de ce qui devrait et ne devrait pas être traité dans la base de données, mais dans le passé, lorsque j'essayais d'extraire des résultats, de les traiter, puis de les coller à nouveau, j'ai obtenu de mauvais résultats de performance (heures et heures parce que je faisais probablement quelque chose de mal en réinsérant les résultats).
Michael McGowan

Réponses:

5

Que diriez-vous de faire un petit calcul avec votre colonne ID pour générer dynamiquement le groupe?

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

Cela vous donnerait des groupes de 10 en fonction de l'ID de l'enregistrement. J'ai utilisé votre tableau d'animaux ci-dessus pour générer les données ci-dessous.

Exemples de données

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

Sortie de requête

 +--------+--------+
 | 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
la source
J'avais l'intention de faire des calculs similaires si je pouvais d'abord générer le tableau en question. J'ai du mal à obtenir les identifiants correctement.
Michael McGowan
Est-ce que cela aide du tout @MichaelMcGowan? expliquentextendu.com/2009/03/05/row-sampling ou jimlife.wordpress.com/2008/09/09/…
jcolebrand
@jcolebrand Merci, je regarde toujours le premier lien. J'ai essayé une approche similaire au 2e lien et j'ai eu des problèmes avec elle: dba.stackexchange.com/questions/1932/…
Michael McGowan
2

En SQL, ce serait généralement:

  • une sous-sélection DISTINCT
  • REJOINDRE le tableau principal sur les touches DISTINCT
  • NTILE avec PARTITION BY sur les touches DISTINCT et un ORDER BY pour créer des compartiments

Ce n'est pas un agrégat donc GROUP BY n'est pas nécessaire

Éditer:

En fait, NTILE suffit à lui seul à créer "n compartiments par ensemble de valeurs distinctes"

gbn
la source
Je ne crois pas que MySQL supporte NTILE.
Michael McGowan
Désolé, ce lien implique que c'est le cas. Il existe probablement une solution / solution de contournement pour NTILE.
gbn
Grande solution Oracle.
Leigh Riffel
@Leigh Riffel: et SQL Server. Et Sybase. Et PostGres ...
gbn
2
@gbn Pas MySQL était le point que j'aurais dû clarifier. L'article fait référence à Oracle.
Leigh Riffel
1

Je ne vois toujours pas de solutions complètes (qui fonctionnent réellement dans MySQL), c'est donc la solution que j'utiliserai probablement:

  1. Générez entièrement les ID aléatoires en dehors de SQL (dans une sorte de script)
  2. Appliquez une division entière sur ces ID pour les regrouper en conséquence.

J'espère toujours que quelqu'un pourra battre cette réponse; Je ne veux pas avoir à accepter ma propre réponse. Je l'ai déjà dit, mais je savais depuis le début comment faire # 2; # 1 est ce qui me dérange. Si vous pouvez répondre # 1, vous répondez également à une autre question , mais il pourrait être possible de répondre à cette question d'une autre manière afin de contourner # 1.

Michael McGowan
la source
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
la source