2009-09-10 8 views
7

¿Cómo obtengo la categoría más frecuente para cada etiqueta en MySQL? Idealmente, me gustaría simular una función agregada que calcule el mode de una columna.SELECCIONAR MySQL más frecuente por grupo

SELECT 
    t.tag 
    , s.category 
FROM tags t 
LEFT JOIN stuff s 
USING (id) 
ORDER BY tag; 

+------------------+----------+ 
| tag    | category | 
+------------------+----------+ 
| automotive  |  8 | 
| ba    |  8 | 
| bamboo   |  8 | 
| bamboo   |  8 | 
| bamboo   |  8 | 
| bamboo   |  8 | 
| bamboo   |  8 | 
| bamboo   |  10 | 
| bamboo   |  8 | 
| bamboo   |  9 | 
| bamboo   |  8 | 
| bamboo   |  10 | 
| bamboo   |  8 | 
| bamboo   |  9 | 
| bamboo   |  8 | 
| banana tree  |  8 | 
| banana tree  |  8 | 
| banana tree  |  8 | 
| banana tree  |  8 | 
| bath    |  9 | 
+-----------------------------+ 
+0

sólo pensé que me gustaría mencionar un par de años más tarde y más inteligente - no organizar las etiquetas de este tipo, que es un anti patrón. Use una tabla many2many para definir la relación entre etiquetas y elementos. Dicho esto, todavía me gustaría que hubiera una función agregada MODE en MySQL. –

Respuesta

3
SELECT t1.* 
FROM (SELECT tag, category, COUNT(*) AS count 
     FROM tags INNER JOIN stuff USING (id) 
     GROUP BY tag, category) t1 
LEFT OUTER JOIN 
    (SELECT tag, category, COUNT(*) AS count 
     FROM tags INNER JOIN stuff USING (id) 
     GROUP BY tag, category) t2 
    ON (t1.tag = t2.tag AND (t1.count < t2.count 
     OR t1.count = t2.count AND t1.category < t2.category)) 
WHERE t2.tag IS NULL 
ORDER BY t1.count DESC; 

Estoy de acuerdo esto es un poco demasiado para una sola consulta SQL. Cualquier uso de GROUP BY dentro de una subconsulta me hace dar un respingo. Puede que sea aspecto más simple mediante el uso de puntos de vista:

CREATE VIEW count_per_category AS 
    SELECT tag, category, COUNT(*) AS count 
    FROM tags INNER JOIN stuff USING (id) 
    GROUP BY tag, category; 

SELECT t1.* 
FROM count_per_category t1 
LEFT OUTER JOIN count_per_category t2 
    ON (t1.tag = t2.tag AND (t1.count < t2.count 
     OR t1.count = t2.count AND t1.category < t2.category)) 
WHERE t2.tag IS NULL 
ORDER BY t1.count DESC; 

pero es básicamente el mismo trabajo detrás de las escenas.

Comenta que podría hacer una operación similar fácilmente en el código de la aplicación. Entonces, ¿por qué no haces eso? Haga la consulta más simple para obtener los recuentos por categoría:

SELECT tag, category, COUNT(*) AS count 
FROM tags INNER JOIN stuff USING (id) 
GROUP BY tag, category; 

Y ordene el resultado en el código de la aplicación.

+0

He estado teniendo dificultades para conseguir que funcione. Parece que sería mejor hacer una función de agregado MOST_FREQUENT(). Voy a ver si eso está dentro de mi nivel de habilidad aquí ... –

+0

Lo siento, yo malentendido tu esquema He echado un vistazo más de cerca y me he burlado de una base de datos de prueba para estar seguro de que la consulta funciona. Pruebe la versión editada arriba. –

+0

Eso parece funcionar. Aunque es un poco difícil de tragar ... y hay dos sub-selecciones en lugar de solo una. Ojalá hubiera solo una función agregada MEAN() o algo así :-P. Probablemente podría escribir eso usando C en 5min. –

2
SELECT tag, category 
FROM (
     SELECT @tag <> tag AS _new, 
       @tag := tag AS tag, 
       category, COUNT(*) AS cnt 
     FROM (
       SELECT @tag := '' 
       ) vars, 
       stuff 
     GROUP BY 
       tag, category 
     ORDER BY 
       tag, cnt DESC 
     ) q 
WHERE _new 

En sus datos, este devuelve lo siguiente:

'automotive', 8 
'ba',   8 
'bamboo',  8 
'bananatree', 8 
'bath',  9 

Aquí está la escritura de la prueba:

CREATE TABLE stuff (tag VARCHAR(20) NOT NULL, category INT NOT NULL); 

INSERT 
INTO stuff 
VALUES 
('automotive',8), 
('ba',8), 
('bamboo',8), 
('bamboo',8), 
('bamboo',8), 
('bamboo',8), 
('bamboo',8), 
('bamboo',10), 
('bamboo',8), 
('bamboo',9), 
('bamboo',8), 
('bamboo',10), 
('bamboo',8), 
('bamboo',9), 
('bamboo',8), 
('bananatree',8), 
('bananatree',8), 
('bananatree',8), 
('bananatree',8), 
('bath',9); 
3

(Editar: se olvidó DESC en apartaderos orden)

Fácil hacer con un LÍMITE en la subconsulta. ¿MySQL todavía tiene la restricción no-LIMIT-in-subqueries? El ejemplo siguiente es usar PostgreSQL.

=> select tag, (select category from stuff z where z.tag = s.tag group by tag, category order by count(*) DESC limit 1) AS category, (select count(*) from stuff z where z.tag = s.tag group by tag, category order by count(*) DESC limit 1) AS num_items from stuff s group by tag; 
    tag  | category | num_items 
------------+----------+----------- 
ba   |  8 |   1 
automotive |  8 |   1 
bananatree |  8 |   4 
bath  |  9 |   1 
bamboo  |  8 |   9 
(5 rows) 

La tercera columna solo es necesaria si necesita el conteo.

1

Esto es para situaciones más simples:

SELECT action, COUNT(action) AS ActionCount FROM log GROUP BY action ORDER BY ActionCount DESC;