用PERCENTILE_CONT和分组计算中位数

时间:2016-01-22 09:36:51

标签: sql-server tsql

我需要计算名为oppo_opened的表格上oppo_offered日期与Opportunity日期之间的天数中位数,并按联合表Company对结果进行分组。做平均值非常简单:

----Average Opened to Offered last 12 months
Select Comp_Name,AVG(DATEDIFF(day,Oppo_Opened,oppo_offerDate)) as averageDTO from company join Opportunity on oppo_lender = Comp_CompanyId
where DATEADD(Year,-1,GETDATE()) > Oppo_Opened AND oppo_offerDate is not null
group by Comp_Name order by averageDTO desc

上面每个公司返回一行,因为我们按Comp_Name进行分组。

这对于中位数来说这并不容易。我找到了以下帖子:

Function to Calculate Median in Sql Server

这让我到了这里:

SELECT     Comp_Name,
    PERCENTILE_CONT(0.5) 
        WITHIN GROUP (ORDER BY DATEDIFF(day,Oppo_Opened,oppo_offerDate))
        OVER (PARTITION BY Comp_Name) AS MedianCont
from company inner join Opportunity on oppo_lender = Comp_CompanyId
where DATEADD(Year,-1,GETDATE()) > Oppo_Opened AND oppo_offerDate is not null
order by MedianCont desc

但是,这会为每个Opportunity记录返回一行,而不是每Company个记录返回一行。我不能对此做一个简单的group by Comp_Name,因为我们得到:

  

Msg 8120,Level 16,State 1,Line 3

     

列'Opportunity.Oppo_Opened'在选择列表中无效,因为它不包含在聚合函数或GROUP BY子句中。

     

Msg 8120,Level 16,State 1,Line 3

     

列'Opportunity.oppo_offerDate'在选择列表中无效,因为它不包含在聚合函数或GROUP BY子句中。

在上面的任何一个中加上一个聚合方法我认为会搞砸我的中位数计算。

我的问题:我如何按PERCENTILE_CONT分区分组,每Company获取一行(我应该获得209行),而不是每{{1}行一行1}}(30k行以北)?

1 个答案:

答案 0 :(得分:1)

我采取了不同的方法,这将使我能够突出并解释如何计算中位数。

我应该说你所看到的previous question包含了对不同中位数计算的表现和相对优点的更好讨论。这种方法可能不是最有效的。但是我希望能够更容易地遵循并根据您的需求进行修改。

我的示例使用下面定义的数据。有两家公司A和B.A的中位数为4,B的中位数为5.

示例数据

/* Using a table variable to create shared
 * sample data.
 */
DECLARE @Sample TABLE
    (
        Company     VARCHAR(1),
        [Value]     INT
    )
;

INSERT INTO @Sample
    (
        Company,
        [Value]
    )
VALUES
    ('A', 2),   -- Median value for Company A: {2, 4, 6} = 4.
    ('A', 4),       
    ('A', 6),
    ('B', 2),   -- Median records for Company B: {2, 4, 6, 8} = {4, 6}.
    ('B', 4),   -- Median value = {4, 6} / 2 = 5.   
    ('B', 6),       
    ('B', 8)
;

我的中位数计算基于此Wikipedia artical。对于任何具有奇数记录的集合,我按中间条目进行排序,按值排序。对于具有偶数记录的集合,我采用了两个中间记录,再次按值排序,并对结果进行平均。

<强>实施例

{3, 1, 2} = {1, 2, 3}        Sort.
{1, 2, 3} = 2                Find middle entry.
2                            Median.

即使

{4, 3, 1, 2} = {1, 2, 3, 4}  Sort.
{1, 2, 3, 4} = {2, 3}        Find middle values.
{2, 3} / 2 = 2.5             Average.
2.5                          Median.

<强>查询

SELECT  
    r.Company,
    AVG(r.[Value])
FROM
    (
        /* You cannot use windowed functions directly in a WHERE clause.
         * This subquery makes those fields available to all clauses in the 
         * outer query.
         */
        SELECT
            ROW_NUMBER() OVER (PARTITION BY s.Company ORDER BY s.[Value])           AS RecordNumber,
            COUNT(*) OVER (PARTITION BY s.Company)                                  AS CompanyRecordCount,
            CAST((COUNT(*) OVER (PARTITION BY s.Company)) AS DECIMAL(9, 1)) / 2.0   AS MedianPoint,
            COUNT(*) OVER (PARTITION BY s.Company)  % 2                             AS IsOdd,
            s.Company,
            s.[Value]
        FROM
            @Sample AS s
    ) AS r
WHERE
    (   
        -- When company has odd number of records median is the middle record.
        r.IsOdd = 1
        AND r.RecordNumber = CEILING(MedianPoint)
    )       
    OR
    (
        -- When company has even number of records median is avg of two middle records.
        r.IsOdd = 0
        AND r.RecordNumber IN (MedianPoint, MedianPoint + 1)
    )
GROUP BY
    r.Company
;

我使用ROW_NUMBER为每个公司内的每条记录编号,按值(RecordNumber)排序。

我已将COUNTOVER clause合并,以返回每家公司的记录数(CompanyRecordCount)。

我已将每家公司的记录数除以2,以找到中间点(MedianPoint)。我们将使用它来过滤中间记录。我还计算了余数(IsOdd)。这将用于确定要应用的计算。

WHERE子句过滤MedianPoint的CEILING的奇数记录。天花板向上舍入到最接近的整数,在奇数的情况下,这始终是您需要的记录。如果你有3条记录,则中间点为1.5,你想要记录#2。

对于偶数记录,WHERE子句返回中值记录,并返回序列中的下一个记录。 4条记录的中间点为2.我们需要记录2和3。

最后,外部查询平均结果。在奇数记录的情况下,只有平均值,所以它保持不变。对于均匀,有两个,中位数是相互分开计算的。