从多列中选择最小值的最佳方法是什么?
-
21-08-2019 - |
题
给定 SQL Server 2005 中的下表:
ID Col1 Col2 Col3
-- ---- ---- ----
1 3 34 76
2 32 976 24
3 7 235 3
4 245 1 792
编写产生以下结果的查询的最佳方法是什么(即生成最后一列 - 包含 Col1、Col2 和 Col 3 中最小值的列 对于每一行)?
ID Col1 Col2 Col3 TheMin
-- ---- ---- ---- ------
1 3 34 76 3
2 32 976 24 24
3 7 235 3 3
4 245 1 792 1
更新:
为了澄清(正如我在评论中所说),在真实场景中,数据库是 适当标准化. 。这些“数组”列不在实际的表中,而是在报告所需的结果集中。新的要求是报表也需要这个 MinValue 列。我无法更改底层结果集,因此我希望使用 T-SQL 来获得方便的“出狱卡”。
我尝试了下面提到的CASE方法,虽然有点麻烦,但它确实有效。它也比答案中所述的更复杂,因为您需要考虑同一行中有两个最小值这一事实。
不管怎样,我想我应该发布我当前的解决方案,考虑到我的限制,该解决方案效果很好。它使用 UNPIVOT 运算符:
with cte (ID, Col1, Col2, Col3)
as
(
select ID, Col1, Col2, Col3
from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
select
ID, min(Amount) as TheMin
from
cte
UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
group by ID
) as minValues
on cte.ID = minValues.ID
我会预先说,我不希望这提供最佳性能,但考虑到这种情况(我无法仅针对新的 MinValue 列要求重新设计所有查询),这是一个非常优雅的“出狱”卡片”。
解决方案
有可能是很多方法可以做到这一点。我的建议是使用案例/何时做。 3列,这不是太糟糕了。
Select Id,
Case When Col1 < Col2 And Col1 < Col3 Then Col1
When Col2 < Col1 And Col2 < Col3 Then Col2
Else Col3
End As TheMin
From YourTableNameHere
其他提示
使用CROSS APPLY
:
SELECT ID, Col1, Col2, Col3, MinValue
FROM YourTable
CROSS APPLY (SELECT MIN(d) MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A
SELECT ID, Col1, Col2, Col3,
(SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table
可以使用“强力”方法与捻度:
SELECT CASE
WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1
WHEN Col2 <= Col3 THEN Col2
ELSE Col3
END AS [Min Value] FROM [Your Table]
当条件失败它保证Col1中并不因此可以从条件其余消除它的最小值当第一。同样地,对于后续的条件。对于五列的查询就变成了:
SELECT CASE
WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
WHEN Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
WHEN Col3 <= Col4 AND Col3 <= Col5 THEN Col3
WHEN Col4 <= Col5 THEN Col4
ELSE Col5
END AS [Min Value] FROM [Your Table]
请注意,如果有两个或多个列之间的粘结,然后<=
确保我们尽早退出CASE
语句。
要做到这一点,最好的办法是可能的不的做到这一点 - 这是奇怪的是,人们坚持存储需要SQL“体操”来提取有意义的信息来确定其数据,当有远更简单的方法,以达到预期的效果,如果你只是组织你的模式更好一点: - )
在右键的方式做到这一点,在我看来,是下面的表有:
ID Col Val
-- --- ---
1 1 3
1 2 34
1 3 76
2 1 32
2 2 976
2 3 24
3 1 7
3 2 235
3 3 3
4 1 245
4 2 1
4 3 792
与ID/Col
作为主键(以及可能Col
作为一个额外的键时,根据需要)。那么你的查询变得简单select min(val) from tbl
,你仍然可以通过在其他查询中使用where col = 2
对待个人“老列”分开。这也可以很容易地扩大应的数目“旧列”成长。
这使你查询的这样的容易得多。一般准则我倾向于使用,如果你的曾经的有东西,看起来像在一个数据库行的一个数组,你可能做错事,应该考虑重组数据。
但是,如果由于某种原因,你的不能的改变那些列,我建议使用INSERT和UPDATE触发器和添加的其他的列这些触发条件设定为最小的Col1/2/3
。这将移动操作的“成本”从选择客场更新/插入它所属的地方 - 在我的经验,多数数据库表中读取的次数远远多于这么写招致上写的成本往往随着时间的推移更加高效。
在换句话说,最小为一排,只有当其它列中的一个变化而变化,所以这是当你应该计算它,而不是选择每次(其如果数据浪费了没有改变)。然后,将与表落得像:
ID Col1 Col2 Col3 MinVal
-- ---- ---- ---- ------
1 3 34 76 3
2 32 976 24 24
3 7 235 3 3
4 245 1 792 1
这是具有select
时间来做出决定的任何其他选项通常是一个坏主意性能明智的,因为数据只改变在插入/更新 - 加入另一列的占用在DB更多的空间,并会稍微慢一点对于插入和更新,但可以的多的更快选择 - 首选的方法应该取决于你的优先次序出现,但正如指出,大多数表都念远的往往比的他们“重新写入。
如果列是整数,如你的例子我会创造一个功能:
create function f_min_int(@a as int, @b as int)
returns int
as
begin
return case when @a < @b then @a else coalesce(@b,@a) end
end
那么当我需要使用它,我会做:
select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)
如果你有5个colums那么上述变
select col1, col2, col3, col4, col5,
dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(col1,col2),col3),col4),col5)
使用这样的:
select least(col1, col2, col3) FROM yourtable
您也可以使用联合查询做到这一点。由于列数的增加,您将需要修改查询,但至少这会是一个简单的修改。
Select T.Id, T.Col1, T.Col2, T.Col3, A.TheMin
From YourTable T
Inner Join (
Select A.Id, Min(A.Col1) As TheMin
From (
Select Id, Col1
From YourTable
Union All
Select Id, Col2
From YourTable
Union All
Select Id, Col3
From YourTable
) As A
Group By A.Id
) As A
On T.Id = A.Id
这是蛮力但工作原理
select case when col1 <= col2 and col1 <= col3 then col1
case when col2 <= col1 and col2 <= col3 then col2
case when col3 <= col1 and col3 <= col2 then col3
as 'TheMin'
end
from Table T
...因为分钟()只对一列,而不是跨列。
如果你能够做一个存储过程,可能需要值的数组,你可以只调用。
select *,
case when column1 < columnl2 And column1 < column3 then column1
when columnl2 < column1 And columnl2 < column3 then columnl2
else column3
end As minValue
from tbl_example
联合查询小捻:
DECLARE @Foo TABLE (ID INT, Col1 INT, Col2 INT, Col3 INT)
INSERT @Foo (ID, Col1, Col2, Col3)
VALUES
(1, 3, 34, 76),
(2, 32, 976, 24),
(3, 7, 235, 3),
(4, 245, 1, 792)
SELECT
ID,
Col1,
Col2,
Col3,
(
SELECT MIN(T.Col)
FROM
(
SELECT Foo.Col1 AS Col UNION ALL
SELECT Foo.Col2 AS Col UNION ALL
SELECT Foo.Col3 AS Col
) AS T
) AS TheMin
FROM
@Foo AS Foo
如果您使用SQL 2005,你可以做这样的事情利落:
;WITH res
AS ( SELECT t.YourID ,
CAST(( SELECT Col1 AS c01 ,
Col2 AS c02 ,
Col3 AS c03 ,
Col4 AS c04 ,
Col5 AS c05
FROM YourTable AS cols
WHERE YourID = t.YourID
FOR
XML AUTO ,
ELEMENTS
) AS XML) AS colslist
FROM YourTable AS t
)
SELECT YourID ,
colslist.query('for $c in //cols return min(data($c/*))').value('.',
'real') AS YourMin ,
colslist.query('for $c in //cols return avg(data($c/*))').value('.',
'real') AS YourAvg ,
colslist.query('for $c in //cols return max(data($c/*))').value('.',
'real') AS YourMax
FROM res
这样,您就不会迷失在这么多的运营商:)
然而,这可能比其他选择慢。
这是你的选择...
下面我用一个临时表,以获得最低的几个日期。第一个临时表查询几个连接表得到不同日期(以及用于查询其他值),第二个临时表,然后因为有日期列使用尽可能多传球得到各列和最小日期。
这基本上是一样的联合查询,需要相同数量的道次,但也可以是更有效的(根据经验,但将需要测试)。效率是不是在这种情况下(8000条记录)的问题。一个可能的索引等等。
--==================== this gets minimums and global min
if object_id('tempdb..#temp1') is not null
drop table #temp1
if object_id('tempdb..#temp2') is not null
drop table #temp2
select r.recordid , r.ReferenceNumber, i.InventionTitle, RecordDate, i.ReceivedDate
, min(fi.uploaddate) [Min File Upload], min(fi.CorrespondenceDate) [Min File Correspondence]
into #temp1
from record r
join Invention i on i.inventionid = r.recordid
left join LnkRecordFile lrf on lrf.recordid = r.recordid
left join fileinformation fi on fi.fileid = lrf.fileid
where r.recorddate > '2015-05-26'
group by r.recordid, recorddate, i.ReceivedDate,
r.ReferenceNumber, i.InventionTitle
select recordid, recorddate [min date]
into #temp2
from #temp1
update #temp2
set [min date] = ReceivedDate
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.ReceivedDate < [min date] and t1.ReceivedDate > '2001-01-01'
update #temp2
set [min date] = t1.[Min File Upload]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Upload] < [min date] and t1.[Min File Upload] > '2001-01-01'
update #temp2
set [min date] = t1.[Min File Correspondence]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Correspondence] < [min date] and t1.[Min File Correspondence] > '2001-01-01'
select t1.*, t2.[min date] [LOWEST DATE]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
order by t1.recordid
有关多列其最好使用CASE语句,但是有两个数字列i和j可以用简单的数学:
分(I,J)=(I + J)/ 2 - ABS(I-J)/ 2
此公式可用于获得多个列的最小值,但其真正凌乱过去2,分钟(I,J,K)。将分(I,分钟(J,K))
SELECT [ID],
(
SELECT MIN([value].[MinValue])
FROM
(
VALUES
([Col1]),
([Col1]),
([Col2]),
([Col3])
) AS [value] ([MinValue])
) AS [MinValue]
FROM Table;
如果你知道什么值你所寻找的,通常是一个状态代码,下面可以帮助:
select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS
我知道这个问题是老了,但我仍然需要回答的,是不是快乐与其他的答案,所以我不得不想出我自己这是对@ paxdiablo's的answer 。
我从SAP ASE 16.0的土地来了,我只需要在其恕我直言,有效地存储在一个单行的不同列中的某些数据的统计偷看(他们代表不同的时间 - 当事情到来进行了规划,它所当战斗开始,最后什么实际时间)的预期。因此,我已转列到临时表中的行和预制我查询过这是一般。
N.B。 不是一个放之四海而皆准的解决办法前进!
CREATE TABLE #tempTable (ID int, columnName varchar(20), dataValue int)
INSERT INTO #tempTable
SELECT ID, 'Col1', Col1
FROM sourceTable
WHERE Col1 IS NOT NULL
INSERT INTO #tempTable
SELECT ID, 'Col2', Col2
FROM sourceTable
WHERE Col2 IS NOT NULL
INSERT INTO #tempTable
SELECT ID, 'Col3', Col3
FROM sourceTable
WHERE Col3 IS NOT NULL
SELECT ID
, min(dataValue) AS 'Min'
, max(dataValue) AS 'Max'
, max(dataValue) - min(dataValue) AS 'Diff'
FROM #tempTable
GROUP BY ID
此花大约30秒源组的630000行和仅使用索引专用的数据,所以不能在时间关键的过程,但对于像一次性数据检查或结束的天报告运行事你可能会被罚款(但与您同行或上司,请验证这一点!)。 这种风格的我的主要奖励是,我可以很容易地使用更多/更少列和改变分组,过滤等,特别是一旦数据copyied了。
在附加数据(columnName
,max
es,...)都来帮助我,我的搜索,所以你可能不会需要他们;我离开他们在这里也许引发一些想法: - )