Подтвердить что ты не робот

Как вы группируете по одному столбцу и извлекаете строку с минимальным значением другого столбца в T/SQL?

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

У меня есть таблица вроде этого:

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))

Однако это явно не работает, так как это полностью недействительно. ИМЕЕТ синтаксис и идентификатор, Foo, Bar и Blagh не агрегируются.

Что я делаю неправильно?

4b9b3361

Ответ 1

Это почти точно такой же вопрос, но он имеет некоторые ответы!

Здесь я издеваюсь над вашей таблицей:

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"

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

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

Ответ 2

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.

Ответ 3

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

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

вы получаете минимальный бар для каждого отдельного Foo. Но вы не можете привязать этот минимум к определенному ID, потому что может быть больше одной строки с этим значением 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:)

Ответ 4

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

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

Ответ 5

Другим вариантом может быть следующее:

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

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

Еще один вариант.

Ответ 6

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)