【问题标题】:Compute median of column in SQL common table expression计算 SQL 公用表表达式中列的中位数
【发布时间】:2011-03-14 02:44:34
【问题描述】:

在 MSSQL2008 中,我尝试使用经典的中位数查询从公用表表达式中计算一列数字的中位数,如下所示:

WITH cte AS
(
   SELECT number
   FROM table
) 

SELECT cte.*,
(SELECT 
  (SELECT (   
    (SELECT TOP 1 cte.number  
     FROM     
     (SELECT TOP 50 PERCENT cte.number     
      FROM cte
      ORDER BY cte.number) AS medianSubquery1   
    ORDER BY cte.number DESC)  
    +   
  (SELECT TOP 1 cte.number
   FROM     
    (SELECT TOP 50 PERCENT cte.number    
     FROM cte   
     ORDER BY cte.number DESC) AS medianSubquery2   
   ORDER BY cte.number ASC) ) / 2)) AS median

FROM cte
ORDER BY cte.number

我得到的结果集如下:

NUMBER    MEDIAN
x1        x1
x1        x1
x1        x1
x2        x2
x3        x3

换句话说,当我希望中位数列一直向下为“x1”时,“中位数”列与“数字”列相同。我使用类似的表达式来计算模式,它在同一个公用表表达式上运行良好。

【问题讨论】:

    标签: sql sql-server sql-server-2008 median common-table-expression


    【解决方案1】:

    这不是一个全新的答案,因为它主要扩展了 Mark Byer 的答案,但有几个选项可以进一步简化查询。

    首先要真正利用 CTE。您不仅可以拥有多个 CTE,而且它们可以相互引用。考虑到这一点,我们可以创建一个额外的 CTE 来根据第一个结果计算中位数。这封装了中值计算,让实际的 SELECT 只做它需要做的事情。请注意,ROW_NUMBER() 必须移到第一个 CTE 中。

    ;WITH cte AS
    (
       SELECT number, ROW_NUMBER() OVER(ORDER BY number) AS rn
       FROM table1
    ),
    med AS
    (
        SELECT AVG(number) AS median
        FROM cte
        WHERE cte.rn = ((SELECT COUNT(*) FROM cte) + 1) / 2
        OR cte.rn = ((SELECT COUNT(*) FROM cte) + 2) / 2
    )
    SELECT cte.number, med.median
    FROM cte
    CROSS JOIN med
    

    为了进一步降低复杂性,您“可以”使用自定义 CLR 聚合来处理中位数(例如免费 SQL# 库中提供的 http://www.SQLsharp.com/ [我是作者])。

    ;WITH cte AS
    (
       SELECT number
       FROM table1
    ),
    med AS
    (
        SELECT  SQL#.Agg_Median(cte.number) AS median
        FROM    cte
    )
    SELECT cte.number, med.median
    FROM cte
    CROSS JOIN med
    

    【讨论】:

      【解决方案2】:

      你的查询的问题是你在做

      SELECT TOP 1 cte.number FROM...

      但它与子查询不相关,它与外部查询相关,因此子查询无关紧要。这就解释了为什么你最终只会得到相同的值。删除 cte.(如下)给出 CTE 的中值。这是一个常数值。你想做什么?

      WITH cte AS
          ( SELECT NUMBER
          FROM master.dbo.spt_values
          WHERE TYPE='p'
          )
      
      SELECT cte.*,
      (SELECT 
        (SELECT (   
          (SELECT TOP 1 number  
           FROM     
           (SELECT TOP 50 PERCENT cte.number     
            FROM cte
            ORDER BY cte.number) AS medianSubquery1   
          ORDER BY number DESC)  
          +   
        (SELECT TOP 1 number
         FROM     
          (SELECT TOP 50 PERCENT cte.number    
           FROM cte   
           ORDER BY cte.number DESC) AS medianSubquery2   
         ORDER BY number ASC) ) / 2)) AS median
      FROM cte
      ORDER BY cte.number
      

      返回

      NUMBER      median
      ----------- -----------
      0           1023
      1           1023
      2           1023
      3           1023
      4           1023
      5           1023
      6           1023
      7           1023
      

      【讨论】:

      • 我在您的查询中看到的唯一区别是在公用表表达式中添加了 WHERE 子句,我不确定该 WHERE 子句应该做什么。
      • 啊,我不明白将 CTE 名称指定为限定符会产生这种效果。在实践中,我将两列用于图表目的,因此恒定中值对于在第一个系列的中值处绘制图表中的第二个系列非常有用。感谢您的帮助。
      • 从技术上讲,输出不正确。 1023.50应该是中位数。因此,如果您有偶数行并需要进行除法,您是否不需要将 Number 转换为小数或浮点数(例如 Number * 1.000)?
      【解决方案3】:

      这里有一个稍微不同的方法:

      WITH cte AS
      (
         SELECT number
         FROM table1
      )
      SELECT T1.number, T3.median
      FROM cte T1, 
      (
          SELECT AVG(number) AS median
          FROM
          (
              SELECT number, ROW_NUMBER() OVER(ORDER BY number) AS rn
              FROM cte
          ) T2
          WHERE T2.rn = ((SELECT COUNT(*) FROM table1) + 1) / 2
          OR T2.rn = ((SELECT COUNT(*) FROM table1) + 2) / 2
      ) T3
      

      【讨论】:

      • 谢谢。这无疑是一种更简洁、更简单的计算中位数的方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多