2008-12-05 12 views
20

Tengo una tabla como la siguiente:Obtener valor más común para cada valor de otra columna en SQL

Column | Type | Modifiers 
---------+------+----------- 
country | text | 
food_id | int | 
eaten | date | 

Y para cada país, que desea obtener el alimento que se come con más frecuencia. Lo mejor que puedo pensar (estoy usando postgres) es:

CREATE TEMP TABLE counts AS 
    SELECT country, food_id, count(*) as count FROM munch GROUP BY country, food_id; 

CREATE TEMP TABLE max_counts AS 
    SELECT country, max(count) as max_count FROM counts GROUP BY country; 

SELECT country, max(food_id) FROM counts 
    WHERE (country, count) IN (SELECT * from max_counts) GROUP BY country; 

En esta última afirmación, la GROUP BY y max() son necesarios para romper los lazos, en los que dos alimentos diferentes tienen el mismo número.

Esto parece mucho trabajo para algo conceptualmente simple. ¿Hay una forma más directa de hacerlo?

Respuesta

2

Pruebe algo como esto

select country, food_id, count(*) cnt 
into #tempTbl 
from mytable 
group by country, food_id 

select country, food_id 
from #tempTbl as x 
where cnt = 
    (select max(cnt) 
    from mytable 
    where country=x.country 
    and food_id=x.food_id) 

Esto podría poner todo en una sola selección, pero no tengo tiempo para ensuciar alrededor con él en este momento.

Buena suerte.

3

Aquí es cómo hacerlo sin ningún tipo de tablas temporales:

Editar: simplifica

select nf.country, nf.food_id as most_frequent_food_id 
from national_foods nf 
group by country, food_id 
having 
    (country,count(*)) in ( 
         select country, max(cnt) 
         from 
          (
          select country, food_id, count(*) as cnt 
          from national_foods nf1 
          group by country, food_id 
         ) 
         group by country 
         having country = nf.country 
         ) 
+0

Estaría interesado en ver el plan de ejecución para este vs. la tabla temporal - los "que tienen" cláusulas se evalúan _after_ SELECT recupera las filas coincidentes , ¿derecho? Parece que puede haber un bote de IO extra. –

+0

Hay un par de escaneos de tablas completos en el plan, sí. – JosephStyons

7
SELECT DISTINCT 
"F1"."food", 
"F1"."country" 
FROM "foo" "F1" 
WHERE 
"F1"."food" = 
    (SELECT "food" FROM 
     (
      SELECT "food", COUNT(*) AS "count" 
      FROM "foo" "F2" 
      WHERE "F2"."country" = "F1"."country" 
      GROUP BY "F2"."food" 
      ORDER BY "count" DESC 
     ) AS "F5" 
     LIMIT 1 
    ) 

Bueno, escribió esto en un apuro y no comprobó muy bien. La sub-selección puede ser bastante lenta, pero esta es la declaración de SQL más corta y simple que pude pensar. Probablemente diga más cuando estoy menos ebrio.

PD: Oh bien, "foo" es el nombre de mi tabla, "comida" contiene el nombre de la comida y "país" el nombre del país. Salida de ejemplo: (.) ​​

food | country 
-----------+------------ 
Bratwurst | Germany 
Fisch  | Frankreich 
+0

Esos deben ser comillas simples en la mayoría de los lugares, creo. – ocket8888

3
SELECT country, MAX(food_id) 
    FROM(SELECT m1.country, m1.food_id 
      FROM munch m1 
     INNER JOIN (SELECT country 
          , food_id 
          , COUNT(*) as food_counts 
         FROM munch m2 
        GROUP BY country, food_id) as m3 
       ON m1.country = m3.country 
     GROUP BY m1.country, m1.food_id 
     HAVING COUNT(*)/COUNT(DISTINCT m3.food_id) = MAX(food_counts)) AS max_foods 
    GROUP BY country 

no me gusta el GRUPO POR MAX para romper los lazos ... Tiene que haber una manera de incorporar la fecha comido en el JOIN de alguna manera para seleccionar arbitrariamente el la más reciente ...

¡Estoy interesado en el plan de consulta para esto si lo ejecutas en tus datos en vivo!

3
select country,food_id, count(*) ne 
from food f1 
group by country,food_id  
having count(*) = (select max(count(*)) 
        from food f2 
        where country = f1.country 
        group by food_id) 
5

probar esto:

Select Country, Food_id 
From Munch T1 
Where Food_id= 
    (Select Food_id 
    from Munch T2 
    where T1.Country= T2.Country 
    group by Food_id 
    order by count(Food_id) desc 
     limit 1) 
group by Country, Food_id 
12

PostgreSQL introdujo soporte para window functions en 8.4, se pidió al año después de esta pregunta. Vale la pena señalar que podría resolverse hoy de la siguiente manera:

SELECT country, food_id 
    FROM (SELECT country, food_id, ROW_NUMBER() OVER (PARTITION BY country ORDER BY freq DESC) AS rn 
      FROM ( SELECT country, food_id, COUNT('x') AS freq 
        FROM country_foods 
       GROUP BY 1, 2) food_freq) ranked_food_req 
WHERE rn = 1; 

Lo anterior romperá lazos. Si no quiere romper lazos, podría usar DENSE_RANK() en su lugar.

+1

Gracias por esto, logré utilizar el mismo enfoque con t-sql en MS SQL Server. Una solución realmente elegante. – niallsco

1

Aquí es una declaración que creo que le da lo que quiere y es simple y concisa:

select distinct on (country) country, food_id 
from munch 
group by country, food_id 
order by country, count(*) desc 

Por favor, hágamelo saber lo que piensa.

Por cierto, el distinto en la característica solo está disponible en Postgres.

ejemplo, los datos fuente:

country | food_id | eaten 
US  1   2017-1-1 
US  1   2017-1-1 
US  2   2017-1-1 
US  3   2017-1-1 
GB  3   2017-1-1 
GB  3   2017-1-1 
GB  2   2017-1-1 

salida:

country | food_id 
US  1 
GB  3 
+0

Si va a proponer una nueva respuesta después de todo este tiempo, le recomiendo probarla en una tabla de muestra y publicar los resultados que obtenga. Además, mencione qué servidor de base de datos está utilizando (mysql o lo que sea). – ToolmakerSteve

+1

La característica _distinct on_ solo está disponible en Postgres, así que no estoy seguro de cómo harías algo como esto en otra base de datos. OP está usando Postgres, así que parece apropiado. Escribí esto usando la tabla de la base de datos sugerida por op llamada _munch_ que tiene tres campos: país (texto), food_id (int), y comida (fecha) – user2247323

Cuestiones relacionadas