如何选择Sql Server中不存在的记录

时间:2014-02-11 01:38:41

标签: sql sql-server tsql

这不是在选择记录之前检查记录是否存在,反之亦然。

问题在于,我有一个包含许多记录的数据库,它们按日期存储,按日具体,每天应该有一组值,但有些日子可能根本没有任何值,它们可能不存在。因此,当我尝试进行查询时,我需要显示所有数据,即使它在技术上不存在,它至少可以显示为空白但应该显示。

想想这张表:

 ___________________________________
| id |    date    | value1 | value2 |
|  1 | 02/01/2014 |    1   |    1   |
|  2 | 02/03/2014 |    2   |    2   |
|  3 | 02/04/2014 |    3   |    3   |
|  4 | 02/06/2014 |    4   |    4   |

现在查询需要做的是返回特定日期范围之间的所有数据,例如从02/01/201402/03/2014,它应该返回该日期范围之间的所有值,比如说:

SELECT * FROM myTable WHERE date>=`02/01/2014` AND date<=`02/03/2014`

但这只会归还:

 ___________________________________
| id |    date    | value1 | value2 |
|  1 | 02/01/2014 |    1   |    1   |
|  2 | 02/03/2014 |    2   |    2   |

现在我知道这是相当明显的,它是根据我发送给查询的范围选择表中存在的所有数据,但我正在处理一个不关心是否有值的报告无论是否给定,它必须每天返回,即使当天有记录,也就是说,即使表中没有02/02/2014,也必须返回。

有点像这样:

 ___________________________________
| id |    date    | value1 | value2 |
|  1 | 02/01/2014 |    1   |    1   |
|    | 02/02/2014 |        |        |
|  2 | 02/03/2014 |    2   |    2   |

它没有数据没关系,它只是不能离开这一天,好像它没有发生。

我已尝试使用COALESCEISNULL,但由于那些日子没有值,因此也没有空值,因此它们只是无法用其他内容替换Null

有没有人有任何想法?

3 个答案:

答案 0 :(得分:3)

您可以使用递归CTE执行此操作。像这样:

DECLARE @startDate datetime = '2/1/2014'
DECLARE @endDate datetime = '2/6/2014'

;WITH DateRange(RunningDate) AS
(
    SELECT @startDate AS RunningDate
    UNION ALL
    SELECT RunningDate + 1
    FROM DateRange
    WHERE RunningDate < @endDate
)

SELECT id, RunningDate date, value1, value2 
FROM DateRange LEFT JOIN myTable ON myTable.date = DateRange.RunningDate

编辑... 如果您使用此解决方案(请注意Aaron Bertrand在我的回答中的评论),请注意,如果您还需要指定max recursion打算处理超过3个月的范围。默认设置为100.这意味着,当查询当前写入时,它最多只能执行101个日期(100个递归级别)。

您可以在最后OPTION (MAXRECURSION 0)的末尾另外指定SELECT来克服此问题。 0表示无限级别的递归。但请再次注意亚伦的帖子。

答案 1 :(得分:3)

这应该有效:

WITH
    -- Numbers CTE, courtesy Itzik Ben-Gan
  L0   AS(SELECT 1 AS c UNION ALL SELECT 1),
  L1   AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
  L2   AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
  L3   AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
  L4   AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
  Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L4),
    -- Turn into Dates
  Dates AS(SELECT CAST('18991231' AS DATETIME)+n AS d FROM Nums)
SELECT * 
FROM Dates d
LEFT JOIN myTable t ON d.d = t.[date]
WHERE d.d>='02/01/2014' AND d.d<='02/03/2014'

这是针对较小日期范围进行了优化的版本:

DECLARE @startDate datetime = '2/1/2014'
DECLARE @endDate datetime = '2/3/2014'
DECLARE @days as INT = DATEDIFF(dd, @startDate, @enddate) + 1
;
WITH
    -- Numbers CTE, courtesy Itzik Ben-Gan
  L0   AS(SELECT 1 AS c UNION ALL SELECT 1),
  L1   AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
  L2   AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
  L3   AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
  L4   AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
  T4   AS(SELECT TOP (@days) 1 AS c FROM L4),
  Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS n FROM T4),
    -- Turn into Dates
  Dates AS(SELECT @startDate+n AS d FROM Nums)
SELECT * 
FROM Dates d
LEFT JOIN myTable t ON d.d = t.[date]
WHERE d.d>=@startDate AND d.d<=@endDate

答案 2 :(得分:1)

Eric Fan有正确的想法,但我认为看到实施可能会有所帮助:

这是一个函数,它将返回包含日期的表格:

CREATE FUNCTION dbo.fDatesBetween(@startDate Date, @endDate Date)
    RETURNS @tbl TABLE
        (
            a_date      Date
        )
AS BEGIN
    DECLARE @thisDt Date
    SET @thisDt = @startDate

    WHILE @thisDt <= @endDate
    BEGIN
        INSERT INTO @tbl SELECT @thisDt
        SET @thisDt = DateAdd(day, 1, @thisDt)
    END
    RETURN
END

现在,如果从函数结果中对表进行外连接, 你会得到你想要的东西:

SELECT DATES.a_date, SampleTable.value1, SampleTable.value2
  FROM dbo.fDatesBetween(@startDate, @endDate) DATES
       LEFT JOIN SampleTable
          ON DATES.a_date = SampleTable.dt

那应该能满足你的要求。