Jak wybrać unikalne rekordy przez SQL

89

Kiedy wykonuję polecenie „SELECT * FROM table”, otrzymałem wyniki takie jak poniżej:

1 item1 data1
2 item1 data2
3 item2 data3
4 item3 data4

Jak widać, istnieją rekordy dup z kolumny 2 (pozycja 1 jest kopiowana). Jak więc mógłbym uzyskać taki wynik:

1 item1 data1
2 item2 data3
3 item3 data4

Tylko jeden rekord jest zwracany z duplikatu wraz z pozostałymi unikalnymi rekordami.

Yinan
źródło
Pozycja 1 nie jest technicznie zduplikowana. Jak pokazano, wiersze 1 i 2 to unikalne obserwacje. A co by było, gdybyś chciał zachować wiersz 2, a nie 1?
Cybernetic

Odpowiedzi:

108

Dzięki distinctsłowu kluczowemu z jedną i wieloma nazwami kolumn otrzymujesz różne rekordy:

SELECT DISTINCT column 1, column 2, ...
FROM table_name;
mjallday
źródło
15
Czy to możliwe, że odpowiedź jest rzeczywiście nieprawidłowa? DISTINCT jest stosowany do wszystkich wybranych kolumn (przynajmniej w DB2), co nadal zwraca zduplikowane wartości w poszczególnych kolumnach.
Konstantin
26

Jeśli chcesz tylko usunąć duplikaty, użyj DISTINCT. GROUP BYnależy używać do stosowania operatorów agregujących do każdej grupy

GROUP BY v DISTINCT

rahul
źródło
11

To zależy od tego, który wiersz chcesz zwrócić za każdy unikalny przedmiot. Twoje dane wydają się wskazywać minimalną wartość danych, więc w tym przypadku dla SQL Server.

SELECT item, min(data)
FROM  table
GROUP BY item
Dave Barker
źródło
11

Istnieją 4 metody, których możesz użyć:

  1. ODRĘBNY
  2. GRUPUJ WEDŁUG
  3. Podzapytanie
  4. Wspólne wyrażenie tabelowe (CTE) z ROW_NUMBER ()

Rozważ następujący przykład TABLEz danymi testowymi:

/** Create test table */
CREATE TEMPORARY TABLE dupes(word text, num int, id int);

/** Add test data with duplicates */
INSERT INTO dupes(word, num, id)
VALUES ('aaa', 100, 1)
      ,('bbb', 200, 2)
      ,('ccc', 300, 3)
      ,('bbb', 400, 4)
      ,('bbb', 200, 5)     -- duplicate
      ,('ccc', 300, 6)     -- duplicate
      ,('ddd', 400, 7)
      ,('bbb', 400, 8)     -- duplicate
      ,('aaa', 100, 9)     -- duplicate
      ,('ccc', 300, 10);   -- duplicate

Opcja 1: SELECT DISTINCT

Jest to najprostszy i najprostszy, ale także najbardziej ograniczony sposób:

SELECT DISTINCT word, num 
FROM    dupes
ORDER BY word, num;

/*
word|num|
----|---|
aaa |100|
bbb |200|
bbb |400|
ccc |300|
ddd |400|
*/

Opcja 2: GROUP BY

Grupowanie pozwala na dodawanie zagregowane dane, podobnie jak min(id), max(id), count(*)itp:

SELECT  word, num, min(id), max(id), count(*)
FROM    dupes
GROUP BY word, num
ORDER BY word, num;

/*
word|num|min|max|count|
----|---|---|---|-----|
aaa |100|  1|  9|    2|
bbb |200|  2|  5|    2|
bbb |400|  4|  8|    2|
ccc |300|  3| 10|    3|
ddd |400|  7|  7|    1|
*/

Opcja 3: podzapytanie

Korzystając z podzapytania, możesz najpierw zidentyfikować zduplikowane wiersze do zignorowania, a następnie odfiltrować je w zewnętrznym zapytaniu za pomocą WHERE NOT IN (subquery)konstrukcji:

/** Find the higher id values of duplicates, distinct only added for clarity */
    SELECT  distinct d2.id
    FROM    dupes d1
        INNER JOIN dupes d2 ON d2.word=d1.word AND d2.num=d1.num
    WHERE d2.id > d1.id

/*
id|
--|
 5|
 6|
 8|
 9|
10|
*/

/** Use the previous query in a subquery to exclude the dupliates with higher id values */
SELECT  *
FROM    dupes
WHERE   id NOT IN (
    SELECT  d2.id
    FROM    dupes d1
        INNER JOIN dupes d2 ON d2.word=d1.word AND d2.num=d1.num
    WHERE d2.id > d1.id
)
ORDER BY word, num;

/*
word|num|id|
----|---|--|
aaa |100| 1|
bbb |200| 2|
bbb |400| 4|
ccc |300| 3|
ddd |400| 7|
*/

Opcja 4: wspólne wyrażenie tabeli z ROW_NUMBER ()

W Common Table Expression (CTE) wybierz ROW_NUMBER (), podzielony na partycje według kolumny grupy i uporządkowany w żądanej kolejności. Następnie WYBIERZ tylko te rekordy, które mają ROW_NUMBER() = 1:

WITH CTE AS (
    SELECT  *
           ,row_number() OVER(PARTITION BY word, num ORDER BY id) AS row_num
    FROM    dupes
)
SELECT  word, num, id 
FROM    cte
WHERE   row_num = 1
ORDER BY word, num;

/*
word|num|id|
----|---|--|
aaa |100| 1|
bbb |200| 2|
bbb |400| 4|
ccc |300| 3|
ddd |400| 7|
*/
isapir
źródło
6

po prostu użyj sprzężenia wewnętrznego, ponieważ grupowanie według nie będzie działać z wieloma kolumnami, które nie są zawarte w żadnej funkcji agregującej.

SELECT a.*
FROM yourtable a
INNER JOIN 
  (SELECT yourcolumn,
    MIN(id) as id
  FROM yourtable 
  GROUP BY yourcolumn
) AS b
  ON a.yourcolumn= b.yourcolumn
  AND a.id = b.id;
Ankit Kashyap
źródło
To jest odpowiedź na inne pytanie, prawdopodobnie takie, które powinno być oznaczone tagiem best-n-per-group
a_horse_with_no_name
To i rozwiązanie Dave'a Bakera są poprawnymi rozwiązaniami dla pytania SO. Zaletą tego rozwiązania jest to, że pozwala ono na wybranie wierszy zawierających tylko kilka określonych odrębnych kolumn i należy zdefiniować jedną kolumnę MIN (id) AS id, aby wybrać tylko jedną z wielu określonych kolumn.
giordano
1

Uważam, że jeśli z jakiegokolwiek powodu nie mogę używać DISTINCT, to GROUP BY będzie działać.

John Hamelink
źródło
1

Aby uzyskać wszystkie kolumny w swoim wyniku, musisz umieścić coś jako:

SELECT distinct a, Table.* FROM Table

zostanie on umieszczony jako pierwszej kolumny, a reszta będzie wszystkie kolumny w tej samej kolejności co definicji. Oznacza to, że kolumna a zostanie powtórzona.

htafoya
źródło
1
Jesteś tego pewien? Wypróbowałem to na w3schools i zwróciło to samo, co SELECT *, z wyjątkiem pierwszej kolumny
Freakishly
@ Niesamowite tak i dokładnie to mówi, że zrobi to w mojej odpowiedzi: /
htafoya
To nie zadziała, nie możesz wybrać * po takim
znaku
@Mohsinkhan cóż, zapomniałem umieścić, że musisz wpisać nazwę tabeli. Jakoś, kiedy to napisałem, zadziałało, ale właśnie przetestowałem teraz i nie udało się bez nazwy tabeli przed *
htafoya
2
To jest dokładnie to samo, coselect distinct * from ...
a_horse_with_no_name
-4

Select Eff_st from (select EFF_ST, ROW_NUMBER () over (PARTITION BY eff_st) XYZ - from ABC.CODE_DIM

) gdzie XYZ = 1 zamówienie przez EFF_ST pobierz tylko pierwsze 5 wierszy

Shailendra Singhai
źródło