私の質問のフィドルは 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
を省略できないと思いますか?
クエリを書き換える必要がありますか?
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コンストラクターの方が高速であると思います。見る:
ここに代替案がありますが、それはあなたがすでに持っているものより優れているわけではありません:
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;