2010-06-21 2 views
0

У меня (кажется, есть) очень простая проблема, но после поиска часов я не могу найти ничего полезного.Как выбрать только одну полную строку для каждой группы в запросе «group by»?

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

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

я могу легко найти дубликаты by doing:

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 к SQL и будет делать два выбирает для каждого из 181 государств, поэтому 363 выбирает. В моем случае я ищу способ иметь максимум 182 выбора.

ответ

0

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

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 
+0

Downvote - Я думаю, ты виноват в том, что стреляешь в ответ, чтобы быть первым. Очевидно, вы не тестировали этот код, так как он говорит «form» и «innet join». Кроме того, тот факт, что он полагается на A, является уникальным ключом (но не первичным ключом, как вы сказали), делает его плохим общим решением. –

+0

ans обновлено сейчас ... спасибо за информацию –

4

Это касается меня, что вы хотите получить старое значение для полей 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

Хорошо, это может сработать. Кстати, я хотел сказать бессмысленно, что это не имеет значения внутри одной группы строк. Я использую эти данные только для того, чтобы иметь * подсказку * * о том, что дублируется и сколько »*. –

+1

Возможно, это неверно. Возвращенные B и C потенциально не связаны друг с другом - они могут поступать из разных записей. Вы не вернули произвольную запись, представляющую один из A, но фрагменты двух разных A. –

+0

И я сказал в ответе, что он это сделает. Сам плакат сказал, что ценности не имеют значения. – HLGEM

10

ROW_NUMBER функция КТР является способ сделать это.Например:

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

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

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

+1

Это одно, потому что вы можете изменить предложение bottom where, чтобы выбрать все записи, которые имеют доступную N-ю строку. т.е. для n = 3 'WHERE rn = 3' – scaryman

Смежные вопросы