DISTINCT tylko dla jednej kolumny


155

Powiedzmy, że mam następujące zapytanie.

SELECT ID, Email, ProductName, ProductModel FROM Products

Jak mogę go zmodyfikować, aby nie zwracał zduplikowanych wiadomości e-mail?

Innymi słowy, jeśli kilka wierszy zawiera ten sam e-mail, chcę, aby wyniki obejmowały tylko jeden z tych wierszy (najlepiej ostatni). Powielanie w innych kolumnach powinno być dozwolone.

Klauzule lubią DISTINCTi GROUP BYwydają się działać na całych wierszach. Więc nie jestem pewien, jak do tego podejść.


2
Czy potrzebujesz użyć PARTITION czy dwóch instrukcji Select?
CarneyCode

A co powinno się pokazać, jeśli są powiedzmy 2 wiersze z tym samym adresem e-mail, ale inną nazwą produktu? (Korzystnie ostatni) nie jest jasne. Ostatni według jakiego zamówienia?
ypercubeᵀᴹ

@ypercube Jak stwierdzono w pytaniu, najlepiej ostatnie. Jednak to nie jest dla mnie krytyczne. Chcę tylko jednego z nich.
Jonathan Wood,

1
Możesz spojrzeć na następujące pytania: pytanie1 , pytanie2 lub pytanie3 .
Marian

Dlaczego nie możesz użyć: SELECT DISTINCT Email, ID, ProductName, ProductModel FROM Products?
Rick Henderson

Odpowiedzi:


186

Jeśli używasz SQL Server 2005 lub nowszego, użyj tego:

SELECT *
  FROM (
                SELECT  ID, 
                        Email, 
                        ProductName, 
                        ProductModel,
                        ROW_NUMBER() OVER(PARTITION BY Email ORDER BY ID DESC) rn
                    FROM Products
              ) a
WHERE rn = 1

EDYCJA: Przykład z użyciem klauzuli where:

SELECT *
  FROM (
                SELECT  ID, 
                        Email, 
                        ProductName, 
                        ProductModel,
                        ROW_NUMBER() OVER(PARTITION BY Email ORDER BY ID DESC) rn
                    FROM Products
                   WHERE ProductModel = 2
                     AND ProductName LIKE 'CYBER%'

              ) a
WHERE rn = 1

4
Muszę zbadać tę klauzulę PARTITION, nigdy wcześniej nie widziałem jej w akcji. Dzięki za przykład
LorenVS

@Cybernate One komplikacja: Moje wewnętrzne SELECTpotrzeby wymagają WHEREstanu. Myślę, że numery wierszy zostaną przypisane do wszystkich wierszy w tabeli. Ta składnia jest trochę poza mną. Czy jest jakaś szansa na aktualizację, która gwarantowałaby jeden wiersz z określonym e-mailem spełniającym WHEREwarunek?
Jonathan Wood,

1
Możesz dodać klauzulę where do wewnętrznego sql. Zaktualizuję post, gdy
uzyskam

1
Zaktualizowałem post o przykład przy użyciu klauzuli where.
Chandu

1
To działa poprawnie tylko wtedy, gdy nie ma JOIN w zapytaniu. Gdy tylko mam a JOIN, ROW_NUMBERzwraca znacznie wyższe wartości niż „1”.
Uwe Keim

10

Zakłada się, że SQL Server 2005+ i Twoja definicja „ostatniego” to maksymalne PK dla danego e-maila

WITH CTE AS
(
SELECT ID, 
       Email, 
       ProductName, 
       ProductModel, 
       ROW_NUMBER() OVER (PARTITION BY Email ORDER BY ID DESC) AS RowNumber 
FROM   Products
)
SELECT ID, 
       Email, 
       ProductName, 
       ProductModel
FROM CTE 
WHERE RowNumber = 1

6

Kiedy używasz, DISTINCTmyśl o tym jako o odrębnym wierszu, a nie kolumnie. Zwróci tylko wiersze, w których kolumny nie pasują dokładnie tak samo.

SELECT DISTINCT ID, Email, ProductName, ProductModel
FROM Products

----------------------
1 | something@something.com | ProductName1 | ProductModel1
2 | something@something.com | ProductName1 | ProductModel1

Zapytanie zwróci oba wiersze, ponieważ IDkolumna jest inna. Zakładam, że IDkolumna jest IDENTITYkolumną rosnącą, jeśli chcesz zwrócić ostatnią, to polecam coś takiego:

SELECT DISTINCT TOP 1 ID, Email, ProductName, ProductModel
FROM Products
ORDER BY ID DESC

TOP 1Powróci tylko pierwszy rekord, zamawiając go przez IDmalejącej powróci pierwszy wyniki z ostatniego rzędu. To da ci ostatni rekord.


2
Jak stwierdzono w pytaniu, widzę, że DISTINCT działa w całym rzędzie. Chcę zrobić to, co sugerujesz powyżej, ale za każdym razem, gdy e-mail jest powielany w wynikach (nie tylko raz).
Jonathan Wood

W takim przypadku poleciłbym wybrać odpowiedź @Cybernate. To powinno zrobić dokładnie to, czego potrzebujesz.
jon3laze

4

Możesz to zmienić za pomocą funkcji GROUP BY

SELECT ID, Email, ProductName, ProductModel FROM Products GROUP BY Email


16
Kolumna „Products.ID” jest nieprawidłowa na liście wyboru, ponieważ nie jest zawarta ani w funkcji agregującej, ani w klauzuli GROUP BY.
palota

2
To nie działa bez użycia czegoś takiego jak MAX (ID), MAX (ProductName), MAX (ProductModel) dla innych kolumn
avl_sweden

2
W postgres potrzebujesz tylko funkcji agregującej na kolumnie, która będzie używana w klauzuli group by, np SELECT id, max(email) AS email FROM tbl GROUP by email. W SQL Server WSZYSTKIE kolumny w SELECTklauzuli muszą być w funkcji agregującej. To mnie gryzie za każdym razem, gdy wracam.
Bruce Pierson

To się nigdy nie uda. To złe rozwiązanie
Dan AS

1

W przypadku programu Access możesz użyć zapytania SQL Select, które przedstawiam tutaj:

Na przykład masz tę tabelę:

KLIENT || NOMBRES || POCZTA

888 || T800 ARNOLD || t800.arnold@cyberdyne.com

123 || JOHN CONNOR || s.connor@skynet.com

125 || SARAH CONNOR ||s.connor@skynet.com

Musisz wybrać tylko odrębne wiadomości. Możesz to zrobić w ten sposób:

WYBÓR SQL:

SELECT MAX(p.CLIENTE) AS ID_CLIENTE
, (SELECT TOP 1 x.NOMBRES 
    FROM Rep_Pre_Ene_MUESTRA AS x 
    WHERE x.MAIL=p.MAIL 
     AND x.CLIENTE=(SELECT MAX(l.CLIENTE) FROM Rep_Pre_Ene_MUESTRA AS l WHERE x.MAIL=l.MAIL)) AS NOMBRE, 
p.MAIL
FROM Rep_Pre_Ene_MUESTRA AS p
GROUP BY p.MAIL;

Możesz użyć tego, aby wybrać maksymalny identyfikator, nazwę odpowiadającą temu maksymalnemu identyfikatorowi, możesz w ten sposób dodać dowolny inny atrybut. Następnie na końcu umieszczasz odrębną kolumnę do przefiltrowania i grupujesz ją tylko z ostatnią odrębną kolumną.

Zapewni to maksymalny identyfikator z odpowiednimi danymi, możesz użyć min lub dowolnej innej funkcji i zreplikować tę funkcję do zapytań podrzędnych.

Ta opcja zwróci:

KLIENT || NOMBRES || POCZTA

888 || T800 ARNOLD || t800.arnold@cyberdyne.com

125 || SARAH CONNOR ||s.connor@skynet.com

Pamiętaj, aby zindeksować wybrane kolumny, a odrębna kolumna nie może zawierać danych liczbowych zawierających wszystkie duże lub małe litery, w przeciwnym razie nie będzie działać. Działa to również z tylko jedną przesyłką poleconą. Miłego kodowania !!!


0

Powód DISTINCTiGROUP BY pracą nad całymi wierszami jest to, że zapytanie zwraca całe wiersze.

Aby pomóc Ci zrozumieć: Spróbuj ręcznie napisać, co zapytanie powinno zwrócić, a zobaczysz, że nie jest jednoznaczne, co umieścić w nieuduplikowanych kolumnach.

Jeśli dosłownie nie obchodzi Cię, co jest w innych kolumnach, nie zwracaj ich. Zwracanie losowego wiersza dla każdego adresu e-mail wydaje mi się trochę bezużyteczne.


@JohnFix Chcę zwrócić całe wiersze. Po prostu nie chcę, aby wiersze były zwracane, gdy wyniki zawierają już wiersz o tej samej wartości w kolumnie E-mail.
Jonathan Wood,

Jak więc powinien zdecydować, który z nich wrócić? Czy naprawdę potrzebujesz zapytania, które zwraca dowolny wiersz dla każdego e-maila. To naprawdę pachnie, jakbyś musiał ponownie przemyśleć problem, który próbujesz rozwiązać. Prawie za każdym razem, gdy ktoś mi zadaje to pytanie (a pojawia się dużo), okazuje się, że programista nie przemyślał konsekwencji tego zachowania w aplikacji.
JohnFx,

6
Naprawdę mam problem z podążaniem za twoją logiką. Jak stwierdzono w pytaniu, wolałbym ostatni (posortowany według ID). Tak, jeśli wybierze losowy wiersz, będzie w porządku. I tak, myślałem o tym.
Jonathan Wood,

0

Spróbuj tego

;With Tab AS (SELECT DISTINCT Email FROM  Products)
SELECT Email,ROW_NUMBER() OVER(ORDER BY Email ASC) AS  Id FROM Tab
ORDER BY Email ASC

-2

Spróbuj tego:

SELECT ID, Email, ProductName, ProductModel FROM Products WHERE ID IN (SELECT MAX(ID) FROM Products GROUP BY Email)

2
Dlaczego powinniśmy tego spróbować? Dlaczego jest to lepsze niż inne odpowiedzi zamieszczone tutaj w ciągu ostatnich 8 lat? Jeśli chcesz podzielić się lepszym sposobem rozwiązania problemu, musisz wyjaśnić, dlaczego go polecasz.
Dharman
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.