从表

时间:2017-04-14 13:41:18

标签: sql sql-server common-table-expression

我的桌子有很多状态,比如

Id | Date | IsEnabled | IsUpdated | IsDuplicate | IsSuspended | ...

状态(IsEnabled,IsUpdated,IsDuplicate,IsSuspended ......)是可以为空的位。

我需要从此表中选择最新(但不大于某些输入日期)不可为空的状态。如果某些状态具有NULL值,则选择以前不可为空的值。

我创建了select以仅选择最新值,并且无法理解如何获取以前不可为空的值。

;WITH CTE AS ( 
    SELECT  cbs.*, rn = ROW_NUMBER() OVER (PARTITION BY cbs.Id ORDER BY cbs.[Date] DESC)
    FROM    [dbo].CompanyBusinessStatus cbs
    WHERE cbs.[Date] <= @inputDate
)

SELECT  *
FROM    CTE
WHERE rn = 1

我正在使用MS SQL 2016

数据示例:

1 | 2017-01-01 | 1 | 0    | 0    | 0
_______________________________________
1 | 2017-01-03 | 1 | NULL | NULL | 1
_______________________________________
2 | 2017-01-03 | 1 | 1    | NULL | 0
_______________________________________
1 | 2017-01-05 | 0 | 1    | 0    | NULL

如果@inputDate是&#39; 2017-01-04&#39;我需要选择

   Id | IsEnabled | IsUpdated | IsDuplicate | IsSuspended 
_________________________________________________________
    1 | 1         | 0         | 0           | 1
_________________________________________________________
    2 | 1         | 1         | NULL        | 0

4 个答案:

答案 0 :(得分:1)

单向(demo)将是

SELECT Id,
       IsEnabled = CAST(RIGHT(MAX(yyyymmdd + CAST(IsEnabled AS CHAR(1))), 1) AS BIT),
       IsUpdated = CAST(RIGHT(MAX(yyyymmdd + CAST(IsUpdated AS CHAR(1))), 1) AS BIT),
       IsDuplicate = CAST(RIGHT(MAX(yyyymmdd + CAST(IsDuplicate AS CHAR(1))), 1) AS BIT),
       IsSuspended = CAST(RIGHT(MAX(yyyymmdd + CAST(IsSuspended AS CHAR(1))), 1) AS BIT)
FROM   dbo.CompanyBusinessStatus cbs
       CROSS APPLY (SELECT FORMAT(Date, 'yyyyMMdd')) CA(yyyymmdd)
WHERE  cbs.[Date] <= @inputDate
GROUP  BY Id 

如果您在id上有覆盖索引(或者即使您没有获得哈希聚合),这可能会产生一个根本没有排序操作的计划,并且可能比Gordon便宜得多#&# 39;答案。

enter image description here

答案 1 :(得分:0)

对于下面的查询,我认为Order by中的ROW_NUMBER会将最少NULL的记录作为输出的第一个记录。

WITH CTE AS ( 
    SELECT  cbs.*, rn = ROW_NUMBER() OVER (PARTITION BY cbs.Id ORDER BY cbs.[Date] DESC, IsEnabled DESC,IsUpdated DESC,IsDuplicate DESC,IsSuspended DESC)
    FROM    [dbo].CompanyBusinessStatus cbs
    WHERE cbs.[Date] <= @inputDate
)

SELECT  *
FROM    CTE
WHERE rn = 1

答案 2 :(得分:0)

我的另一个答案明显错误地解释了这个问题。不幸的是,SQL Server仅提供FIRST_VALUE()作为窗口函数。所以,这是一种方法:

SELECT DISTINCT cbs.id,
       MAX(cbs.date) OVER (PARTITION BY cbs.id) as date,
       FIRST_VALUE(IsEnabled) OVER (PARTITION BY cbs.id ORDER BY (CASE WHEN IsEnabled IS NULL THEN 2 ELSE 1 END), cbs.date DESC) as isEnabled,
       FIRST_VALUE(IsUpdated) OVER (PARTITION BY cbs.id ORDER BY (CASE WHEN IsUpdated IS NULL THEN 2 ELSE 1 END), cbs.date DESC) as IsUpdated,
       . . .
FROM [dbo].CompanyBusinessStatus cbs
WHERE cbs.[Date] <= @inputDate ;

为此目的,我不是SELECT DISTINCT的粉丝,但它似乎是表达逻辑的最简单方法。

ANSI SQL为IGNORE NULL(以及其他一些窗口函数)提供FIRST_VALUE()选项。但是,SQL Server(尚未)支持此选项。

答案 3 :(得分:0)

我知道做你想做的唯一方法是为每个“状态”列做一个相关的子查询。编写的SQL很多,并且看起来不是很优雅,但它肯定适用于任何版本的SQL Server。

可能有一个更优雅的解决方案,包括UNPIVOTing然后RE-PIVOTing,但除非我有超过20个不同的“状态”列,否则我不会去那条路。