Используйте что-то вроде 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