Je veux exécuter cette requête:
SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY purchases.purchased_at DESC
Mais je reçois cette erreur:
PG :: Erreur: ERREUR: les expressions SELECT DISTINCT ON doivent correspondre aux expressions ORDER BY initiales
Ajouter address_id
en tant que première expression ORDER BY
réduit au silence l'erreur, mais je ne souhaite vraiment pas ajouter de tri sur address_id
. Est-il possible de faire sans commander par address_id
?
La documentation dit:
DISTINCT ON (expression [ ...]) conserve uniquement la première ligne de chaque ensemble de lignes où les expressions données sont considérées comme égales. [...] Notez que la "première ligne" de chaque ensemble est imprévisible sauf si ORDER BY est utilisé pour s'assurer que la ligne souhaitée apparaît en premier. [...] Les expressions DISTINCT ON doivent correspondre aux expressions ORDER BY les plus à gauche.
Vous devrez donc ajouter le address_id
à la commande par.
Alternativement, si vous recherchez la ligne complète contenant le dernier produit acheté pour chaque address_id
et que le résultat est trié par purchased_at
, vous essayez de résoudre le plus grand problème de N par groupe qui puisse être résolus par les approches suivantes:
La solution générale qui devrait fonctionner dans la plupart des SGBD:
SELECT t1.* FROM purchases t1
JOIN (
SELECT address_id, max(purchased_at) max_purchased_at
FROM purchases
WHERE product_id = 1
GROUP BY address_id
) t2
ON t1.address_id = t2.address_id AND t1.purchased_at = t2.max_purchased_at
ORDER BY t1.purchased_at DESC
Une solution plus orientée PostgreSQL basée sur la réponse de @ hkf:
SELECT * FROM (
SELECT DISTINCT ON (address_id) *
FROM purchases
WHERE product_id = 1
ORDER BY address_id, purchased_at DESC
) t
ORDER BY purchased_at DESC
Problème clarifié, étendu et résolu ici: Sélection de lignes ordonnées par une colonne et distinctes sur une autre
Vous pouvez commander par adresse_id dans une sous-requête, puis par ce que vous voulez dans une requête externe.
SELECT * FROM
(SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM "purchases"
WHERE "purchases"."product_id" = 1 ORDER BY address_id DESC )
ORDER BY purchased_at DESC
Une sous-requête peut le résoudre:
SELECT *
FROM (
SELECT DISTINCT ON (address_id) *
FROM purchases
WHERE product_id = 1
) p
ORDER BY purchased_at DESC;
Les expressions principales dans ORDER BY
doivent correspondre aux colonnes dans DISTINCT ON
, de sorte que vous ne pouvez pas classer par colonnes différentes dans la même SELECT
.
Utilisez uniquement un ORDER BY
supplémentaire dans la sous-requête si vous souhaitez sélectionner une ligne particulière dans chaque jeu:
SELECT *
FROM (
SELECT DISTINCT ON (address_id) *
FROM purchases
WHERE product_id = 1
ORDER BY address_id, purchased_at DESC -- get "latest" row per address_id
) p
ORDER BY purchased_at DESC;
Si purchased_at
peut être NULL
, considérons DESC NULLS LAST
.
En relation, avec plus d'explications:
La fonction window peut résoudre ce problème en un seul passage:
SELECT DISTINCT ON (address_id)
LAST_VALUE(purchases.address_id) OVER wnd AS address_id
FROM "purchases"
WHERE "purchases"."product_id" = 1
WINDOW wnd AS (
PARTITION BY address_id ORDER BY purchases.purchased_at DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
Pour ceux qui utilisent Flask-SQLAlchemy, cela a fonctionné pour moi
from app import db
from app.models import Purchases
from sqlalchemy.orm import aliased
from sqlalchemy import desc
stmt = Purchases.query.distinct(Purchases.address_id).subquery('purchases')
alias = aliased(Purchases, stmt)
distinct = db.session.query(alias)
distinct.order_by(desc(alias.purchased_at))
SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY address_id, purchases.purchased_at DESC
ORDER BY adresse_id, achats.purchased_at DESC
address_id doit être ajouté dans l'ordre pour pour la fonction DISTINCT ON ()