Jak wybrać tylko pierwsze wiersze dla każdej unikalnej wartości kolumny

97

Powiedzmy, że mam tabelę adresów klientów:

CName           |   AddressLine
-------------------------------
John Smith      | 123 Nowheresville
Jane Doe        | 456 Evergreen Terrace
John Smith      | 999 Somewhereelse
Joe Bloggs      | 1 Second Ave

W tabeli jeden klient, taki jak John Smith, może mieć wiele adresów. Potrzebuję zapytania wybierającego dla tej tabeli, aby zwracał tylko pierwszy znaleziony wiersz, w którym znajdują się duplikaty w „CName”. W przypadku tej tabeli powinna zwrócić wszystkie wiersze z wyjątkiem trzeciego (lub pierwszego - każdy z tych dwóch adresów jest prawidłowy, ale można zwrócić tylko jeden). Czy istnieje słowo kluczowe, które mogę dodać do zapytania SELECT, aby filtrować na podstawie tego, czy serwer widział już wcześniej wartość kolumny?

nuit9
źródło

Odpowiedzi:

127

Bardzo prosta odpowiedź, jeśli powiesz, że nie obchodzi Cię, który adres jest używany.

SELECT
    CName, MIN(AddressLine)
FROM
    MyTable
GROUP BY
    CName

Jeśli chcesz, aby pierwsza była zgodna, powiedzmy, z kolumną „wstawioną”, to jest to inne zapytanie

SELECT
    M.CName, M.AddressLine,
FROM
    (
    SELECT
        CName, MIN(Inserted) AS First
    FROM
        MyTable
    GROUP BY
        CName
    ) foo
    JOIN
    MyTable M ON foo.CName = M.CName AND foo.First = M.Inserted
gbn
źródło
Chociaż może nie być przeznaczone do używania w ten sposób podczas wybierania 10 kolumn. Wydaje się również, że nie może zaakceptować kolumny typu bitowego.
nuit9
1
@ nuit9: oczywiście nie będzie działać z bitami i 10 kolumnami. Żaden z tych faktów nie jest przedmiotem twojego pytania. Użyłbyś drugiej techniki lub techniki Bena Thula. Odpowiedziałem na to, o co prosiłeś, ze wskazówkami, jak rozwiązać bardziej ogólnie.
gbn
Pierwsza część DZIAŁA z wieloma kolumnami, chociaż nie z kolumnami bitowymi. Przetestowałem to jednak w MS SQL Server 2016.
netfed
27

W SQL 2k5 + możesz zrobić coś takiego:

;with cte as (
  select CName, AddressLine,
  rank() over (partition by CName order by AddressLine) as [r]
  from MyTable
)
select CName, AddressLine
from cte
where [r] = 1
Ben Thul
źródło
6
Proszę wyjaśnić, co robi ranga, podział i [r]
Roberto
10

Możesz użyć, row_number()aby uzyskać numer wiersza wiersza. Używa overpolecenia - partition byklauzula określa, kiedy ponownie uruchomić numerowanie, a order bywybiera, na czym ma być uporządkowany numer wiersza. Nawet jeśli dodasz order byna końcu zapytania, zachowałoby to kolejność w overpoleceniu podczas numerowania.

select *
from mytable
where row_number() over(partition by Name order by AddressLine) = 1
Szczery
źródło
6
W postgresql funkcje okna nie są dozwolone w klauzuli WHERE
ekanna
3
Nie jest to dozwolone w przypadku MS-SQL.
Mixxiphoid
1
ROW_NUMBER()nie działa również w Whereklauzuli w Teradata
Pirate X
6

Możesz użyć takiej row_numer() over(partition by ...)składni:

select * from
(
select *
, ROW_NUMBER() OVER(PARTITION BY CName ORDER BY AddressLine) AS row
from myTable
) as a
where row = 1

Powoduje to utworzenie kolumny o nazwie row, która jest licznikiem, który zwiększa się za każdym razem, gdy widzi to samo CName, i indeksuje te wystąpienia według AddressLine. Narzucając where row = 1, można wybrać, CNamekto AddressLinejest pierwszy w kolejności alfabetycznej. Gdyby tak order bybyło desc, wybierałoby to, CNamektóre AddressLinejest ostatnie w kolejności alfabetycznej.

FatihAkici
źródło
1

To da ci jeden wiersz z każdego zduplikowanego wiersza. Podaje również kolumny typu bitowego i działa przynajmniej na serwerze MS Sql.

(select cname, address 
from (
  select cname,address, rn=row_number() over (partition by cname order by cname) 
  from customeraddresses  
) x 
where rn = 1) order by cname

Jeśli zamiast tego chcesz znaleźć wszystkie duplikaty, po prostu zmień rn = 1 na rn> 1. Mam nadzieję, że to pomoże

netfed
źródło