在SQL Server中计算中值的函数

Yaa*_*lis 212 sql sql-server aggregate-functions median

根据MSDN,Median不能作为Transact-SQL中的聚合函数使用.但是,我想知道是否可以创建此功能(使用Create Aggregate函数,用户定义函数或其他方法).

这样做的最佳方式(如果可能) - 允许在聚合查询中计算中值(假设数值数据类型)?

Jef*_*ood 194

如果您使用的是SQL 2005或更高版本,这对于表中的单个列来说是一个很好的,简单的中位数计算:

SELECT
(
 (SELECT MAX(Score) FROM
   (SELECT TOP 50 PERCENT Score FROM Posts ORDER BY Score) AS BottomHalf)
 +
 (SELECT MIN(Score) FROM
   (SELECT TOP 50 PERCENT Score FROM Posts ORDER BY Score DESC) AS TopHalf)
) / 2 AS Median
Run Code Online (Sandbox Code Playgroud)

  • 鉴于没有Median()聚合函数,这很聪明,相对简单.但是怎么没有Median()函数存在!?坦白说,我有点FLOOR()编辑. (55认同)
  • 如何在GROUP BY中使用此解决方案? (3认同)
  • 如果“分数”字段可为空,则不会计算中位数。您可以在查询中指定: 'SELECT ( (SELECT MAX(Score) FROM (SELECT TOP 50 PERCENT Score FROM PostsWhere Score is not null ORDER BY Score) AS BottomHalf) + (SELECT MIN(Score) FROM (SELECT TOP 50 PERCENT Score FROM 分数不为空的帖子 ORDER BY 分数 DESC) AS TopHalf) ) / 2 AS Median' (3认同)
  • 对于奇数个结果来说,这是否正确?就像 9 个元素一样,中位数是第五个。 (2认同)

Jus*_*ant 129

有很多方法可以做到这一点,性能差异很大.这是一个特别优化的解决方案,来自Medians,ROW_NUMBERs和性能.当涉及到执行期间生成的实际I/O时,这是一个特别优化的解决方案 - 它看起来比其他解决方案更昂贵,但它实际上要快得多.

该页面还包含对其他解决方案和性能测试详细信息的讨论.请注意,如果有多个行具有相同的中间列值,则使用唯一列作为消歧器.

与所有数据库性能方案一样,始终尝试使用真实硬件上的实际数据测试解决方案 - 您永远不知道对SQL Server优化程序的更改或环境中的特性何时会使正常快速的解决方案变慢.

DECLARE @c BIGINT = (SELECT COUNT(*) FROM dbo.EvenRows);

SELECT AVG(1.0 * val)
FROM (
    SELECT val FROM dbo.EvenRows
     ORDER BY val
     OFFSET (@c - 1) / 2 ROWS
     FETCH NEXT 1 + (1 - @c % 2) ROWS ONLY
) AS x;
Run Code Online (Sandbox Code Playgroud)

  • 这就是为什么有一个消歧器(上面的代码示例中的SalesOrderId)很重要,因此您可以确保结果集行的顺序向后和向前都是一致的.通常,唯一的主键是理想的消歧器,因为它可以在没有单独的索引查找的情况下使用.如果没有可用的消歧列(例如,如果表没有无统一的键),则必须使用另一种方法来计算中值,因为正如您正确指出的那样,如果您不能保证DESC行号是镜像的ASC行号,然后结果是不可预测的. (26认同)
  • 如果您的数据中有欺骗行为,特别是很多欺骗行为,我认为这不起作用.你不能保证row_numbers会排队.你可以得到一些非常疯狂的答案,你的中位数,甚至更糟,没有中位数. (12认同)
  • 我建议在代码本身添加注释,描述对消歧器的需求. (8认同)
  • 谢谢,当将列切换到我的数据库时,我放弃了消除歧义,认为它不相关.在这种情况下,这个解决方案非常有效. (4认同)
  • 真棒!我知道它的重要性,但现在我可以给它一个名字......消除歧义!谢谢贾斯汀! (4认同)

Sim*_*ver 78

在SQL Server 2012中,您应该使用PERCENTILE_CONT:

SELECT SalesOrderID, OrderQty,
    PERCENTILE_CONT(0.5) 
        WITHIN GROUP (ORDER BY OrderQty)
        OVER (PARTITION BY SalesOrderID) AS MedianCont
FROM Sales.SalesOrderDetail
WHERE SalesOrderID IN (43670, 43669, 43667, 43663)
ORDER BY SalesOrderID DESC
Run Code Online (Sandbox Code Playgroud)

另见:http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012/

  • 由于性能不佳,这种专家分析对PERCENTILE函数提出了令人信服的论据.http://sqlperformance.com/2012/08/t-sql-queries/median (12认同)
  • 你不需要添加`DISTINCT`或`GROUPY BY SalesOrderID`?否则你会有很多重复的行. (3认同)

Sir*_*bin 21

我原来的快速回答是:

select  max(my_column) as [my_column], quartile
from    (select my_column, ntile(4) over (order by my_column) as [quartile]
         from   my_table) i
--where quartile = 2
group by quartile
Run Code Online (Sandbox Code Playgroud)

这将一举为您提供中位数和四分位数范围.如果你真的只想要一行是中位数,那么取消注释where子句.

当你坚持使用解释计划时,60%的工作是对数据进行排序,这在计算像这样的位置相关统计数据时是不可避免的.

我修改了答案,遵循RobertŠevčík-Robajz在以下评论中提出的出色建议:

;with PartitionedData as
  (select my_column, ntile(10) over (order by my_column) as [percentile]
   from   my_table),
MinimaAndMaxima as
  (select  min(my_column) as [low], max(my_column) as [high], percentile
   from    PartitionedData
   group by percentile)
select
  case
    when b.percentile = 10 then cast(b.high as decimal(18,2))
    else cast((a.low + b.high)  as decimal(18,2)) / 2
  end as [value], --b.high, a.low,
  b.percentile
from    MinimaAndMaxima a
  join  MinimaAndMaxima b on (a.percentile -1 = b.percentile) or (a.percentile = 10 and b.percentile = 10)
--where b.percentile = 5
Run Code Online (Sandbox Code Playgroud)

当您拥有偶数个数据项时,这应该计算正确的中位数和百分位数值.如果您只想要中位数而不是整个百分位数分布,请再次取消注释最终的where子句.

  • 如果可以一个人关闭,那么上面的查询就可以了.但如果你需要确切的中位数,那么你将遇到麻烦.例如,对于序列(1,3,5,7),中位数为4,但上面的查询返回3.对于(1,2,3,503,603,703),中位数为258,但上面的查询返回503. (3认同)
  • 这实际上工作得很好,并且允许对数据进行分区。 (2认同)

l--*_*''' 18

更好的是:

SELECT @Median = AVG(1.0 * val)
FROM
(
    SELECT o.val, rn = ROW_NUMBER() OVER (ORDER BY o.val), c.c
    FROM dbo.EvenRows AS o
    CROSS JOIN (SELECT c = COUNT(*) FROM dbo.EvenRows) AS c
) AS x
WHERE rn IN ((c + 1)/2, (c + 2)/2);
Run Code Online (Sandbox Code Playgroud)

来自大师自己,Itzik Ben-Gan!


enk*_*tor 8

MS SQL Server 2012(及更高版本)具有PERCENTILE_DISC函数,该函数计算排序值的特定百分位数.PERCENTILE_DISC(0.5)将计算中位数 - https://msdn.microsoft.com/en-us/library/hh231327.aspx