【问题标题】:SQL Server - Group By, Average and PercentilesSQL Server - 分组依据、平均值和百分位数
【发布时间】:2016-06-17 09:49:32
【问题描述】:

我在 SQL Server 中有一个 FormSummaries 表,其中包含以下相关的示例数据列:

FormName | CompletionTime
Form1    | 70
Form1    | 20
Form1    | 30
Form1    | 40
Form1    | 80
Form1    | 60
Form1    | 90
Form1    | 10
Form2    | 30
Form2    | 40
Form2    | 80
Form2    | 90
Form2    | 40
Form2    | 1000
Form2    | 120
Form2    | 70

我需要做的是:

1) 按表单名称和该表单的平均完成时间对数据进行分组,非常简单:

SELECT 
    FormName, AVG(CompletionTime) 
FROM 
    FormSummaries 
WHERE 
    CompletionTime  is not null
GROUP BY
    FormName

2) 获取每种表单类型前 25%/后 25% 的完成时间的平均值(即完成每个表单所需的平均最快和最慢 25% 的时间)。理想情况下,这将在一个查询中,即

FormName | Bottom25%AverageCompletionTime | Top25%AverageCompletionTime
Form1    | 85                             | 15
Form2    | 560                            | 35

我生活在现实世界中,并意识到这可能是不可能的,所以单独查询顶部和底部就可以了,即

FormName | Bottom25%AverageCompletionTime
Form1    | 85                            
Form2    | 560                           

FormName | Top25%AverageCompletionTime
Form1    | 15
Form2   | 35

我查看了 Partition by、Ntile 和 Over,但我似乎无法获得任何东西来产生所需的结果(尽管这很可能是因为我没有正确实现这些!)。

有人可以帮忙吗?

非常感谢。

【问题讨论】:

  • 在您的第一个查询中,您不需要 'WHERE CompletionTime is not null' ,因为 AVG 将忽略空值

标签: sql sql-server group-by average


【解决方案1】:

NTILE 以块的形式对结果进行排名,因此您对季度感兴趣,因此使用 NTILE (4) 分成 4 组,并在 formname 上进行分区。要使用 2 个查询来执行此操作,请尝试

-- top 25%
SELECT  formname, AVG(CompletionTime) 
FROM
(SELECT 
    FormName,completiontime, NTILE(4) over (partition by FormName order by completiontime) as QuartPercentile
FROM 
    FormSummaries
WHERE CompletionTime IS NOT NULL )
    x
WHERE  QuartPercentile = 1
GROUP BY formname

-- bottom 25%
SELECT  formname, AVG(CompletionTime) 
FROM
(SELECT 
    FormName,completiontime, NTILE(4) over (partition by FormName order by completiontime) as QuartPercentile
FROM 
    FormSummaries 
WHERE CompletionTime IS NOT NULL)
    x
WHERE  QuartPercentile = 4
GROUP BY formname

或者用一个查询

SELECT  formname,AVG( case when QuartPercentile = 4 then CompletionTime else null end)   as [Bottom25%AverageCompletionTime]
, AVG( case when QuartPercentile = 1 then CompletionTime else null end)   as [Top25%AverageCompletionTime]
FROM
(SELECT 
    FormName,completiontime, NTILE(4) over (partition by FormName order by completiontime) as QuartPercentile
FROM 
    FormSummaries 
WHERE CompletionTime IS NOT NULL)
    x

GROUP BY formname

请记住,如果您的完成时间列包含整数,AVG 将返回一个整数,因此您可能需要转换以获得所需的精度,例如

AVG( case when QuartPercentile = 1 then cast(CompletionTime AS decimal(9,2))  else null end) 

【讨论】:

  • 这太好了,谢谢。但是,我确实发现,在运行 Top 25% 查询时,CompletionTime 列中的所有值都为空。我自己运行了内部选择,可以看到正在返回空值。我在 CompletionTime 不为空的地方添加了,现在可以完美运行。
  • 我没有意识到 NTILE 包含空值并将它们分配给一大块记录,但我刚刚找到了这个链接sqlservercentral.com/Forums/Topic1508622-391-1.aspx我已经更新了我的答案
【解决方案2】:

您可以使用 CTE + PIVOT:

;WITH PercentCount AS (
SELECT  FormName,
        COUNT(*)/4 as [Bottom25Percent],
        COUNT(*) as [Top25Percent]
FROM Forms
GROUP BY FormName
), FormsWithRowNumber AS (
SELECT  f.FormName,
        f.CompletionTime,
        ROW_NUMBER() OVER (PARTITION BY f.FormName ORDER BY f.CompletionTime) as rn
FROM Forms f
), final AS (
SELECT  f.FormName, 
        f.CompletionTime,       
        CASE WHEN f.rn between 1 and [Bottom25Percent] THEN 1 
             WHEN f.rn between [Top25Percent]-[Bottom25Percent]+1 and [Top25Percent] THEN 2
             ELSE 0 END as [TopBottom]
FROM FormsWithRowNumber f
INNER JOIN PercentCount p
    ON p.FormName = f.FormName
)

SELECT *
FROM final
PIVOT (
    AVG(CompletionTime) FOR TopBottom IN ([1],[2])
) as pvt

输出:

FormName    Top25%AverageCompletionTime Bottom25%AverageCompletionTime
Form1       15                          85
Form2       35                          560

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-03
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    • 2020-08-07
    • 1970-01-01
    相关资源
    最近更新 更多