SQL 如何计算不基于行的中位数

SQL how to calculate median not based on rows

我的 table 中有一个汽车样本,我想用 SQL 计算我的样本的中位数价格。最好的方法是什么?

+-----+-------+----------+
| Car | Price | Quantity |
+-----+-------+----------+
| A   |   100 |        2 |
| B   |   150 |        4 |
| C   |   200 |        8 |
+-----+-------+----------+

我知道我可以使用 percentile_cont(或 percentile_disc),如果我的 table 是这样的:

+-----+-------+
| Car | Price |
+-----+-------+
| A   |   100 |
| A   |   100 |
| B   |   150 |
| B   |   150 |
| B   |   150 |
| B   |   150 |
| C   |   200 |
| C   |   200 |
| C   |   200 |
| C   |   200 |
| C   |   200 |
| C   |   200 |
| C   |   200 |
| C   |   200 |
+-----+-------+

但在现实世界中,我的第一个 table 有大约 1 亿行,第二个 table 应该有大约 3 个台球行(而且我不知道如何转换我的第一个table进入第二个)。

这在少数结果上看起来是正确的,但尝试更大的结果集 double-check。

首先创建一个 table,其中包含每辆车的总数(或使用 CTE 或 sub-query),您可以选择。我只是在这里创建一个单独的 table。

    create table table2 as
    (
        select car, 
        quantity, 
        price, 
        price * quantity as total
        from table1
    )

然后运行这个查询,查找落在中间的价格组。

    select price
    from (
        select car, price, 
        sum(total) over (order by car) as rollsum, 
        sum(total) over () as total
        from table2
        )a
    where rollsum >= total/2

正确地 returns 价值 200 美元。

这是在 sql 服务器

中执行此操作的方法

在第一步中,我做的是计算对应于中位数下限和上限的索引(如果我们有奇数个元素,则下限和上限相同,否则它基于 x/2 和 x/2+第 1 个值)

然后得到数量的累加和,用它来选择下限和上限对应的元素如下

with median_dt
  as (
select case when sum(quantity)%2=0 then
                 sum(quantity)/2 
            else 
                 sum(quantity)/2 + 1
        end as lower_limit
      ,case when sum(quantity)%2=0 then
                 (sum(quantity)/2) + 1
            else 
                 sum(quantity)/2 + 1
        end as upper_limit  
 from t 
     )
    ,data
    as (
 select *,sum(quantity) over(order by price asc) as cum_sum
   from t
       )
   ,rnk_val
   as(select * 
       from (
             select price,row_number() over(order by d.cum_sum asc) as rnk
               from data d
               join median_dt b
                 on b.lower_limit<=d.cum_sum
             )x 
      where x.rnk=1
      union all 
     select * 
       from (
             select price,row_number() over(order by d.cum_sum asc) as rnk
               from data d
               join median_dt b
                 on b.upper_limit<=d.cum_sum
             )x 
      where x.rnk=1
      ) 
  select avg(price) as median
    from rnk_val



+--------+
| median |
+--------+
|    200 |
+--------+

db fiddle link https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=c5cfa645a22aa9c135032eb28f1749f6