如何获得组中“中间”值的平均值?
Posted
技术标签:
【中文标题】如何获得组中“中间”值的平均值?【英文标题】:How to get average of the 'middle' values in a group? 【发布时间】:2013-09-30 18:00:31 【问题描述】:我有一个包含值和组 ID 的表(简化示例)。我需要得到中间 3 个值的每组的平均值。因此,如果有 1、2 或 3 个值,它只是平均值。但是如果有 4 个值,它将排除最高值,最高和最低值 5 个值,等等。我在想某种窗口函数,但我不确定它是否可能。
http://www.sqlfiddle.com/#!11/af5e0/1
对于这个数据:
TEST_ID TEST_VALUE GROUP_ID
1 5 1
2 10 1
3 15 1
4 25 2
5 35 2
6 5 2
7 15 2
8 25 3
9 45 3
10 55 3
11 15 3
12 5 3
13 25 3
14 45 4
我愿意
GROUP_ID AVG
1 10
2 15
3 21.6
4 45
【问题讨论】:
为什么要这样做?似乎是一个奇怪的使用要求。当您将行数扩大到数百万时,您是否仍只想平均 3 个值?对于偶数行,你总是想先去掉最高值,然后再去掉最低值吗? @Vulcronos,会有很多很多的组,但每个组将包含 0 到 10 之间的值,大多数有 3-5 个值。这些值是测试结果,中间平均值和总体平均值之间的差异值得关注。 【参考方案1】:使用解析函数的另一种选择;
SELECT group_id,
avg( test_value )
FROM (
select t.*,
row_number() over (partition by group_id order by test_value ) rn,
count(*) over (partition by group_id ) cnt
from test t
) alias
where
cnt <= 3
or
rn between floor( cnt / 2 )-1 and ceil( cnt/ 2 ) +1
group by group_id
;
演示 --> http://www.sqlfiddle.com/#!11/af5e0/59
【讨论】:
这是一件美丽的事情! +1 表示简洁的 SQL,但如果我可以不提供代码解释,则为 -0.5。【参考方案2】:我不熟悉窗口函数上的 Postgres 语法,但我能够通过 SQL Fiddle 在 SQL Server 中解决您的问题。也许您可以轻松地将其迁移到与 Postgres 兼容的代码中。希望对您有所帮助!
关于我如何工作的快速入门。
-
为每组的测试分数排序
获取每个组中的项目计数
将其用作子查询并仅选择中间 3 项(即外部查询中的 where 子句)
获取每组的平均值
--
select
group_id,
avg(test_value)
from (
select
t.group_id,
convert(decimal,t.test_value) as test_value,
row_number() over (
partition by t.group_id
order by t.test_value
) as ord,
g.gc
from
test t
inner join (
select group_id, count(*) as gc
from test
group by group_id
) g
on t.group_id = g.group_id
) a
where
ord >= case when gc <= 3 then 1 when gc % 2 = 1 then gc / 2 else (gc - 1) / 2 end
and ord <= case when gc <= 3 then 3 when gc % 2 = 1 then (gc / 2) + 2 else ((gc - 1) / 2) + 2 end
group by
group_id
【讨论】:
SQLFiddle 也支持 Postgres。 我知道 - 我不熟悉我在 SQL Server 中熟悉的确切的 postgres 语法/函数名称。如果有人两者都知道并想翻译,那就太好了。 我打开了您的 SQL Fiddle,选择了 PostgreSQL,然后构建并运行。唯一抱怨的是非标准的CONVERT
函数;我把它换成了标准的CAST .. AS
,效果很好:sqlfiddle.com/#!12/07bc9/2
标准 SQL 再次大获全胜。【参考方案3】:
with cte as (
select
*,
row_number() over(partition by group_id order by test_value) as rn,
count(*) over(partition by group_id) as cnt
from test
)
select
group_id, avg(test_value)
from cte
where
cnt <= 3 or
(rn >= cnt / 2 - 1 and rn <= cnt / 2 + 1)
group by group_id
order by group_id
sql fiddle demo
在 cte 中,我们需要通过 window function 获取每个 group_id
上的元素计数 + 计算每个 group_id
中的 row_number。然后,如果这个计数 > 3,那么我们需要通过将计数除以 2 来获得组的中间,然后获得 +1 和 -1 元素。如果 count
【讨论】:
+1 表示使用 CTE,但如果我可以不提供代码解释,则为 -0.5。 @IMSoP 实际上,我真的,真的在尝试编写不需要 cmets 的 SQL :),但无论如何都添加了解释 是的,公平地说,它的代码可读性很好 :) 虽然你可以将 CTE 命名为比“cte”更好:P 在使用我的实际数据进行快速测试时,子查询比 CTE 快一点。但是,一个很好的解决方案! @IMSoP 是的,我仍在为这样一个简单的预计算 cte 开发代码约定。我认为'precalc'或'aggr'之类的东西不会更好:)我愿意接受建议顺便说一句:)【参考方案4】:这行得通:
SELECT A.group_id, avg(A.test_value) AS avg_mid3 FROM
(SELECT group_id,
test_value,
row_number() OVER (PARTITION BY group_id ORDER BY test_value) AS position
FROM test) A
JOIN
(SELECT group_id,
CASE
WHEN count(*) < 4 THEN 1
WHEN count(*) % 2 = 0 THEN (count(*)/2 - 1)
ELSE (count(*) / 2)
END AS position_start,
CASE
WHEN count(*) < 4 THEN count(*)
WHEN count(*) % 2 = 0 THEN (count(*)/2 + 1)
ELSE (count(*) / 2 + 2)
END AS position_end
FROM test GROUP BY group_id) B
ON A.group_id=B.group_id
AND A.position >= B.position_start
AND A.position <= B.position_end
GROUP BY A.group_id
小提琴链接:http://www.sqlfiddle.com/#!11/af5e0/56
【讨论】:
谢谢!我不知道如何从中间对它们进行排序,但那个 case 语句有效。【参考方案5】:如果您需要计算组的平均值,那么您可以这样做:
SELECT CASE WHEN NUMBER_FIRST_GROUP <> 0
THEN SUM_FIRST_GROUP / NUMBER_FIRST_GROUP
ELSE NULL
END AS AVG_FIRST_GROUP,
CASE WHEN NUMBER_SECOND_GROUP <> 0
THEN SUM_SECOND_GROUP / NUMBER_SECOND_GROUP
ELSE NULL
END AS AVG_SECOND_GROUP,
CASE WHEN NUMBER_THIRD_GROUP <> 0
THEN SUM_THIRD_GROUP / NUMBER_THIRD_GROUP
ELSE NULL
END AS AVG_THIRD_GROUP,
CASE WHEN NUMBER_FOURTH_GROUP <> 0
THEN SUM_FOURTH_GROUP / NUMBER_FOURTH_GROUP
ELSE NULL
END AS AVG_FOURTH_GROUP
FROM (
SELECT
SUM(CASE WHEN GROUP_ID = 1 THEN 1 ELSE 0 END) AS NUMBER_FIRST_GROUP,
SUM(CASE WHEN GROUP_ID = 1 THEN TEST_VALUE ELSE 0 END) AS SUM_FIRST_GROUP,
SUM(CASE WHEN GROUP_ID = 2 THEN 1 ELSE 0 END) AS NUMBER_SECOND_GROUP,
SUM(CASE WHEN GROUP_ID = 2 THEN TEST_VALUE ELSE 0 END) AS SUM_SECOND_GROUP,
SUM(CASE WHEN GROUP_ID = 3 THEN 1 ELSE 0 END) AS NUMBER_THIRD_GROUP,
SUM(CASE WHEN GROUP_ID = 3 THEN TEST_VALUE ELSE 0 END) AS SUM_THIRD_GROUP,
SUM(CASE WHEN GROUP_ID = 4 THEN 1 ELSE 0 END) AS NUMBER_FOURTH_GROUP,
SUM(CASE WHEN GROUP_ID = 4 THEN TEST_VALUE ELSE 0 END) AS SUM_FOURTH_GROUP
FROM TEST
) AS FOO
【讨论】:
这不适合我,因为我会有很多很多的组。但是,谢谢。以上是关于如何获得组中“中间”值的平均值?的主要内容,如果未能解决你的问题,请参考以下文章