Как вы группируете по одному столбцу и извлекаете строку с минимальным значением другого столбца в 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 не агрегируются.
Что я делаю неправильно?
Ответы
Ответ 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)