Wie mache ich eine komplexe GROUP BY in MySQL?

8

Ich habe eine Tabelle, die mehrere Schlüssel in andere Tabellen enthält (wobei jeder Schlüssel aus mehreren Spalten besteht). Ich möchte in der Lage sein, Zeilen mit einem gleichen Schlüssel zu gruppieren, aber ich möchte nicht alle zusammen gruppieren . Es ist nicht einfach GROUP BYfür den Schlüssel, sondern ich möchte in der Lage sein, Gruppen von beispielsweise 10 zu bilden. Wenn also ein bestimmter Schlüssel 50 Mal auftaucht, würde ich bei dieser Gruppierung 5 Ergebnisse erhalten (5 Gruppen von 10). Ich möchte auch, dass diese Gruppierung innerhalb des Schlüssels zufällig erfolgt.

Ich wusste nicht, wie ich das direkt tun sollte, und die von mir entwickelte Kreisverkehrsmethode funktioniert nicht so, wie ich es mir vorgestellt habe. Die Kreisverkehrslösung, die ich mir ausgedacht habe, bestand darin, für jeden Schlüssel eine neue Spalte zu erstellen, die eine Ganzzahl ist, sodass der Wert idas ithAuftreten dieses Schlüssels darstellt (jedoch in zufälliger Reihenfolge). Ich könnte dann eine Ganzzahldivision durchführen, so dass alle n (sagen wir 10) Zeilen innerhalb des Schlüssels den gleichen Wert haben, und ich könnte einen GROUP BYfür diesen Wert ausführen.

Gibt es einen direkteren Weg, um das zu erreichen, was ich gerade beschrieben habe? Es ist ziemlich umständlich und ich hatte Probleme beim Erstellen der neuen Indexspalte (wie in dieser Frage beschrieben ).

EDIT: Beachten Sie zunächst, dass dies für MySQL ist. Ich werde ein Beispiel hinzufügen, falls mein Ziel nicht klar ist. Die MySQL-Dokumente zeigen eine Methode, um fast dorthin zu gelangen :

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;

Dadurch wird eine Tabelle erstellt, die, obwohl nicht das, was ich möchte, nahe kommt:

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

Ich möchte im Wesentlichen eine GROUP BYID, außer ich möchte, dass die Datensätze mammaleine "Gruppe" für die IDs 1-10, eine andere "Gruppe" für die IDs 11-20 usw. haben. Ich würde dies jedoch mit einer vorhandenen Tabelle tun. und ich würde nicht unbedingt wollen, dass "Hund" mit ID 1 auftaucht. Ich möchte, dass diese anfängliche Reihenfolge zufällig ist, aber dann von da an deterministisch.

Michael McGowan
quelle
I would want that initial ordering to be random, but then deterministic from then out.<- sag was? Ich denke, egal was Sie tun, Sie müssen die Aufzeichnungen in eine zweite Tabelle legen. Wie genau funktioniert diese Geschäftslogik? Es gibt nichts zu verlangen, dass (zum Beispiel) der Hund an erster Stelle steht. Und was meinst du mit I would want the records from *mammal* to have one "group" for IDs 1-10, and another for IDs 11-20... kannst du das mit einer anderen Tabelle veranschaulichen, die sich auf Säugetiere konzentriert, in der obigen Fragebeschreibung?
jcolebrand
@jcolebrand Für jeden Datensatz, der ein Säugetier ist, möchte ich eine eindeutige ID von 1 bis zuweisen numMammal. Es ist mir egal, was die ID dogbekommt, aber ich möchte nicht, dass es von der ursprünglichen Einfügereihenfolge abhängt.
Michael McGowan
@jcolebrand Angenommen, ich hatte auch eine Gewichtsspalte. Ich möchte vielleicht das Durchschnittsgewicht von Säugetieren mit IDs von 1-10 und das Durchschnittsgewicht von Säugetieren mit IDs von 11-20 usw. nehmen. Das ist der Sinn, den ich möchte GROUP BY. Ich möchte dann vielleicht Gruppen von 10 koppeln, um die Korrelation zwischen dem Durchschnitt zu finden. Ich brauche diese zufällige Reihenfolge, denn wenn die ursprüngliche Einfügereihenfolge nach Gewicht sortiert wäre, würde dies zu falschen Ergebnissen führen. Ich hoffe ich mache Sinn.
Michael McGowan
Ich denke immer noch, dass eine Beispieltabelle in der Frage hilfreich wäre. Aber ich glaube ich sehe was du willst. Ich sehe nur nicht, wo diese Dinge die Domäne von SQL sind, da es nicht wirklich um Mengen geht. SQL ist die Domäne von Mengen. Ich würde die Logik, die Sie vorschlagen, in einer PHP-Datei mit einer einzelnen (oder zwei) Schleife machen. SQL würde eine effektive Einzelschleife ausführen, um die Nummern trotzdem zuzuweisen.
Jcolebrand
@jcolebrand Es mag gut sein, dass ich dies nicht in SQL tun sollte, aber ich dachte, eine nützliche Faustregel wäre, die Datenbank die Arbeit für Sie erledigen zu lassen. Ich lerne immer noch die Grenzen dessen, was in der Datenbank verarbeitet werden soll und was nicht, aber in der Vergangenheit habe ich schlechte Leistungsergebnisse erzielt, als ich versucht habe, Ergebnisse abzurufen, zu verarbeiten und dann wieder einzustecken (Stunden und Stunden, weil ich wahrscheinlich etwas falsch gemacht habe, als ich die Ergebnisse wieder eingefügt habe).
Michael McGowan

Antworten:

5

Wie wäre es, wenn Sie ein wenig mit Ihrer ID-Spalte rechnen, um die Gruppe dynamisch zu generieren?

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

Dies würde Ihnen Gruppen von 10 basierend auf der ID des Datensatzes geben. Ich habe Ihre Tier-Tabelle oben verwendet, um die Daten unten zu generieren.

Beispieldaten

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

Ausgabe abfragen

 +--------+--------+
 | 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
quelle
Ich hatte vor, ähnliche Berechnungen anzustellen, wenn ich zuerst die betreffende Tabelle generieren könnte. Ich habe Probleme, die IDs richtig zuzuweisen.
Michael McGowan
Hilft das überhaupt @MichaelMcGowan? explainextended.com/2009/03/05/row-sampling oder jimlife.wordpress.com/2008/09/09/…
jcolebrand
@jcolebrand Danke, ich schaue immer noch auf den ersten Link. Ich habe versucht, einen Ansatz ähnlich dem 2. Link und hatte Probleme damit: dba.stackexchange.com/questions/1932/…
Michael McGowan
2

In SQL wäre dies im Allgemeinen:

  • eine DISTINCT-Unterauswahl
  • JOIN zurück zur Haupttabelle auf DISTINCT-Tasten
  • NTILE mit PARTITION BY auf DISTINCT-Schlüsseln und einem ORDER BY zum Erstellen von Buckets

Es ist kein Aggregat, daher wird GROUP BY nicht benötigt

Bearbeiten:

Tatsächlich reicht NTILE aus, um "n Buckets pro Satz unterschiedlicher Werte" zu erstellen.

gbn
quelle
Ich glaube nicht, dass MySQL NTILE unterstützt.
Michael McGowan
Entschuldigung, dieser Link impliziert, dass dies der Fall ist. Es gibt wahrscheinlich eine Lösung / Problemumgehung für NTILE.
gbn
Großartige Oracle-Lösung.
Leigh Riffel
@Leigh Riffel: und SQL Server. Und Sybase. Und PostGres ...
gbn
2
@gbn Nicht MySQL war der Punkt, den ich hätte klarstellen sollen. Der Artikel verweist auf Oracle.
Leigh Riffel
1

Ich sehe immer noch keine vollständigen Lösungen (die tatsächlich in MySQL funktionieren), daher ist dies die Lösung, die ich wahrscheinlich verwenden werde:

  1. Generieren Sie die zufälligen IDs vollständig außerhalb von SQL (in einer Art Skript).
  2. Wenden Sie auf diese IDs eine Ganzzahldivision an, um diese entsprechend zu gruppieren.

Ich hoffe immer noch, dass jemand diese Antwort schlagen kann; Ich möchte meine eigene Antwort nicht akzeptieren müssen. Ich habe das schon einmal gesagt, aber ich wusste von Anfang an, wie man # 2 macht; # 1 hat mich beunruhigt. Wenn Sie # 1 beantworten können, beantworten Sie tatsächlich auch eine andere Frage , aber es ist möglicherweise möglich, diese Frage auf eine andere Weise zu beantworten, um # 1 zu umgehen.

Michael McGowan
quelle
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
quelle