Выбор или фильтрация столбцов на основе рангов нескольких полей в PostgreSQL

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

+---------+---------------+-----------+-----------------+-------------+----------------------+------------+
| Country |     City      | City_Rank |     Artist      | Artist_Rank |        Movie         | Movie_Rank |
+---------+---------------+-----------+-----------------+-------------+----------------------+------------+
| USA     | Las Vegas     |         2 | Louis C.K       |           2 | Justice League       |          3 |
| USA     | New York City |         3 | Michael Flynn   |           3 | IT                   |          1 |
| USA     | Los Angeles   |         1 | Matt Lauer      |           1 | Get Out              |          2 |
| UK      | Leeds         |         2 | Jack Maynard    |           3 | Beauty and the Beast |          2 |
| UK      | Manchester    |         3 | Charlie Gard    |           1 | Wonder Woman         |          1 |
| UK      | London        |         1 | Shannon Mathews |           2 | Logan                |          3 |
+---------+---------------+-----------+-----------------+-------------+----------------------+------------+

Теперь мне нужен ранг 1 City, Artist и Movie, сгруппированных по Country в одной записи. Таким образом, ожидаемый результат:

+---------+------------------+--------------------+-------------------+
| Country | Best_Ranked_City | Best_Ranked_Artist | Best_Ranked_Movie |
+---------+------------------+--------------------+-------------------+
| USA     | Los Angeles      | Matt Lauer         | IT                |
| UK      | London           | Charlie Gard       | Wonder Woman      |
+---------+------------------+--------------------+-------------------+

У меня есть еще много атрибутов, против которых у меня есть поле ранга. Я могу получить желаемый результат, сформировав несколько наборов данных из вышеперечисленного с условием фильтрации для каждого ранжированного поля (где ранг = 1), а затем объединив эти наборы данных по групповому полю.

Однако это довольно затратное дело из-за миллионов записей в таблице, и многократная фильтрация и объединение этого набора данных не кажутся лучшим способом решить эту проблему. Я получил ранги для каждого поля, используя Rank() функцию Windows, применив к ней некоторую бизнес-логику.

Я хотел бы в дальнейшем решить эту проблему, используя функцию Window, только если это возможно.


person Jaspreet Singh    schedule 21.12.2017    source источник
comment
Это звучит как вопрос SQL, но код отформатирован в формате html. Если это вопрос SQL, можете ли вы опубликовать SQL, с которым вам нужна помощь? Если нет, возможно, некоторые пояснения к тому, что именно поможет.   -  person tatmanblue    schedule 21.12.2017
comment
Я переформатировал таблицы ASCII - я думаю, это то, что вы имели в виду?   -  person Benjamin W.    schedule 22.12.2017


Ответы (2)


Я получил ранги для каждого поля, используя функцию Windows Rank(), применив к ней некоторую бизнес-логику.

Я предполагаю, что есть какой-то запрос, который вычисляет ранги, а затем выполняет операцию сводки для генерации сводная таблица, показанная в вопросе.
Было бы хорошо исключить операцию сводки, чтобы входные данные, сгенерированные этим запросом, выглядели примерно так:

| country | category |            cat_value | rank_value |
|---------|----------|----------------------|------------|
|      UK |   Artist |         Jack Maynard |          3 |
|      UK |   Artist |      Shannon Mathews |          2 |
|      UK |   Artist |         Charlie Gard |          1 |
|      UK |     City |                Leeds |          2 |
|      UK |     City |           Manchester |          3 |
|      UK |     City |               London |          1 |
|      UK |    Movie |                Logan |          3 |
|      UK |    Movie | Beauty and the Beast |          2 |
|      UK |    Movie |         Wonder Woman |          1 |
|     USA |   Artist |            Louis C.K |          2 |
|     USA |   Artist |        Michael Flynn |          3 |
|     USA |   Artist |           Matt Lauer |          1 |
|     USA |     City |            Las Vegas |          2 |
|     USA |     City |          Los Angeles |          1 |
|     USA |     City |        New York City |          3 |
|     USA |    Movie |       Justice League |          3 |
|     USA |    Movie |                   IT |          1 |
|     USA |    Movie |              Get Out |          2 |

Если это невозможно, то этот набор результатов можно отменить, используя:

SELECT Country, 'City' as category, City as cat_value, City_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Artist' as category, Artist as cat_value, Artist_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Movie' as category, Movie as cat_value, Movie_Rank as rank_value
FROM Table1

Если вы развернете эту таблицу, то выбрать элементы с рангом = 1 очень просто, просто выполните:

SELECT * FROM unpivot_table WHERE rank_value = 1

а затем можно сделать еще один поворот по его результатам.


Окончательный запрос может выглядеть следующим образом (демонстрация: http://sqlfiddle.com/#!17/05e53/5)

With unpivot_me As (
SELECT Country, 'City' as category, City as cat_value, City_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Artist' as category, Artist as cat_value, Artist_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Movie' as category, Movie as cat_value, Movie_Rank as rank_value
FROM Table1
)


SELECT Country,
       Max( case when category = 'City' Then cat_value End) As Best_Ranked_City,
       Max( case when category = 'Artist' Then cat_value End) As Best_Ranked_Artist,
       Max( case when category = 'Movie' Then cat_value End) As Best_Ranked_Movie
FROM unpivot_me
WHERE rank_value = 1 
GROUP BY Country

| country | best_ranked_city | best_ranked_artist | best_ranked_movie |
|---------|------------------|--------------------|-------------------|
|      UK |           London |       Charlie Gard |      Wonder Woman |
|     USA |      Los Angeles |         Matt Lauer |                IT |
person krokodilko    schedule 21.12.2017
comment
Спасибо за ваше возвращение. Я не применял операцию поворота для получения набора данных. Данные были моей базовой таблицей, как показано в моем посте, без полей ранга. Я получил ранги на основе некоторой бизнес-логики. - person Jaspreet Singh; 22.12.2017
comment
Чтобы добавить к моему предыдущему комментарию: в моей таблице миллионы записей, и я хочу ранжировать примерно 8-9 полей. Насколько я понимаю, объединение этого набора данных 8-9 раз будет очень дорогим и, вероятно, будет похоже на фильтрацию наборов данных с рангом 1 и их последующее объединение. - person Jaspreet Singh; 22.12.2017
comment
Да, разворот будет очень дорогим. По этой причине было бы лучше изменить запрос, который вычисляет ранги и присваивает их значениям, таким образом, чтобы он генерировал набор результатов по строкам, а не по столбцам, как сейчас (и как показано в вопросе). Другими словами, запрос, присваивающий ранги, тоже нужно переписать. - person krokodilko; 22.12.2017
comment
Смог решить это, используя оконную функцию с теми же рангами. Опубликовали ответ после попытки sqlfiddle. Попробую на реальных данных после отпуска. Спасибо за вашу помощь :-) - person Jaspreet Singh; 23.12.2017

Использовали оконную функцию max() и в ней поместили условие случая, когда ранги 1 разделены по странам. Это позволило получить значения первого ранга для нужных столбцов по всем странам. Позже отфильтровал его, используя одно из ранжированных полей со значением 1 (можно было отфильтровать, используя любое из доступных ранговых полей). Вот SQL: http://sqlfiddle.com/#!17/05e53/18

With T1 as (
select Country, max(case when City_Rank =1 then City else '' end) 
over (partition by Country) as Best_Ranked_City, City_Rank, 
max(case when Artist_Rank =1 then Artist else '' end) 
over (partition by Country) as Best_Ranked_Artist, max(case when 
Movie_Rank =1 then Movie else '' end) 
over (partition by Country) as Best_Ranked_Movie 
from Table1
  )
select Country, Best_Ranked_City, Best_Ranked_Artist, Best_Ranked_Movie 
from T1 where city_rank=1;
person Jaspreet Singh    schedule 23.12.2017