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.
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
)
Ş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
db<>fiddle here Old SQL Fiddle
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:
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.
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';
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.
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.