【问题标题】:SQL ranking query to compute ranks and median in sub groupsSQL 排名查询,用于计算子组中的排名和中位数
【发布时间】:2013-04-03 12:26:06
【问题描述】:

我想在这个简单的xy_table子组 中计算yMedian

  x | y --groups--> gid |   x | y --medians-->  gid |   x | y
-------             -------------               -------------
0.1 | 4             0.0 | 0.1 | 4               0.0 | 0.1 | 4
0.2 | 3             0.0 | 0.2 | 3                   |     |
0.7 | 5             1.0 | 0.7 | 5               1.0 | 0.7 | 5
1.5 | 1             2.0 | 1.5 | 1                   |     |
1.9 | 6             2.0 | 1.9 | 6                   |     |
2.1 | 5             2.0 | 2.1 | 5               2.0 | 2.1 | 5
2.7 | 1             3.0 | 2.7 | 1               3.0 | 2.7 | 1

在此示例中,每个 x 都是唯一的,并且表已按 x 排序。 我现在想GROUP BY round(x) 并获取每个组中包含y 中位数的元组。

我已经可以用这个排名查询计算整个表格的中位数:

SELECT a.x, a.y FROM xy_table a,xy_table b
WHERE a.y >= b.y
GROUP BY a.x, a.y
HAVING count(*) = (SELECT round((count(*)+1)/2) FROM xy_table)

输出:0.1, 4.0

但我尚未成功编写查询来计算子组的中位数。

注意:我没有可用的median() 聚合函数。也请不要使用特殊的PARTITIONRANKQUANTILE 语句(如在类似但过于特定于供应商的SO questions 中找到)提出解决方案。我需要普通的 SQL(即兼容没有 median() 函数的 SQLite)

编辑:我实际上是在寻找Medoid,而不是Median

【问题讨论】:

  • 在您的示例中,第 2 表和第 3 表之间的关系是什么?第一个 gid 更改为 0.1,xy 值都不是 0.0 组的中位数
  • 对不起,我有一个错字。第三张表应显示gid(组 ID)组的中位数。在这种情况下,我假设 [4,3] 的中位数为 4(较大的值)。
  • [4,3] 的中值通常是 3.5,如您的 wikipedia 链接第一段中所建议的,您是否明确想要获得更大的值?
  • 是的,我想要更大的。查询应该只选择现有记录而不引入新记录。很抱歉称其为“中位数”。 :) 我目前正在处理您的查询,到目前为止看起来不错。我刚刚删除了 left_row/right_row 的东西和 avg,因为我不需要。

标签: sql sqlite group-by ranking median


【解决方案1】:

我建议用你的编程语言进行计算:

for each group:
  for each record_in_group:
    append y to array
  median of array

但是如果你被SQLite卡住了,你可以按y对每个组进行排序,然后像http://sqlfiddle.com/#!5/d4c68/55/0这样选择中间的记录:

更新:只有更大的“中位数”值才是重要的,即使是 nr。行数,因此不需要avg()

select groups.gid,
  ids.y median
from (
  -- get middle row number in each group (bigger number if even nr. of rows)
  -- note the integer divisions and modulo operator
  select round(x) gid,
    count(*) / 2 + 1 mid_row_right
  from xy_table
  group by round(x)
) groups
join (
  -- for each record get equivalent of
  -- row_number() over(partition by gid order by y)
  select round(a.x) gid,
    a.x,
    a.y,
    count(*) rownr_by_y
  from xy_table a
  left join xy_table b
    on round(a.x) = round (b.x)
    and a.y >= b.y
  group by a.x
) ids on ids.gid = groups.gid
where ids.rownr_by_y = groups.mid_row_right

【讨论】:

  • 谢谢,您的回答为我的解决方案提供了基础。为什么不直接使用SELECT round(x) gid, 1+(count(*))/2 mid_row 来获得“中间”行?至少对我有用。
  • 是的,它是左侧中点计算的剩余部分:)
【解决方案2】:

好的,这依赖于一个临时表:

create temporary table tmp (x float, y float);

insert into tmp
  select * from xy_table order by round(x), y

但是您可能会为您感兴趣的一系列数据创建它。另一种方法是确保xy_table 具有这种排序顺序,而不是仅对x 进行排序。原因是 SQLite 缺乏行编号功能。

然后:

select tmp4.x as gid, t.* from (
  select tmp1.x, 
         round((tmp2.y + coalesce(tmp3.y, tmp2.y)) / 2) as y -- <- for larger of the two, change to: (case when tmp2.y > coalesce(tmp3.y, 0) then tmp2.y else tmp3.y end)
  from (
    select round(x) as x, min(rowid) + (count(*) / 2) as id1, 
           (case when count(*) % 2 = 0 then min(rowid) + (count(*) / 2) - 1 
                 else 0 end) as id2
    from (  
      select *, rowid from tmp
    ) t
    group by round(x)
  ) tmp1
  join tmp tmp2 on tmp1.id1 = tmp2.rowid
  left join tmp tmp3 on tmp1.id2 = tmp3.rowid
) tmp4
join xy_table t on tmp4.x = round(t.x) and tmp4.y = t.y

如果您想将中位数视为两个中间值中较大的一个,这不符合 @Aprillion 已经指出的定义,那么您只需取两个 y 值中的较大者,而不是他们的平均值,在查询的第三行。

【讨论】:

    猜你喜欢
    • 2014-03-28
    • 2010-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 1970-01-01
    相关资源
    最近更新 更多