在行中查找last not null值的列名

时间:2012-08-03 18:33:02

标签: sql-server-2008

表格就像这样

ID A1   A2   A3   A4   A5   A6   A7   A8   A9
1  YE  YE    YE  NULL  YE   YE   YE  NULL NULL
2  YE  YE    YE  NULL NULL NULL NULL NULL NULL
3  YE  YE    YE   YE   YE    YE   YE  YE  NULL

ID是主键。
我想在一行中获取最后一个非空值的列名,结果就像这样

ID LAST
1   A7
2   A3
3   A8

对此有何帮助?

3 个答案:

答案 0 :(得分:2)

尽管我对这种模式存有疑虑,但请考虑这种“反向优先”条件:

select
  id,
  case
    -- first match terminates search
    when A9 is not null then 'A9'
    when A8 is not null then 'A8'
    when A7 is not null then 'A7'
    ..
    else null
  as lastNonNullColumn
from ..

TSQL保证了评估顺序(参见CASE),所以我们只是向后推进:)

  

按照指定的顺序评估每个WHEN子句的Boolean_expression。

此外,也许可以使用UNPIVOT(或ROLLUP [?]或手动UNION)。也就是说,将固定的列名集转换为值,然后它就是一个简单的查询..也就是说,如果表被规范化,这可以很容易地完成: - )

select
  id,
  max(colName) as lastNonNullColumn
from <<normalized_derived_table>>
where colValue is not null
group by id

答案 1 :(得分:2)

这个怎么样?它使用UNPIVOT转换数据,然后您将选择非空/空白的最后一个值。

;with cte as
(
  select id
    , last
    , value
    , row_number() over(partition by id order by last) rn
  from
  (
      select id, 
          isnull(a1, '') as a1, 
          isnull(a2, '') as a2, 
          isnull(a3, '') as a3, 
          isnull(a4, '') as a4, 
          isnull(a5, '') as a5, 
          isnull(a6, '') as a6, 
          isnull(a7, '') as a7, 
          isnull(a8, '') as a8, 
          isnull(a9, '') as a9
      from t
  ) x
  unpivot
  (
      value
      for last in (a1, a2, a3, a4, a5, a6, a7, a8, a9)
  ) u
) 
select id, max(last) as last
from cte
where value != ''
group by id

请参阅SQL Fiddle with Demo

编辑,实际上它不需要那么复杂:

select id
  , max(last) last
from
(
    select id, a1, a2, a3, a4, a5, a6, a7, a8, a9
    from t
) x
unpivot
(
    value
    for last in (a1, a2, a3, a4, a5, a6, a7, a8, a9)
) u
group by id

请参阅SQL Fiddle with Demo

答案 2 :(得分:1)

这是一个伪UNPIVOT版本,允许您指定列的顺序(如果列名称不按其位置排序)。

SELECT
   T.ID,
   X.Name
FROM
   T
   CROSS APPLY (
      SELECT TOP 1 Name FROM (
         VALUES (1, 'A1', T.A1), (2, 'A2', T.A2), (3, 'A3', T.A3), (4, 'A4', T.A4),
         (5, 'A5', T.A5), (6, 'A6', T.A6), (7, 'A7', T.A7), (8, 'A8', T.A8),
         (9, 'A9', T.A9)
      ) X (Pos, Name, Col)
      WHERE Col IS NOT NULL
      ORDER BY X.Pos DESC
   ) X;

然而,虽然实际的IO和CPU并不比自然的UNPIVOT方法差(执行计划看起来很糟糕,但真正的服务器影响并没有那么差),但这并不是最好的表现者。 @pst给出的简单CASE表达式是。

假设列名可以按原样排序,UNPIVOT可以进一步简化:

SELECT ID, Max(Last)
FROM T UNPIVOT (Value FOR Last IN (A1, A2, A3, A4, A5, A6, A7, A8, A9)) U
GROUP BY ID;

最后,这是一个我想到的疯狂版本,不幸的是比其他版本表现更差:

SELECT
   T.ID,
   Coalesce(
      (SELECT 'A9' WHERE T.A9 IS NOT NULL),
      (SELECT 'A8' WHERE T.A8 IS NOT NULL),
      (SELECT 'A7' WHERE T.A7 IS NOT NULL),
      (SELECT 'A6' WHERE T.A6 IS NOT NULL),
      (SELECT 'A5' WHERE T.A5 IS NOT NULL),
      (SELECT 'A4' WHERE T.A4 IS NOT NULL),
      (SELECT 'A3' WHERE T.A3 IS NOT NULL),
      (SELECT 'A2' WHERE T.A2 IS NOT NULL),
      (SELECT 'A1' WHERE T.A1 IS NOT NULL)
   ) LastNotNullColumn
FROM T
ORDER BY ID

从理论上讲,引擎可能会提出一个看起来更像CASE表达版本的计划,但事实并非如此。该计划看起来非常疯狂,每个select语句有一个表对象,并且占用大约两倍的CPU作为CASE表达式。

我测试的所有版本使用相同数量的逻辑读取,仅在CPU中有所不同。我用15,000行来测试。

最后,我不能在良心上警告你,你的架构可能不是最好的。虽然我无法分辨您的数据是什么,但您试图找到最后一个数据可能表明这些列表示某个生命周期的时间或阶段 - 这不是正确的数据库设计。相反,存储数据不透明。当您需要一个旋转的结果集时,您可以PIVOT。而且,查询每个ID的最新值会变得更简单一些!