这不是在选择记录之前检查记录是否存在,反之亦然。
问题在于,我有一个包含许多记录的数据库,它们按日期存储,按日具体,每天应该有一组值,但有些日子可能根本没有任何值,它们可能不存在。因此,当我尝试进行查询时,我需要显示所有数据,即使它在技术上不存在,它至少可以显示为空白但应该显示。
想想这张表:
___________________________________
| 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/2014
到02/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 |
它没有数据没关系,它只是不能离开这一天,好像它没有发生。
我已尝试使用COALESCE
和ISNULL
,但由于那些日子没有值,因此也没有空值,因此它们只是无法用其他内容替换Null
有没有人有任何想法?
答案 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)
这是一个函数,它将返回包含日期的表格:
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
那应该能满足你的要求。