Birden fazla sütun üzerinde DISTINCT'i nasıl SELECT edebilirim (veya edebilir miyim)?

Birleştirilmiş 2 sütunun hepsinin farklı olduğu bir tablodan tüm satırları almam gerekiyor. Yani aynı gün aynı fiyata gerçekleşen başka bir satış olmayan tüm satışları istiyorum. Gün ve fiyat bazında benzersiz olan satışlar aktif duruma güncellenecek.

Düşünüyorum da:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Ama bundan daha ileri gitmek beynimi acıtıyor.

Çözüm
SELECT DISTINCT a,b,c FROM t

ile eşdeğerdir:

SELECT a,b,c FROM t GROUP BY a,b,c

Daha güçlü olduğu için GROUP BY sözdizimine alışmak iyi bir fikirdir.

Sorgunuz için ben olsam şöyle yapardım:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )
Yorumlar (3)

Şimdiye kadarki cevapları bir araya getirir, temizler ve geliştirirseniz, bu üstün sorguya ulaşırsınız:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

Bu da ikisinden de çok daha hızlı. Şu anda kabul edilen cevabın performansını 10 - 15 kat artırıyor (PostgreSQL 8.4 ve 9.1 üzerinde yaptığım testlerde).

Ancak bu hala optimal olmaktan uzaktır. Daha iyi performans için bir NOT EXISTS (anti-)semi-join kullanın. EXISTS` standart SQL'dir, uzun zamandır kullanılmaktadır (en azından PostgreSQL 7.2'den beri, bu soru sorulmadan çok önce) ve sunulan gereksinimlere mükemmel şekilde uymaktadır:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id  s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

dbfiddle here Old SQL Fiddle

Satırı tanımlamak için benzersiz anahtar

Tablo için birincil veya benzersiz bir anahtarınız yoksa (örnekte id), bu sorgunun amacı için ctid sistem sütunu ile değiştirebilirsiniz (ancak başka amaçlar için değil):

   AND    s1.ctid  s.ctid

Her tablonun bir birincil anahtarı olmalıdır. Eğer henüz yoksa bir tane ekleyin. Postgres 10+'da bir serial veya IDENTITY sütunu öneririm.

İlgili:

Bu nasıl daha hızlı?

EXISTS` anti-semi-join'deki alt sorgu, ilk dupe bulunur bulunmaz değerlendirmeyi durdurabilir (daha fazla aramanın bir anlamı yoktur). Az sayıda kopyası olan bir temel tablo için bu yalnızca biraz daha verimlidir. Çok sayıda kopya ile bu çok daha verimli hale gelir.

Boş güncellemeleri hariç tut

Halihazırda status = 'ACTIVE' olan satırlar için bu güncelleme hiçbir şeyi değiştirmez, ancak yine de tam maliyetle yeni bir satır sürümü ekler (küçük istisnalar geçerlidir). Normalde bunu istemezsiniz. Bunu önlemek ve daha da hızlı hale getirmek için yukarıda gösterildiği gibi başka bir WHERE koşulu ekleyin:

Eğer status NOT NULL olarak tanımlanmışsa, şu şekilde basitleştirebilirsiniz:

AND status  'ACTIVE';

NULL işlemede ince fark

Bu sorgu (Joel tarafından şu anda kabul edilen yanıtın aksine) NULL değerleri eşit olarak değerlendirmez. (saleprice, saledate)` için aşağıdaki iki satır "distinct" olarak nitelendirilebilir (insan gözüne aynı görünmesine rağmen):

(123, NULL)
(123, NULL)

Ayrıca, SQL standardına göre NULL değerler eşit olarak karşılaştırılmadığından, benzersiz bir dizinde ve hemen hemen her yerde geçer. Bkz:

Buna karşın, GROUP BY, DISTINCT veya DISTINCT ON () NULL değerleri eşit olarak ele alır. Ne elde etmek istediğinize bağlı olarak uygun bir sorgu stili kullanın. NULL karşılaştırmasını eşit yapmak için herhangi bir veya tüm karşılaştırmalar için = yerine IS NOT DISTINCT FROM ile bu daha hızlı sorguyu kullanmaya devam edebilirsiniz. Daha fazlası:

Karşılaştırılan tüm sütunlar NOT NULL olarak tanımlanmışsa, anlaşmazlığa yer yoktur.

Yorumlar (12)

Sorgunuzla ilgili sorun, bir GROUP BY cümlesi kullanırken (aslında distinct kullanarak yapıyorsunuz) yalnızca grupladığınız sütunları veya toplama işlevlerini kullanabilmenizdir. İd sütununu kullanamazsınız çünkü potansiyel olarak farklı değerler vardır. Sizin durumunuzda HAVING cümlesi nedeniyle her zaman yalnızca bir değer vardır, ancak çoğu RDBMS bunu fark edecek kadar akıllı değildir.

Ancak bu işe yaramalıdır (ve birleştirme gerektirmez):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

MIN yerine MAX veya AVG de kullanabilirsiniz, sadece tek bir eşleşen satır varsa sütunun değerini döndüren bir fonksiyon kullanmak önemlidir.

Yorumlar (0)