Используйте что-то вроде TOP с GROUP BY
в таблице table1
ниже
+--------+-------+-------+------------+-------+
| flight | orig | dest | passenger | bags |
+--------+-------+-------+------------+-------+
| 1111 | sfo | chi | david | 3 |
| 1112 | sfo | dal | david | 7 |
| 1112 | sfo | dal | kim | 10|
| 1113 | lax | san | ameera | 5 |
| 1114 | lax | lfr | tim | 6 |
| 1114 | lax | lfr | jake | 8 |
+--------+-------+-------+------------+-------+
Я агрегирую таблицу по orig
ниже
select
orig
, count(*) flight_cnt
, count(distinct passenger) as pass_cnt
, percentile_cont(0.5) within group ( order by bags ASC) as bag_cnt_med
from table1
group by orig
мне нужно добавить passenger
С самым длинным названием ( length(passenger)
) для каждого orig
группа-как мне это сделать?
ожидаемый результат
+------+-------------+-----------+---------------+-------------------+
| orig | flight_cnt | pass_cnt | bags_cnt_med | pass_max_len_name |
+------+-------------+-----------+---------------+-------------------+
| sfo | 3 | 2 | 7 | david |
| lax | 3 | 3 | 6 | ameera |
+------+-------------+-----------+---------------+-------------------+
5 ответов
вы можете удобно получить пассажира с самым длинным именем в группе с DISTINCT ON
.
но я не вижу способа объединить это (или любой другой простой способ) с вашим исходным запросом в одном SELECT
. Предлагаю объединить два отдельных подзапроса:
SELECT *
FROM ( -- your original query
SELECT orig
, count(*) AS flight_cnt
, count(distinct passenger) AS pass_cnt
, percentile_cont(0.5) WITHIN GROUP (ORDER BY bags) AS bag_cnt_med
FROM table1
GROUP BY orig
) org_query
JOIN ( -- my addition
SELECT DISTINCT ON (orig) orig, passenger AS pass_max_len_name
FROM table1
ORDER BY orig, length(passenger) DESC NULLS LAST
) pas USING (orig);
USING
в предложении Join удобно выводит только один экземпляр orig
, так что вы можете просто использовать SELECT *
во внешнем SELECT
.
если passenger
может быть NULL, важно добавить NULLS LAST
:
из нескольких имен пассажиров с одинаковой максимальной длиной в одной группе вы получаете произвольный выбор - если вы не добавите больше выражений в ORDER BY
как тай-брейке. Подробное объяснение в ответе связано выше.
производительность?
как правило, одно сканирование превосходит, особенно с последовательным сканированием.
выше использует запрос два сканирование (возможно, сканирование индекса / только индекса). Но второе сканирование сравнительно дешево, если таблица не слишком велика, чтобы поместиться в кэше (в основном). Лукас предложил альтернативный запрос с один SELECT
добавляем:
, (ARRAY_AGG (passenger ORDER BY LENGTH (passenger) DESC))[1] -- I'd add NULLS LAST
идея умная, но последний раз, когда я тестировал, array_agg
С ORDER BY
не так хорошо. (Накладные расходы на группу ORDER BY
является существенным, и обработка массива также является дорогостоящей.)
такой же подход может быть дешевле с пользовательской агрегатной функции first()
как указано в Вики Postgres здесь. Или, еще быстрее, с версия, написанная на C, доступна на PGXN. Исключает дополнительную цену для регулировать массива, но мы все еще потребность в группе ORDER BY
. может быть быстрее только для нескольких групп. Затем вы добавляете:
, first(passenger ORDER BY length(passenger) DESC NULLS LAST)
Гордон и Лукаш также упомяните функцию окна first_value()
. Применяются оконные функции после агрегатные функции. Использовать его в том же SELECT
, нам нужно было бы агрегировать passenger
как-то первый-Уловка 22. Гордон решает это с помощью подзапроса-еще один кандидат на добро производительность со стандартными Postgres.
first()
делает то же самое без подзапрос и должен быть проще и немного быстрее. Но это все равно не будет быстрее, чем отдельной DISTINCT ON
для большинства случаев с несколькими строками в группе. Для большого количества строк в группе рекурсивный метод CTE обычно быстрее. Есть еще более быстрые методы, Если у вас есть отдельная таблица, содержащая все соответствующие, уникальные orig
значения. Подробности:
лучшее решение зависит от различных факторов. Доказательство пудинга в еде. Для оптимизации производительности необходимо протестировать программу установки. Вышеуказанный запрос должен быть одним из самых быстрых.
один метод использует функцию окна first_value()
. К сожалению, это недоступно в качестве функции агрегирования:
select orig,
count(*) flight_cnt,
count(distinct passenger) as pass_cnt,
percentile_cont(0.5) within group ( order by bags ASC) as bag_cnt_med,
max(longest_name) as longest_name
from (select t1.*,
first_value(name) over (partition by orig order by length(name) desc) as longest_name
from table1
) t1
group by orig;
вы ищете что-то вроде Oracle KEEP FIRST/LAST
где вы получаете значение (имя пассажира) в соответствии с агрегатом (длина имени). Насколько я знаю, PostgreSQL не имеет такой функции.
один из способов сделать это трюк: объединить длину и имя, получить максимум, а затем извлечь имя:'0005david'
>'0003kim'
etc.
select
orig
, count(*) flight_cnt
, count(distinct passenger) as pass_cnt
, percentile_cont(0.5) within group ( order by bags ASC) as bag_cnt_med,
, substr(max(to_char(char_length(passenger), '0000') || passenger), 5) as name
from table1
group by orig
order by orig;
для небольших размеров группы, вы можете использовать array_agg()
SELECT
orig
, COUNT (*) AS flight_cnt
, COUNT (DISTINCT passenger) AS pass_cnt
, PERCENTILE_CONT (0.5) WITHIN GROUP (ORDER BY bags ASC) AS bag_cnt_med
, (ARRAY_AGG (passenger ORDER BY LENGTH (passenger) DESC))[1] AS pass_max_len_name
FROM table1
GROUP BY orig
сказав Так, в то время как это более короткий синтаксис, a first_value()
оконная функция на основе подхода может быть быстрее для больших наборов данных, поскольку накопление массива может стать дорогостоящим.
бот это не решит проблему, если у вас есть несколько имен wqith одинаковой длины:
t=# with p as (select distinct orig,passenger,length(trim(passenger)),max(length(trim(passenger))) over (partition by orig) from s127)
, o as ( select
orig
, count(*) flight_cnt
, count(distinct passenger) as pass_cnt
, percentile_cont(0.5) within group ( order by bags ASC) as bag_cnt_med
from s127
group by orig)
select distinct o.*,p.passenger from o join p on p.orig = o.orig where max=length;
orig | flight_cnt | pass_cnt | bag_cnt_med | passenger
---------+------------+----------+-------------+--------------
lax | 3 | 3 | 6 | ameera
sfo | 3 | 2 | 7 | david
(2 rows)
заполнение:
t=# create table s127(flight int,orig text,dest text, passenger text, bags int);
CREATE TABLE
Time: 52.678 ms
t=# copy s127 from stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 1111 | sfo | chi | david | 3
>> 1112 | sfo | dal | david | 7
1112 | sfo | dal | kim | 10
1113 | lax | san | ameera | 5
1114 | lax | lfr | tim | 6
1114 | lax | lfr | jake | 8 >> >> >> >>
>> \.
COPY 6