¿Cómo hago un GROUP BY complejo en MySQL?

8

Tengo una tabla que contiene varias claves en otras tablas (donde cada clave se compone de varias columnas). Me gustaría poder agrupar filas que tengan una clave igual, pero no quiero agruparlas todas . No es simple GROUP BYen la clave, sino que quiero poder hacer grupos de digamos 10. Entonces, si una clave en particular apareciera 50 veces, obtendría 5 resultados cuando haga esta agrupación (5 grupos de 10). También quiero que esta agrupación ocurra al azar dentro de la clave.

No sabía la forma directa de hacer esto, y el método indirecto que se me ocurrió no funciona como creo que debería. La solución indirecta que se me ocurrió fue crear una nueva columna para cada clave que sería un número entero tal que el valor irepresente la ithaparición de esa clave (pero en orden aleatorio). Entonces podría hacer una división entera para que cada n (digamos 10) filas dentro de la clave tenga el mismo valor, y podría hacer un GROUP BYsobre ese valor.

¿Hay alguna forma más directa de lograr lo que acabo de describir? Es bastante incómodo, y tuve problemas para crear la nueva columna de índice (como describí en esta pregunta ).

EDITAR: en primer lugar, tenga en cuenta que esto es para MySQL. Agregaré un ejemplo en caso de que mi objetivo no esté claro. Los documentos de MySQL muestran un método para llegar casi allí :

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;

Esto crea una tabla que, aunque no es lo que quiero, se acerca:

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

Me esencialmente igual a GROUP BYla identificación, excepto que me gustaría los registros con mammalque tienen un "grupo" para los ID de 1-10, otro "grupo" para los ID 11-20, etc. Sin embargo, estaría haciendo esto con una tabla existente, y no necesariamente quiero que aparezca "perro" con ID 1. Quisiera que ese pedido inicial sea aleatorio, pero luego determinista a partir de ese momento.

Michael McGowan
fuente
I would want that initial ordering to be random, but then deterministic from then out.<- decir qué? Creo que no importa lo que hagas, tendrás que poner los registros en una segunda tabla de algún tipo. ¿Con qué precisión funciona esta lógica de negocios? Como es, no hay nada que requiera (por ejemplo) que el perro sea lo primero. ¿Y qué quieres decir con I would want the records from *mammal* to have one "group" for IDs 1-10, and another for IDs 11-20... puedes ilustrar eso con otra tabla, centrada en los mamíferos, en la descripción de la pregunta anterior?
jcolebrand
@jcolebrand Para cada registro que sea un mamífero, quiero asignar una identificación única de 1 a numMammal. Realmente no me importa qué identificador dogobtenga, pero no quiero que dependa del orden de inserción original.
Michael McGowan
@jcolebrand Supongamos que también tuviera una columna de peso. Es posible que desee tomar el peso promedio de los mamíferos con identificaciones de 1 a 10 y el peso promedio de los mamíferos con identificaciones de 11 a 20, etc. Ese es el sentido que quiero GROUP BY. Entonces podría querer emparejar grupos de 10 para encontrar la correlación entre el promedio. Necesito este orden aleatorio porque si el orden de inserción original se clasificara por peso, entonces me daría los resultados incorrectos. Espero tener sentido.
Michael McGowan
Sigo pensando que una TABLA de muestra en la pregunta sería útil. Pero creo que veo lo que quieres. Simplemente no veo dónde están esas cosas en el dominio de SQL, ya que no se trata realmente de conjuntos. SQL es el dominio de los conjuntos. Haría la lógica que estás sugiriendo en un archivo php con un solo (o dos) bucles. SQL estaría haciendo un bucle único efectivo para asignar los números de todos modos.
jcolebrand
@jcolebrand Puede ser que no debería estar haciendo esto en SQL, pero pensé que una regla práctica útil era dejar que la base de datos hiciera el trabajo por usted. Todavía estoy aprendiendo los límites de lo que debe y no debe procesarse dentro de la base de datos, pero en el pasado cuando intenté extraer resultados, procesarlos y luego pegar los resultados nuevamente, obtuve resultados de bajo rendimiento. (horas y horas porque probablemente estaba haciendo algo mal al volver a insertar los resultados).
Michael McGowan

Respuestas:

5

¿Qué tal hacer un poco de matemática contra su columna de ID para generar dinámicamente el grupo?

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

Esto le daría grupos de 10 según la ID del registro. Usé la tabla de animales de arriba para generar los datos a continuación.

Data de muestra

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

Consulta de salida

 +--------+--------+
 | 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
fuente
Estaba planeando hacer matemática similar si primero pudiera generar la tabla en cuestión. Tengo problemas para obtener las ID asignadas correctamente.
Michael McGowan
¿Ayuda esto en absoluto @MichaelMcGowan? explainextended.com/2009/03/05/row-sampling o jimlife.wordpress.com/2008/09/09/…
jcolebrand
@jcolebrand Gracias, todavía estoy mirando el primer enlace. Intenté un enfoque similar al segundo enlace y tuve problemas con él: dba.stackexchange.com/questions/1932/…
Michael McGowan
2

En SQL generalmente esto sería:

  • una subselección DISTINCT
  • ÚNETE de nuevo a la tabla principal con las teclas DISTINCT
  • NTILE con PARTITION BY en las teclas DISTINCT y ORDER BY para crear cubos

No es un agregado, por lo que GROUP BY no es necesario

Editar:

En realidad, NTILE es suficiente por sí solo para crear "n cubos por conjunto de valores distintos"

gbn
fuente
No creo que MySQL sea compatible con NTILE.
Michael McGowan
Lo sentimos, ese enlace implica que sí. Probablemente hay una solución / solución para NTILE por ahí.
gbn
Gran solución de Oracle.
Leigh Riffel
@Leigh Riffel: y SQL Server. Y Sybase. Y PostGres ...
gbn
2
@gbn No MySQL fue el punto que debería haber dejado claro. El artículo hace referencia a Oracle.
Leigh Riffel
1

Todavía no veo ninguna solución completa (que realmente funcione en MySQL), por lo que esta es la solución que probablemente usaré:

  1. Genere las ID aleatorias fuera de SQL por completo (en algún tipo de secuencia de comandos)
  2. Aplique la división de enteros en esos ID para agruparlos en consecuencia.

Todavía espero que alguien pueda superar esta respuesta; No quiero tener que aceptar mi propia respuesta. He dicho esto antes, pero sabía desde el principio cómo hacer el # 2; # 1 es lo que me ha estado preocupando. Si puede responder el n. ° 1, entonces también contestará otra pregunta , pero podría ser posible responder esta pregunta de alguna otra manera para evitar el n. ° 1.

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