Получить наиболее распространенное значение для каждого значения другого столбца в SQL - PullRequest
21 голосов
/ 05 декабря 2008

У меня есть такая таблица:

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

И для каждой страны я хочу получить еду, которую едят чаще всего. Лучшее, что я могу придумать (я использую postgres):

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;

В этом последнем утверждении GROUP BY и max () необходимы для разрыва связей, когда два разных продукта имеют одинаковое количество.

Это кажется большой работой для чего-то концептуально простого. Есть ли более прямой способ сделать это?

Ответы [ 9 ]

15 голосов
/ 16 сентября 2012

PostgreSQL представил поддержку оконных функций в 8.4, через год после того, как был задан этот вопрос. Стоит отметить, что сегодня это может быть решено следующим образом:

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;

Выше разорвут связи. Если вы не хотите разрывать связи, вы можете вместо этого использовать DENSE_RANK ().

8 голосов
/ 05 декабря 2008
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
    )

Ну, я спешно написал это и не очень хорошо проверил. Подвыбор может быть довольно медленным, но это самый короткий и самый простой оператор SQL, который я мог придумать. Я, наверное, скажу больше, когда буду меньше пьян.

PS: О, хорошо, «foo» - это название моей таблицы, «food» содержит название еды, а «country» - название страны. Пример вывода:

   food    |  country   
-----------+------------
 Bratwurst | Germany
 Fisch     | Frankreich
6 голосов
/ 25 апреля 2017

Теперь стало еще проще: в PostgreSQL 9.4 появилась функция mode():

select mode() within group (order by food_id)
from munch
group by country

возвращает (как в примере с user2247323):

country | mode
--------------
GB      | 3
US      | 1

См. Документацию здесь: https://wiki.postgresql.org/wiki/Aggregate_Mode

https://www.postgresql.org/docs/current/static/functions-aggregate.html#FUNCTIONS-ORDEREDSET-TABLE

5 голосов
/ 06 декабря 2008

попробуйте это:

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
3 голосов
/ 06 декабря 2008
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)  
3 голосов
/ 05 декабря 2008
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

Мне не нравится, когда MAX (.) GROUP BY разрывает связи ... Должен быть способ каким-то образом включить съеденную дату в JOIN для произвольного выбора самой последней ...

Меня интересует план запроса для этой вещи, если вы запускаете его на своих живых данных!

3 голосов
/ 05 декабря 2008

Вот как это сделать без временных таблиц:

Редактировать: упрощенный

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
                        )
2 голосов
/ 05 декабря 2008

Попробуйте что-то вроде этого

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)

Это можно было бы поместить в один выбор, но сейчас у меня нет времени разбираться с этим.

Удачи.

1 голос
/ 13 апреля 2016

Вот утверждение, которое, я считаю, дает вам то, что вы хотите, и простое и лаконичное:

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

Пожалуйста, дайте мне знать, что вы думаете.

Кстати, функция , отличная от , доступна только в Postgres.

Пример, исходные данные:

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

выход:

country | food_id
US        1
GB        3
...