Farklı ORDER BY ile PostgreSQL DISTINCT ON


216

Bu sorguyu çalıştırmak istiyorum:

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

Ama bu hatayı alıyorum:

PG :: Hata: HATA: SELECT DISTINCT ON ifadeleri, ilk ORDER BY ifadeleriyle eşleşmelidir

address_idİlk ORDER BYifade olarak eklemek hatayı susturur, ancak gerçekten sıralama eklemek istemiyorum address_id. Sipariş vermeden yapmak mümkün mü address_id?


Sipariş şartınız address_id değil satın aldı. Sorunuzu netleştirebilir misiniz?
Teja

sipariş istiyorum çünkü satın almak var, ama postgres ayrıca adres sorar (hata iletisine bakın).
sl_bug


Şahsen, DISTINCT'in SİPARİŞ İLE eşleşmesini istemenin çok tartışmalı olduğunu düşünüyorum, çünkü bunların farklı olması için çeşitli meşru kullanım durumları var. Benzer hissedenler için postgresql.uservoice'de bunu değiştirmeye çalışan bir yazı var. postgresql.uservoice.com/forums/21853-general/suggestions/…
noktalı virgül

tam olarak aynı konuyu ele aldı ve aynı sınırlama ile karşı karşıya kaldı. Şu anda bir alt sorgu içine kırık ve daha sonra sipariş var, ama kirli hissediyor.
Guy Park

Yanıtlar:


208

Belgeler diyor:

DISTINCT ON (ifade [, ...]), verilen ifadelerin eşit olduğu değerlendirilen her satır kümesinin yalnızca ilk satırını tutar. [...] İstenen satırın önce görünmesini sağlamak için ORDER BY kullanılmadığı sürece her kümenin "ilk satırı" nın önceden kestirilemeyeceğini unutmayın. [...] DISTINCT ON ifadeleri, en sol ORDER BY ifadesiyle eşleşmelidir.

Resmi belgeler

Yani address_idsiparişi ile eklemeniz gerekecek .

Alternatif olarak, her biri için en son satın alınan ürünü içeren tam satırı arıyorsanız address_idve bu sonuca göre sıralanmışsa purchased_at, aşağıdaki yaklaşımlarla çözülebilecek en büyük N grubu problemini çözmeye çalışıyorsunuz:

Çoğu DBMS'de çalışması gereken genel çözüm:

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'nin cevabına dayanan daha PostgreSQL odaklı bir çözüm:

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

Burada açıklığa kavuşturulmuş, genişletilmiş ve çözülmüş sorun: Bir sütun tarafından sıralanan ve diğerinde farklı olan satırları seçme


40
Çalışıyor, ancak yanlış sipariş veriyor. Bu yüzden sipariş maddesinde address_id'den kurtulmak istiyorum
sl_bug

1
Belgeler açık: Seçili satır tahmin edilemez olacağından
yapamazsınız

3
Ancak, semt adresleri için en son satın alımları seçmenin başka bir yolu olabilir mi?
sl_bug

1
Eğer purchases.purchased_at tarafından sipariş gerekiyorsa, size DISTINCT koşullara purchased_at ekleyebilirsiniz: SELECT DISTINCT ON (purchases.purchased_at, address_id). Ancak, aynı adres_kimli ancak farklı satın alınmış_at değerlerine sahip iki kayıt, döndürülen kümede yinelenmelerle sonuçlanır. Sorguladığınız verilerin farkında olduğunuzdan emin olun.
Brendan Benson

23
Sorunun ruhu açıktır. Anlambilimi seçmeye gerek yok. Kabul edilen ve en çok oy verilen cevabın sorunu çözmenize yardımcı olmaması üzücü.
nicooga

55

Bir alt sorguda adres_kimliği ile sipariş verebilir, ardından dış sorguda istediğiniz şeye göre sipariş verebilirsiniz.

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
Ama bu sadece bir sorgudan daha yavaş olacak, değil mi?
sl_bug

2
Çok az evet. Orijinalinizde bir satın alma işleminiz olduğundan. * select, Bunun üretim kodu olduğunu düşünmüyorum?
hkf

8
Bunu yeni postgres sürümleri için alt sorguyu diğer adı olarak eklemeniz gerektiğini ekliyorum. Örneğin: SELECT * FROM (SELECT DISTINCT ON (adres_kimliği) alımları.adresi_kimliği, alımlar. * FROM "alımları" NEREDEN "satın alır". "Product_id" = 1 ORDER BY adres_kimliği DESC) tmp ORDER BY tmp.purchased_at DESC
aembke

Bu address_idiki kez (gerek olmadan) dönecekti . Birçok istemcinin yinelenen sütun adlarıyla ilgili sorunları vardır. ORDER BY address_id DESCanlamsız ve yanıltıcıdır. Bu sorguda yararlı bir şey yapmaz. Sonuç, her satır kümesinden, address_iden son satır değil, aynı satırdan rastgele bir seçimdir purchased_at. Belirsiz soru bunu açıkça sormadı, ama bu kesinlikle OP'nin niyeti. Kısacası: bu sorguyu kullanmayın . Alternatiflerle açıklama yaptım.
Erwin Brandstetter

Benim için çalıştı. Mükemmel cevap.
Matt West

46

Bir alt sorgu bunu çözebilir:

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

Baştaki ifadelerin içindeki ORDER BYsütunları kabul etmesi gerekir DISTINCT ON, böylece aynı sütunları farklı şekilde sıralayamazsınız SELECT.

ORDER BYAlt sorguda yalnızca her kümeden belirli bir satır seçmek istiyorsanız ek kullanın :

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;

Eğer purchased_atolabilir NULL, düşünün DESC NULLS LAST. Ancak, kullanmak istiyorsanız dizininizi eşleştirdiğinizden emin olun. Görmek:

İlgili, daha fazla açıklama ile:


DISTINCT ONEşleştirme olmadan kullanamazsınız ORDER BY. İlk sorgu ORDER BY address_idalt sorgunun içini gerektirir .
Aristotle Pagaltzis

4
@AristotlePagaltzis: Ama yapabilirsin . Nereden alırsanız alın, bu yanlış. Aynı sorguda DISTINCT ONolmadan kullanabilirsiniz ORDER BY. DISTINCT ONBu durumda , madde tarafından tanımlanan her bir eş grubundan rastgele bir satır alırsınız . Deneyin veya kılavuzun ayrıntıları ve bağlantıları için yukarıdaki bağlantıları izleyin. ORDER BYaynı sorguda (aynı SELECT) aynı fikirde değil DISTINCT ON. Ben de açıkladım.
Erwin Brandstetter

Ha, haklısın. ORDER BYDokümanlarda “kullanılmadıkça öngörülemeyen ” notun imalarına kör oldum çünkü bu özelliğin ardışık olmayan değer kümeleriyle başa çıkabilmek için uygulandığını anlamıyorum çünkü yine de Bunu açık bir sıralamayla kullanın. Can sıkıcı.
Aristotle Pagaltzis

@AristotlePagaltzis: Bunun nedeni, Postgres'in dahili olarak (en azından) iki farklı algoritmadan birini kullanmasıdır: ya sıralı bir listeden geçme ya da karma değerlerle çalışma - hangisi daha hızlı olacağa benziyor. Sonraki durumda sonuç DISTINCT ONifadelere göre sıralanmaz (henüz).
Erwin Brandstetter

2
Teşekkür ederim. Cevaplarınız her zaman berrak ve yardımcı olur!
Andrey Deineko

10

Pencere işlevi bunu bir geçişte çözebilir:

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
Birisi sorguyu açıklasa iyi olur.
17'de Gajus

@Gajus: Kısa açıklama: işe yaramıyor, sadece farklı geliyor address_id. Bu prensip işe yarayabilir. İlgili örnekler: stackoverflow.com/a/22064571/939860 veya stackoverflow.com/a/11533808/939860 . Ancak mevcut sorun için daha kısa ve / veya daha hızlı sorgular vardır.
Erwin Brandstetter

5

Flask-SQLAlchemy kullanan herkes için bu benim için çalıştı

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
Evet, hatta daha da kolay kullanabildim:query.distinct(foo).from_self().order(bar)
Laurent Meyer

@LaurentMeyer demek istediniz Purchases.query?
reubano

Evet, Purchases.query demek
Laurent Meyer

-2

Bunu ayrıca group by cümlesi kullanarak da yapabilirsiniz

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

Bu yanlıştır ( purchasesyalnızca iki sütuna sahip olmadıkça address_idve purchased_at). Bu nedenle, GROUP BYgruplama için kullanılmayan her bir sütunun değerini elde etmek için bir toplama işlevi kullanmanız gerekecektir, böylece çirkin ve verimsiz jimnastik yapmazsanız değerlerin tümü grubun farklı satırlarından gelecektir. Bu, yalnızca pencere işlevleri kullanılarak düzeltilebilir GROUP BY.
Aristotle Pagaltzis
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.