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


216

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

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

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

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

address_id最初のORDER BY式として追加するとエラーが表示されなくなりますが、並べ替えを追加したくありませんaddress_id。注文せずにできaddress_idますか?


注文条項がaddress_idではなくpurchase_atになっています。質問を明確にできますか?
Teja

私の注文は欲しいので購入しましたが、postgresもアドレスを要求します(エラーメッセージを参照)。
sl_bug 2012年


個人的には、DISTINCT ONをORDER BYに一致させる必要があるかどうかは非常に疑わしいと思います。それらを異なるものにするための正当な使用例はさまざまにあるからです。同様に感じる人のためにこれを変更しようとするpostgresql.uservoiceへの投稿があります。postgresql.uservoice.com/forums/21853-general/suggestions/...
セミコロン

まったく同じ問題が発生し、同じ制限に直面しています。現時点では、それをサブクエリに分割してから順序付けしていますが、汚い感じがします。
ガイ・パーク

回答:


207

ドキュメンテーションは言う:

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

公式ドキュメント

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

または、それぞれの最新の購入済み製品を含む完全な行を探し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

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


40
動作しますが、順序が間違っています。これが、order句のaddress_idを
削除

1
ドキュメントは明確です:選択した行が予測不可能になるため、それは不可能です
Mosty Mostacho 2012年

3
しかし、異なる住所に対して最新の購入を選択する別の方法があるのでしょうか?
sl_bug 2012年

1
purchases.purchased_atで注文する必要がある場合は、DISTINCT条件にを追加できますSELECT DISTINCT ON (purchases.purchased_at, address_id)。ただし、address_idが同じで、Purchased_atの値が異なる2つのレコードは、返されるセットに重複が生じます。クエリしているデータを認識していることを確認してください。
ブレンダンベンソン

23
質問の精神は明らかです。セマンティクスを選択する必要はありません。受け入れられ、最も投票された回答が問題の解決に役立たないのは悲しいことです。
nicooga

55

サブクエリでは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

3
しかし、これは1つのクエリよりも遅くなりますよね?
sl_bug 2012年

2
非常にわずかです。元のselectでの購入品*があるので、これは製品コードではないと思いますか?
hkf 2012年

8
postgresの新しいバージョンでは、サブクエリにエイリアスを付ける必要があることを追加します。例:SELECT * FROM(SELECT DISTINCT ON(address_id)purchases.address_id、purchases。* FROM "purchases" WHERE "purchases"。 "product_id" = 1 ORDER BY address_id DESC)AS tmp ORDER BY tmp.purchased_at DESC
aembke

これはaddress_id2回戻ります(必要なし)。多くのクライアントは、列名の重複に関する問題を抱えています。ORDER BY address_id DESC無意味で誤解を招く。このクエリでは役に立ちません。結果はaddress_id、最新の行ではなく、同じ行の各セットからの任意の選択purchased_atです。あいまいな質問はそれを明示的に要求しませんでしたが、それはほぼ間違いなくOPの意図です。つまり、このクエリは使用しないでください。代替案を説明付きで投稿しました。
Erwin Brandstetter 2017

私のために働いた。すばらしい答えです。
Matt West

46

サブクエリは、それを解決することができます:

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_atすることができNULL、考えますDESC NULLS LAST。ただし、使用する場合は、必ずインデックスを一致させてください。見る:

関連、より詳しい説明:


DISTINCT ON一致しないと使用できませんORDER BY。最初のクエリにはORDER BY address_id、サブクエリの内部が必要です。
アリストテレスPagaltzis 2017

4
@AristotlePagaltzis:でもできます。どこから入手したとしても、それは誤りです。同じクエリでDISTINCT ONなくORDER BYても使用できます。DISTINCT ONこの場合、句によって定義されたピアの各セットから任意の行を取得します。詳細を確認するか、マニュアルへのリンクについては、上記のリンクをクリックしてください。ORDER BY同じクエリ(同じSELECT)では、に同意することはできませんDISTINCT ON。私もそれを説明しました。
Erwin Brandstetter 2017

ええ、あなたは正しいです。ORDER BYドキュメント内の「使用されない限り予測不可能」という注記の意味が分からなかったのは、連続しない値のセットを処理できるように機能が実装されていることは意味がないためです。明示的な順序でそれを利用します。迷惑です。
アリストテレスPagaltzis 2017

@AristotlePagaltzis:これは、内部的に、Postgresが(少なくとも)2つの異なるアルゴリズムの1つを使用するためです。後者の場合、結果はDISTINCT ON(まだ)式でソートされません。
Erwin Brandstetter 2017

2
ありがとうございました。あなたの答えは常に非常に明確で役に立ちます!
Andrey Deineko

10

ウィンドウ関数は、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)

7
誰かがクエリを説明してくれるといいですね。
Gajus

@Gajus:簡単な説明:機能せず、distinctのみを返しますaddress_id。ただし、原則機能します。関連する例:stackoverflow.com/a/22064571/939860またはstackoverflow.com/a/11533808/939860。しかし、当面の問題に対するより短いおよび/またはより速いクエリがあります。
Erwin Brandstetter 2017

5

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

2
はい、またはもっと簡単に、私は使用することができました:query.distinct(foo).from_self().order(bar)
Laurent Meyer

@LaurentMeyerどういう意味Purchases.queryですか?
reubano 2018年

はい、私はPurchases.queryを意味しました
Laurent Meyer

-2

group by句を使用してこれを行うこともできます

   SELECT purchases.address_id, purchases.* FROM "purchases"
    WHERE "purchases"."product_id" = 1 GROUP BY address_id,
purchases.purchased_at ORDER purchases.purchased_at DESC

これは不正解です(ただしpurchases、2つの列address_idとしかない場合を除くpurchased_at)。のためGROUP BY、集計関数を使用してグループ化に使用されない各列の値を取得する必要があるため、醜く非効率的な体操を行わない限り、それらの値はすべてグループの異なる行から取得されます。これは、ではなくウィンドウ関数を使用することによってのみ修正できますGROUP BY
アリストテレスPagaltzis 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.