Как сгруппировать по одному столбцу и получить строку с минимальным значением другого столбца в T / SQL? - PullRequest
13 голосов
/ 31 июля 2009

Так что я знаю, что это довольно тупой вопрос, однако (как говорит довольно длинный заголовок) я хотел бы знать, как сделать следующее:

У меня есть такая таблица:

ID Foo Bar Blagh
----------------
1  10  20  30
2  10  5   1
3  20  50  40
4  20  75  12

Я хочу сгруппировать по Foo, затем вытащить строки с минимальным баром, т.е. я хочу следующее:

ID Foo Bar Blagh
----------------
2  10  5   1
3  20  50  40

Я не могу на всю жизнь выработать правильный SQL, чтобы получить это. Я хочу что-то вроде:

SELECT ID, Foo, Bar, Blagh
FROM Table
GROUP BY Foo
HAVING(MIN(Bar))

Однако это явно не работает, поскольку это совершенно неверный синтаксис HAVING и ID, Foo, Bar и Blagh не агрегированы.

Что я делаю не так?

Ответы [ 6 ]

11 голосов
/ 31 июля 2009

Этот - почти тот же вопрос, но на него есть ответы!

Вот я издеваюсь над вашим столом:

declare @Borg table (
    ID int,
    Foo int,
    Bar int,
    Blagh int
)
insert into @Borg values (1,10,20,30)
insert into @Borg values (2,10,5,1)
insert into @Borg values (3,20,50,70)
insert into @Borg values (4,20,75,12)

Затем вы можете выполнить анонимное внутреннее объединение, чтобы получить нужные данные.

select B.* from @Borg B inner join 
(
    select Foo,
        MIN(Bar) MinBar 
    from @Borg 
    group by Foo
) FO
on FO.Foo = B.Foo and FO.MinBar = B.Bar

РЕДАКТИРОВАТЬ Адам Робинсон услужливо указал, что «это решение может возвращать несколько строк, когда минимальное значение Bar дублировано, и устраняет любое значение foo, где bar равно null»

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

Если вам нужно захватить NULLs в поле, по которому вы агрегируете (в данном случае MIN), вы можете coalesce NULL с приемлемо высоким (или низким) значением (это хак) :

...
MIN(coalesce(Bar,1000000)) MinBar -- A suitably high value if you want to exclude this row, make it suitably low to include
...

Или вы можете пойти на UNION и прикрепить все такие значения к нижней части вашего набора результатов.

on FO.Foo = B.Foo and FO.MinBar = B.Bar
union select * from @Borg where Bar is NULL

Последний не будет группировать значения в @Borg с одинаковым значением Foo, потому что не знает, как выбирать между ними.

3 голосов
/ 31 июля 2009
select 
    ID, 
    Foo, 
    Bar, 
    Blagh
from Table
join (
    select 
    ID, 
    (row_number() over (order by foo, bar) - rank() over (order by foo)) as rowNumber
) t on t.ID = Table.ID and t.rowNumber = 0 

Это снова присоединяется к таблице, но на этот раз добавляется относительный номер строки для значения для bar, как если бы оно было отсортировано по возрастанию в пределах каждого значения foo. Фильтруя на rowNumber = 0, он выбирает только самые низкие значения для bar для каждого значения foo. Это также эффективно устраняет предложение group by, поскольку теперь вы получаете только одну строку на foo.

1 голос
/ 31 июля 2009

Другой вариант будет выглядеть примерно так:

DECLARE @TEST TABLE(    ID int,    Foo int,    Bar int,    Blagh int)
INSERT INTO @TEST VALUES (1,10,20,30)
INSERT INTO @TEST VALUES (2,10,5,1)
INSERT INTO @TEST VALUES (3,20,50,70)
INSERT INTO @TEST VALUES (4,20,75,12)

SELECT Id, Foo, Bar, Blagh 
FROM (
      SELECT id, Foo, Bar, Blagh, CASE WHEN (Min(Bar) OVER(PARTITION BY FOO) = Bar) THEN 1 ELSE 0 END as MyRow
      FROM @TEST) t
WHERE MyRow = 1

Хотя для этого все еще требуется подзапрос, он устраняет необходимость в объединениях.

Просто еще один вариант.

1 голос
/ 31 июля 2009

Это может помочь:

DECLARE @Table TABLE(
        ID INT,
        Foo FLOAT,
        Bar FLOAT,
        Blah FLOAT
)

INSERT INTO @Table (ID,Foo,Bar,Blah) SELECT 1, 10 ,20 ,30
INSERT INTO @Table (ID,Foo,Bar,Blah) SELECT 2, 10 ,5 ,1
INSERT INTO @Table (ID,Foo,Bar,Blah) SELECT 3, 20 ,50 ,40
INSERT INTO @Table (ID,Foo,Bar,Blah) SELECT 4, 20 ,75 ,12

SELECT  t.*
FROM    @Table t INNER JOIN
        (
            SELECT  Foo,
                    MIN(Bar) MINBar
            FROM    @Table
            GROUP BY Foo
        ) Mins ON t.Foo = Mins.Foo
                AND t.Bar = Mins.MINBar
1 голос
/ 31 июля 2009

Насколько я понимаю, вы не можете сделать это за один раз.

select Foo, min(Bar) from table group by Foo

,, получает минимальный бар для каждого отдельного Foo. Но вы не можете привязать этот минимум к определенному идентификатору, потому что может быть более одной строки с этим значением Bar.

То, что вы можете сделать, выглядит примерно так:

select * from Table t
join (
   select Foo, min(Bar) as minbar
   from Table group by Foo
) tt on t.Foo=tt.Foo and t.Bar=tt.minbar

Обратите внимание, что если равно более чем одной строке с минимальным значением Bar, вы получите их все с помощью вышеуказанного запроса.

Теперь я не претендую на звание гуру SQL, и уже поздно, где я нахожусь, и я могу что-то упустить, но есть мои $ 0,02

0 голосов
/ 04 марта 2014
declare @Borg table (
    ID int,
    Foo int,
    Bar int,
    Blagh int
)
insert into @Borg values (1,10,20,30)
insert into @Borg values (2,10,5,1)
insert into @Borg values (3,20,50,70)
insert into @Borg values (4,20,75,12)

select * from @Borg

select Foo,Bar,Blagh from @Borg b 
where Bar = (select MIN(Bar) from @Borg c where c.Foo = b.Foo)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...