【问题标题】:Use something like TOP with GROUP BY将 TOP 与 GROUP BY 一起使用
【发布时间】:2017-10-11 14:19:47
【问题描述】:

如下表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

我需要为每个orig 组添加名称最长的passenger (length(passenger)) - 我该怎么做?

预期输出

+------+-------------+-----------+---------------+-------------------+
| orig |  flight_cnt |  pass_cnt |  bags_cnt_med | pass_max_len_name |
+------+-------------+-----------+---------------+-------------------+
| sfo  |           3 |         2 |             7 |  david            |
| lax  |           3 |         3 |             6 | ameera            |
+------+-------------+-----------+---------------+-------------------+

【问题讨论】:

  • 发布你想要的输出...问题不清楚
  • @mohan111 添加
  • 如果两个名字的长度相同?
  • 顺便说一句,这是length()char_length(),而不是len()

标签: sql postgresql aggregate greatest-n-per-group


【解决方案1】:

bot 如果你有几个长度相同的名字,它不会解决问题:

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

【讨论】:

  • 预期输出不同,更新问题与预期输出。
【解决方案2】:

您正在寻找类似于 Oracle 的 KEEP FIRST/LAST 的东西,您可以在其中根据聚合(姓名长度)获得一个值(乘客姓名)。据我所知PostgreSQL没有这个功能。

解决此问题的一种方法是一个技巧:结合长度和名称,获取最大值,然后提取名称:'0005david' > '0003kim' 等。

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;

【讨论】:

    【解决方案3】:

    一种方法使用窗口函数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;
    

    【讨论】:

      【解决方案4】:

      您可以通过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);
      

      join 子句中的USING 方便地只输出orig 的一个实例,因此您可以简单地在外部SELECT 中使用SELECT *

      如果passenger可以为NULL,则添加NULLS LAST很重要:

      从同一组中具有相同最大长度的多个乘客姓名中,您将获得任意选择 - 除非您将更多表达式添加到ORDER BY 作为决胜局。上面链接的答案中有详细说明。

      性能?

      通常,单次扫描更胜一筹,尤其是顺序扫描。

      上述查询使用两个扫描(可能是索引/仅索引扫描)。但是第二次扫描相对便宜,除非表太大而无法放入缓存(大多数情况下)。 Lukas suggested an alternative query with only a single SELECT加:

      , (ARRAY_AGG (passenger ORDER BY LENGTH (passenger) DESC))[1]  -- I'd add NULLS LAST
      

      这个想法很聪明,但是last time I testedarray_aggORDER BY 表现不佳。 (每组ORDER BY 的开销很大,而且数组处理也很昂贵。)

      使用自定义聚合函数first() like instructed in the Postgres Wiki here,同样的方法可能更便宜。或者,使用a version written in C, available on PGXN,速度更快。消除了数组处理的额外成本,但我们仍然需要每个组 ORDER BY可能会更快,仅适用于少数群体。然后你会添加:

       , first(passenger ORDER BY length(passenger) DESC NULLS LAST)
      

      GordonLukas 还提到了窗口函数first_value()。窗口函数在聚合函数之后应用。要在同一个 SELECT 中使用它,我们需要先聚合 passenger 以某种方式 - 捕获 22。Gordon 使用子查询解决了这个问题 - 另一个使用标准 Postgres 获得良好性能的候选者。

      first() 在没有子查询的情况下也可以这样做,并且应该更简单且更快一些。但对于大多数每组只有几行的情况,它仍然不会比单独的 DISTINCT ON 快。对于每组很多行,递归 CTE 技术通常更快。如果您有一个单独的表来保存所有相关的、唯一的 orig 值,那么还有更快的技术。详情:

      最佳解决方案取决于各种因素。布丁的证据在吃。要优化性能,您必须使用您的设置进行测试。上面的查询应该是最快的。

      【讨论】:

      • 嗯,这意味着两个表访问,当只有一个可能时......
      • @LukasEder:基本上你是对的。单次扫描会很可爱。但是上次我测试时,array_aggORDER BY 的表现并不那么好。带有first_value() 的子查询有更好的机会,但有更快的替代方案……我添加了一个讨论选项的章节。
      • 感谢您的洞察力。很有意思。 first() 可能是作为开箱即用功能添加到 PostgreSQL 的不错选择。可能与 Oracle 的 KEEP 子句同样有用。
      • @LukasEder:是的,first() 在标准 Postgres 中会很方便。这可能是天真的用户错误地假设行的自然顺序而忘记添加ORDER BY 的陷阱。
      • 在 Oracle 的 KEEP 子句中,ORDER BY 是一个语法要求。当然,您仍然可以使用 ORDER BY 1ORDER BY (SELECT 1) 之类的东西来实现随机排序,但我想这没关系
      【解决方案5】:

      对于小团体,您可以使用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
      

      话虽如此,虽然这是较短的语法,但 first_value() window function based approach 对于较大的数据集可能会更快,因为数组累积可能会变得昂贵。

      【讨论】:

        猜你喜欢
        • 2014-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-03
        • 1970-01-01
        • 2016-03-15
        • 1970-01-01
        • 2022-10-22
        相关资源
        最近更新 更多