web-dev-qa-db-ja.com

異なるORDER BYを使用したPostgreSQL DISTINCT ON

このクエリを実行したい:

SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY purchases.purchased_at DESC

しかし、私はこのエラーを受け取ります:

PG ::エラー:エラー:SELECT DISTINCT ON式は最初のORDER BY式と一致する必要があります

address_idを最初のORDER BY式として追加すると、エラーは沈黙しますが、address_idに並べ替えを追加したくありません。 address_idで注文せずに行うことは可能ですか?

169
sl_bug

ドキュメントは言う:

DISTINCT ON(expression [、...])は、指定された式が等しいと評価される各行セットの最初の行のみを保持します。 [...] ORDER BYを使用して目的の行が最初に表示されるようにしない限り、各セットの「最初の行」は予測できないことに注意してください。 [...] DISTINCT ON式は、左端のORDER BY式と一致する必要があります。

公式文書

したがって、address_idをorder byに追加する必要があります。

または、各address_idの最新の購入製品を含む行全体を探しており、その結果をpurchased_atでソートした場合、次の方法で解決できるグループごとの最大Nの問題を解決しようとしています。

ほとんどのDBMSで機能する一般的なソリューション:

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

@hkfの答えに基づいた、よりPostgreSQL指向のソリューション:

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

ここで問題を明確にし、拡張し、解決しました: ある列で順序付けられ、別の列で区別される行を選択する

161
Mosty Mostacho

サブクエリでaddress_idで並べ替えてから、外部クエリで必要なもので並べ替えることができます。

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
50
hkf

subqueryはそれを解決できます:

SELECT *
FROM  (
    SELECT DISTINCT ON (address_id) *
    FROM   purchases
    WHERE  product_id = 1
    ) p
ORDER  BY purchased_at DESC;

ORDER BYの先頭の式はDISTINCT ONの列と一致する必要があるため、同じSELECTの異なる列で並べ替えることはできません。

各セットから特定の行を選択する場合にのみ、サブクエリで追加のORDER BYを使用します。

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;

purchased_atNULLにできる場合は、DESC NULLS LASTを検討してください。
関連、詳細説明:

36

ウィンドウ関数は、1つのパスでそれを解決できます

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)
10
savenkov

Flask-SQLAlchemyを使用している人にとっては、これは私のために働いた

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))
4
reubano
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 address_id、purchases.purchased_at DESC

dISTINCT ON()関数のために、address_idを順番に追加する必要があります

0
REMITH