Как выбрать только одну полную строку на группу в запросе «group by»? - PullRequest
1 голос
/ 21 июня 2010

У меня (кажется, что) очень простая проблема, но после поисков в течение нескольких часов я не могу найти ничего полезного.

Вот проблема:

В Microsoft SQL у меня есть таблица, в которой столбец A хранит некоторые данные. Эти данные могут содержать дубликаты (т. Е. Две или более строки будут иметь одинаковое значение для столбца A ).

Я легко могу найти дубликаты , выполнив :

select A, count(A) as CountDuplicates
from TableName
group by A having (count(A) > 1)

Теперь я хочу получить значения других столбцов, скажем, B и C . Конечно, эти значения B и C могут отличаться даже для строк, имеющих одинаковое значение A , но для меня это не имеет значения. Мне просто нужно любое значение B и любое C одно, первое, последнее или случайное.

Если бы у меня была маленькая таблица и один или два столбца для извлечения, я бы сделал что-то вроде:

select A, count(A) as CountDuplicates, (
    select top 1 child.B from TableName as child where child.A = base.A) as B
)
from TableName as base group by A having (count(A) > 1)

Проблема в том, что мне нужно получить гораздо больше строк, и таблица довольно большая, поэтому выбор нескольких дочерних элементов будет иметь высокую производительность.

Итак, есть ли менее уродливое решение для чистого SQL для этого?


Не уверен, что мой вопрос достаточно ясен, поэтому приведу пример на основе базы данных AdventureWorks . Допустим, я хочу перечислить доступные штаты, и для каждого штата получить его код, город (любой город) и адрес (любой адрес). Самый простой и самый неэффективный способ сделать это:

var q = from c in data.StateProvinces select new { c.StateProvinceCode, c.Addresses.First().City, c.Addresses.First().AddressLine1 };

в LINQ-to-SQL и сделает два выбора для каждого из 181 состояния, поэтому 363 выбора. В моем случае я ищу способ выбрать максимум 182.

Ответы [ 3 ]

11 голосов
/ 22 июня 2010

Функция ROW_NUMBER в CTE - способ сделать это. Например:

DECLARE @mytab TABLE (A INT, B INT, C INT)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 1, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 1, 2)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 2, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 3, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (2, 2, 2)
INSERT INTO @mytab ( A, B, C ) VALUES (3, 3, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (3, 3, 2)
INSERT INTO @mytab ( A, B, C ) VALUES (3, 3, 3)
;WITH numbered AS 
(
    SELECT *, rn=ROW_NUMBER() OVER (PARTITION BY A ORDER BY B, C)
        FROM @mytab AS m
)
SELECT *
    FROM numbered
    WHERE rn=1

Как я уже упоминал в своем комментарии к HLGEM и Филипу Келли, их простое использование агрегатной функции не обязательно возвращает одну "твердую" запись для каждой группы A; вместо этого он может возвращать значения столбцов из множества отдельных строк, все сшитые вместе, как если бы они были одной записью. Например, если это таблица PERSON, в которой PersonID представляет собой столбец «A», а отдельные записи о контактах (скажем, «Домой» и «Word»), вы можете вернуть свой город, но почтовый индекс его офиса это явно напрашивается на неприятности.

Использование ROW_NUMBER в сочетании с CTE здесь немного сложнее для начала, потому что синтаксис неудобен. Но это становится довольно распространенным паттерном, так что лучше узнать его.

В моем примере я определил CTE, который привязывает дополнительный столбец rn (обозначающий «номер строки») к таблице, которая сама группируется по столбцу A. A SELECT для этого результата, отфильтровывая только те, которые имеют номер строки 1 (т. Е. Первую найденную запись для этого значения A), возвращает «сплошную» запись для каждой группы A - в моем примере выше вы Обязательно получите рабочий адрес или Home, но не элементы обоих вместе.

5 голосов
/ 21 июня 2010

Меня беспокоит, что вам нужно любое старое значение для полей b и c.Если они бессмысленны, почему вы их возвращаете?

Если это действительно не имеет значения (и я, честно говоря, не могу представить себе случай, когда я бы этого хотел, но это то, что вы сказали) изначения для b и c даже не обязательно должны быть из одной и той же записи, группировка с использованием mon или max - это путь.Это сложнее, если вы хотите, чтобы значения для конкретной записи были для всех полей.

select A, count(A) as CountDuplicates, min(B) as B , min(C) as C
from TableName as base 
group by A 
having (count(A) > 1) 
0 голосов
/ 21 июня 2010

вы можете сделать что-то подобное, если у вас есть идентификатор в качестве первичного ключа в вашей таблице

select id,b,c from tablename 
inner join
(
select id, count(A) as CountDuplicates
from TableName as base group by A,id having (count(A) > 1) 
)d on tablename.id= d.id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...