다른 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 :: 오류 : 오류 : SELECT DISTINCT ON 표현식은 초기 ORDER BY 표현식과 일치해야합니다.

address_id첫 번째 ORDER BY표현식으로 추가 하면 오류가 발생하지 않지만 실제로 정렬을 추가하고 싶지 않습니다 address_id. 에 의해 주문하지 않고 할 수 address_id있습니까?


귀하의 주문 조항이 address_id가 아닌 구입되었습니다. 질문을 명확히 할 수 있습니까?
Teja

주문이 필요하기 때문에 내 주문을 구매했지만 postgres도 주소를 묻습니다 (오류 메시지 참조).
sl_bug


개인적으로 ORDER BY와 일치하기 위해 DISTINCT ON을 요구하는 것은 매우 합리적이라고 생각합니다. 다양한 합법적 인 사용 사례가 있기 때문입니다. postgresql.uservoice에 비슷한 느낌이 드는 사람들을 위해 이것을 변경하려는 게시물이 있습니다. postgresql.uservoice.com/forums/21853-general/suggestions/…
세미콜론

똑같은 문제가 있고 같은 절정에 직면했습니다. 현재 나는 그것을 하위 쿼리로 나누고 주문했지만 더러운 느낌이 든다.
Guy Park

답변:


208

설명서에 따르면 :

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를 제거하고 싶습니다
sl_bug

1
문서가 명확합니다 : 선택한 행을 예측할 수 없기 때문에 불가능합니다
Mosty Mostacho

3
그러나 다른 주소에 대한 최신 구매를 선택하는 다른 방법이 있습니까?
sl_bug

1
purchases.purchased_at로 주문해야하는 경우 DISTINCT 조건에 purchase_at를 추가 할 수 있습니다 SELECT DISTINCT ON (purchases.purchased_at, address_id). 그러나 address_id는 같지만 purchase_at 값이 다른 두 개의 레코드는 리턴 된 세트에서 중복됩니다. 쿼리하는 데이터를 인식하고 있는지 확인하십시오.
Brendan Benson

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
그러나 이것은 단지 하나의 쿼리보다 느릴 것입니다.
sl_bug

2
아주 그렇습니다. 오리지널 구매가 있었지만 select이것이 생산 코드라고 생각하지 않습니까?
hkf

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) tmp ORDER BY tmp.purchased_at DESC
ambke

이것은 address_id(필요없이) 두 번 반환 됩니다. 많은 클라이언트가 중복 열 이름에 문제가 있습니다. ORDER BY address_id DESC무의미하고 오해의 소지가 있습니다. 이 쿼리에서는 유용하지 않습니다. 결과는 address_id최신 행이 아닌 동일한 행을 가진 각 행 세트에서 임의로 선택됩니다 purchased_at. 모호한 질문은 명시 적으로 요구하지 않았지만 OP의 의도는 거의 확실합니다. 한마디로 : 이 쿼리를 사용하지 마십시오 . 설명과 함께 대안을 게시했습니다.
Erwin Brandstetter

나를 위해 일했다. 좋은 대답입니다.
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

4
@AristotlePagaltzis :하지만 할 수 있습니다 . 당신이 그것을 어디에서 얻었 든, 그것은 틀 렸습니다. 동일한 쿼리 DISTINCT ON없이 사용할 수 있습니다 ORDER BY. DISTINCT ON이 경우 절에서 정의한 각 피어 집합에서 임의의 행을 얻습니다 . 시도하거나 위의 링크를 따라 자세한 내용과 설명서 링크를 확인하십시오. ORDER BY동일한 쿼리에서 (동일한 SELECT) 동의하지 않을 수 있습니다 DISTINCT ON. 나도 그것을 설명했다.
Erwin Brandstetter

응, 네 말이 맞아 ORDER BY이 기능은 비 연속적인 값 집합을 처리 할 수 ​​있도록 구현되었다는 의미가 아니기 때문에 문서에 "예기치 않은 경우 사용하지 않음"메모 의 의미에 대해 눈을 멀게 했습니다. 명시적인 순서로 그것을 이용하십시오. 성가신.
아리스토텔레스 Pagaltzis

@AristotlePagaltzis : 내부적으로 Postgres는 (적어도) 두 가지 고유 한 알고리즘 중 하나를 사용하기 때문입니다 . 정렬 된 목록을 순회하거나 해시 값으로 작업하는 것이 더 빠릅니다. 후자의 경우 결과는 DISTINCT ON표현식 (아직) 으로 정렬되지 않습니다 .
Erwin Brandstetter

2
감사합니다. 귀하의 답변은 항상 명확하고 도움이됩니다!
Andrey Deineko

10

창 함수는 한 번에 해결 할 수 있습니다.

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

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

예, 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에만 두 개의 열이 address_idpurchased_at). 때문에 GROUP BY그룹화에 사용되지 않은 각 열의 값을 얻으려면 집계 함수를 사용해야하므로 추악하고 비효율적 인 체조를 거치지 않으면 값이 그룹의 다른 행에서 나옵니다. 이 기능은 창 기능을 사용하는 대신 창 기능을 사용해야 만 해결할 수 있습니다 GROUP BY.
아리스토텔레스 Pagaltzis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.