如何在不使用数据透视表和逆透视表的情况下从 SQL Server 2008 R2 中的行获取中值

Isa*_*van 6 sql-server sql-server-2008-r2 compatibility-level

创建表脚本

create table temp 
(
    id int identity(1,1),
    a decimal(6,2),
    b decimal(6,2),
    c decimal(6,2),
    d decimal(6,2),
    e decimal(6,2),
    f decimal(6,2),
    g decimal(6,2),
    h decimal(6,2),
    i decimal(6,2),
    j decimal(6,2),
    k decimal(6,2),
    l decimal(6,2),
    m decimal(6,2),
    n decimal(6,2),
    o decimal(6,2),
    p decimal(6,2),
    q decimal(6,2),
    r decimal(6,2),
    s decimal(6,2),
    t decimal(6,2),
    u decimal(6,2)
)
Run Code Online (Sandbox Code Playgroud)

插入脚本

insert into temp
    (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)
values
    (1,5,6,7,8,2,6,3,4,5,2,1,6,5,7,8,2,7,6,2,8)

insert into temp
    (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)
values
    (1,5,6,7,8,2,2,3,2,4,2,1,4,5,9,8,2,7,6,2,8)
Run Code Online (Sandbox Code Playgroud)

预期结果

Median
======
first row  - 5.00
second row - 4.00 
Run Code Online (Sandbox Code Playgroud)

非工作解决方案

我尝试了以下查询,该查询在 SQL Server 2014 中运行良好,但在 SQL Server 2008 R2 中存在问题。

select id, avg(val)
from ( 
    select id, val
         , count(*) over (partition by id) as c
         , row_number() over (partition by id order by val) as rn
    from temp unpivot (
             val for col in (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)
         ) as x 
) as y
where rn IN ((c + 1)/2, (c + 2)/2) 
group by id;
Run Code Online (Sandbox Code Playgroud)

我在 2014 版本中运行了上述查询并且它工作正常,但它在 2008 R2 中不起作用。我在 SQL Server 2008 R2 中收到此错误:

关键字“for”附近的语法不正确

原因一定是因为我的数据库兼容级别是80。但是如果我更改兼容级别,会影响我的应用程序,所以我不能这样做。

我也试过这个查询:

select id,        
(select cast(Avg(TotAvg)as decimal(6,2)) as Median from (values (convert(decimal(6,2), a)),(convert(decimal(6,2), b)),        
(convert(decimal(6,2), c)),        
(convert(decimal(6,2), d)),(convert(decimal(6,2), e)),        
(convert(decimal(6,2), f)),(convert(decimal(6,2), g)),(convert(decimal(6,2), h)),(convert(decimal(6,2), i)),        
(convert(decimal(6,2), j)),(convert(decimal(6,2), k)),(convert(decimal(6,2), l)),(convert(decimal(6,2), m)),        
(convert(decimal(6,2), n)),(convert(decimal(6,2), o)),(convert(decimal(6,2), p)),(convert(decimal(6,2), q)),        
(convert(decimal(6,2), r)),(convert(decimal(6,2), s)),(convert(decimal(6,2), t)),(convert(decimal(6,2), u))) as Totalavg(TotAvg))         
Median
from tempone
Run Code Online (Sandbox Code Playgroud)

显然它计算平均值,但我需要中位数。

And*_*y M 7

在兼容级别 80 下确实不支持 PIVOT 和 UNPIVOT。

但是,您可以使用嵌套的 VALUES 构造函数对行进行逆透视。由于双重嵌套,在我的案例中生成的查询看起来有点笨拙,但它可以在具有任何支持的兼容级别的 SQL Server 2008 中工作:

SELECT
  id,
  median =
  (
    SELECT
      AVG(val)
    FROM
      (
        SELECT
          c   = COUNT(*) OVER (),
          rn  = ROW_NUMBER() OVER (ORDER BY v.val ASC),
          val = v.val
        FROM
          (
            VALUES
            (t.a), (t.b), (t.c), (t.d), (t.e), (t.f), (t.g),
            (t.h), (t.i), (t.j), (t.k), (t.l), (t.m), (t.n),
            (t.o), (t.p), (t.q), (t.r), (t.s), (t.t), (t.u)
          ) AS v (val)
        WHERE
          v.val IS NOT NULL
      ) AS derived
    WHERE
      rn IN ((c + 1) / 2, (c + 2) / 2)
  )
FROM
  temp AS t
;
Run Code Online (Sandbox Code Playgroud)

v.val IS NOT NULL过滤是有更紧密地模仿UNPIVOT行为,因为UNPIVOT自动过滤掉NULL值。

额外的嵌套是必要的,因为没有其他方法可以生成计数和行号并在同一嵌套级别使用它们。

所以最里面的 SELECT ( SELECT ... FROM (VALUES ...)) 对行进行反透视并提供行数和行号,而中间层计算中值。

借助 CROSS APPLY 和主查询中的分组可以减少嵌套,如下所示:

SELECT
  id,
  median = AVG(x.val)
FROM
  temp AS t
  CROSS APPLY
  (
    SELECT
      c   = COUNT(*) OVER (),
      rn  = ROW_NUMBER() OVER (ORDER BY v.val ASC),
      val = v.val
    FROM
      (
        VALUES
        (t.a), (t.b), (t.c), (t.d), (t.e), (t.f), (t.g),
        (t.h), (t.i), (t.j), (t.k), (t.l), (t.m), (t.n),
        (t.o), (t.p), (t.q), (t.r), (t.s), (t.t), (t.u)
      ) AS v (val)
    WHERE
      v.val IS NOT NULL
  ) AS x
WHERE
  x.rn IN ((x.c + 1) / 2, (x.c + 2) / 2)
GROUP BY
  t.id
;
Run Code Online (Sandbox Code Playgroud)

如需两种方法的现场演示,请点击此 dbfiddle.uk 链接


ype*_*eᵀᴹ 7

一种使用的方法CROSS APPLY(所以我认为它可以在兼容级别为 80 的数据库中使用)。只有当这些列中没有空值时,这才能正常工作:

select t.id, z.val
from temp as t
  cross apply
    ( select top (1) y.val
      from 
        ( select top (11) x.val
          from
            ( values (a),(b),(c),(d),(e),(f),(g),(h),(i),(j),
                     (k),(l),(m),(n),(o),(p),(q),(r),(s),(t),(u)
            ) as x (val)
          order by x.val
        ) as y
      order by y.val desc
    ) as z ;
Run Code Online (Sandbox Code Playgroud)

测试:dbfiddle.uk

最“内部”的子查询 ( x) 执行从 (21) 列到 (21) 行的逆透视:

            ( values (a),(b),(c),(d),(e),(f),(g),(h),(i),(j),
                     (k),(l),(m),(n),(o),(p),(q),(r),(s),(t),(u)
            ) as x (val)
Run Code Online (Sandbox Code Playgroud)

然后第二个 ( y) 按值对它们进行排序并只选择其中的一半:

        ( select top (11) x.val
          from
            --
            --
               x (val)
          order by x.val
        ) as y
Run Code Online (Sandbox Code Playgroud)

第三个 ( z) 选择最后一个(11 行中),以找到中位数。


处理空值的不同方法如下。self join andGROUP BY用于对更小和更高的值进行“滚动”计数:

select t.id, w.val
from temp as t
  cross apply
    ( select val = avg(val)
      from 
        ( select d.val, 
                 cnt_less = count(case when y.val < d.val then 1 end),
                 cnt_more = count(case when y.val > d.val then 1 end),
                 cnt = count(y.val)
          from
              ( select distinct val
                from 
                  ( values (a),(b),(c),(d),(e),(f),(g),(h),(i),(j),
                           (k),(l),(m),(n),(o),(p),(q),(r),(s),(t),(u)
                  ) as x (val)
                where x.val is not null
              ) as d
            cross join
              ( values (a),(b),(c),(d),(e),(f),(g),(h),(i),(j),
                       (k),(l),(m),(n),(o),(p),(q),(r),(s),(t),(u)
              ) as y (val)
          group by d.val
        ) as z
      where 2 * cnt_less <= cnt
        and 2 * cnt_more <= cnt
    ) as w ;
Run Code Online (Sandbox Code Playgroud)