web-dev-qa-db-ja.com

集計関数と組み合わせてORDER BYおよびLIMITを適用する方法は?

私の質問のフィドルは https://dbfiddle.uk/?rdbms=postgres_10&fiddle=3cd9335fa07565960c1837aa65143685 にあります。

シンプルなテーブルレイアウトがあります。

class
person: belongs to a class

すべてのクラスを選択し、クラスごとに、所属する人物の最初の2人の識別子を降順の名前で並べ替えます。

私は次のクエリでこれを解決しました:

select     c.identifier, array_agg(p.identifier order by p.name desc) as persons
from       class as c
left join lateral (
             select   p.identifier, p.name
             from     person as p
             where    p.class_identifier = c.identifier
             order by p.name desc
             limit    2
           ) as p
on         true
group by   c.identifier
order by   c.identifier

注:SELECT句で相関サブクエリを使用することもできましたが、学習プロセスの一部としてそれを回避しようとしています。

ご覧のとおり、2つの場所にorder by p.name descを適用しています。

  • サブクエリ内
  • 集計関数内

それを回避する方法はありますか?私の熟練した列車:

  • まず、明らかに、サブクエリ内のorder byを削除することはできません。これは、上記の要件を満たさないクエリを生成するためです。

  • 第2に、サブクエリの行の順序は必ずしも集計関数で保持されないため、集計関数のorder byを省略できないと思いますか?

クエリを書き換える必要がありますか?

8
Jarius Hebzo

2つの場所で_order by p.name desc_を適用しています...これを回避する方法はありますか?

はい。横方向のサブクエリで ARRAYコンストラクター を直接集計します。

_SELECT c.identifier, p.persons
FROM   class c
CROSS  JOIN LATERAL (
   SELECT ARRAY (
      SELECT identifier
      FROM   person
      WHERE  class_identifier = c.identifier
      ORDER  BY name DESC
      LIMIT  2
      ) AS persons
   ) p
ORDER  BY c.identifier;
_

この方法では、外側のSELECTに_GROUP BY_も必要ありません。より短く、よりきれいに、より速く。

ARRAYコンストラクターは常に正確に1行を返すため、_LEFT JOIN_を単純な_CROSS JOIN_に置き換えました。 (コメントで指摘したように。)

db <> fiddle ここ。

関連:

サブクエリの行の順序

対処するには あなたのコメント

サブクエリの行の順序が外部クエリで保持されることが保証されていないことを学びました。

うーん、ダメ。 SQL標準は保証を提供していませんが、Postgresには限られた保証があります。 マニュアル:

この順序はデフォルトでは指定されていませんが、 セクション4.2.7 に示すように、集計呼び出し内に_ORDER BY_句を記述することで制御できます。あるいは、ソートされたサブクエリからの入力値の提供は通常は機能します。例えば:

_SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
_

外部クエリレベルに結合などの追加の処理が含まれている場合、集計が計算される前にサブクエリの出力が並べ替えられる可能性があるため、このアプローチは失敗する可能性があることに注意してください。

次のレベルで行うすべてが行の集計である場合、順序は確実に保証されます。はい、ARRAYコンストラクターにフィードするものもsubqueryです。それはポイントではありません。 array_agg()でも機能します。

_SELECT c.identifier, p.persons
FROM   class c
CROSS  JOIN LATERAL (
   SELECT array_agg(identifier) AS persons
   FROM  (
      SELECT identifier
      FROM   person
      WHERE  class_identifier = c.identifier
      ORDER  BY name DESC
      LIMIT  2
      ) sub
   ) p
ORDER  BY c.identifier;
_

ただし、ARRAYコンストラクターの方が高速であると思います。見る:

4

ここに代替案がありますが、それはあなたがすでに持っているものより優れているわけではありません:

with enumeration (class_identifier, identifier, name, n) as (
    select  p.class_identifier, p.identifier, p.name
         , row_number() over (partition by p.class_identifier 
                              order by p.name desc)
    from     person as p
)
select c.identifier, array_agg(e.identifier order by e.n) as persons
from class as c
left join  enumeration e
    on c.identifier = e.class_identifier
where e.n <= 2
group by   c.identifier
order by   c.identifier;
2
Lennart