Un violon pour ma question se trouve sur https://dbfiddle.uk/?rdbms=postgres_10&fiddle=3cd9335fa07565960c1837aa65143685 .
J'ai une disposition de table simple:
class
person: belongs to a class
Je veux sélectionner toutes les classes, et pour chaque classe, je veux que les deux premiers identifiants des personnes appartenant soient triés par nom décroissant.
J'ai résolu cela avec la requête suivante:
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
Remarque: j'aurais pu utiliser une sous-requête de corrélation dans la clause SELECT
, mais j'essaie d'éviter cela dans le cadre d'un processus d'apprentissage.
Comme vous pouvez le voir, j'applique order by p.name desc
à deux endroits:
Y a-t-il un moyen d'éviter cela? Mon train de réflexion:
Tout d'abord, je ne peux évidemment pas supprimer le order by
dans la sous-requête, car cela donnerait une requête qui ne répond pas à mes exigences, comme indiqué ci-dessus.
Deuxièmement, je pense que le order by
dans la fonction d'agrégation ne peut pas être omis, car l'ordre des lignes de la sous-requête n'est pas nécessairement conservé dans la fonction d'agrégation?
Dois-je réécrire la requête?
J'applique
order by p.name desc
À deux endroits ... Y a-t-il un moyen d'éviter cela?
Oui. Agréger avec un constructeur ARRAY dans la sous-requête latérale directement:
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;
Vous n'avez pas non plus besoin de GROUP BY
Dans le SELECT
extérieur de cette façon. Plus court, plus propre, plus rapide.
J'ai remplacé le LEFT JOIN
Par un simple CROSS JOIN
Car le constructeur ARRAY renvoie toujours exactement 1 ligne. (Comme vous l'avez souligné dans un commentaire.)
db <> violon ici.
En relation:
Pour adresser votre commentaire :
J'ai appris que l'ordre des lignes d'une sous-requête n'est jamais garanti d'être conservé dans la requête externe.
Et bien non. Bien que le standard SQL n'offre aucune garantie, il existe des garanties limitées dans Postgres. Le manuel:
Cet ordre n'est pas spécifié par défaut, mais peut être contrôlé en écrivant une clause
ORDER BY
Dans l'appel agrégé, comme indiqué dans Section 4.2.7 . Alternativement, la fourniture des valeurs d'entrée à partir d'une sous-requête triée fonctionne généralement. Par exemple:SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
Attention, cette approche peut échouer si le niveau de requête externe contient un traitement supplémentaire, tel qu'une jointure, car cela pourrait entraîner la réorganisation de la sortie de la sous-requête avant le calcul de l'agrégat.
Si tout ce que vous faites au niveau suivant est d'agréger des lignes, l'ordre est garanti positivement. Tout oui, ce que nous fournissons au constructeur ARRAY est aussi une sous-requête. Ce n'est pas le propos. Cela fonctionnerait également avec 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;
Mais je m'attends à ce que le constructeur ARRAY soit plus rapide pour le cas. Voir:
Voici une alternative, mais ce n'est pas mieux que ce que vous avez déjà:
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;