我的表格有两列,如下所示:
+-----------+------------+
| FROM | TO |
+-----------+------------+
|2015-03-01 | 2015-03-04 |
|2015-03-05 | 2015-03-09 |
+-----------+------------+
我想写一个函数,它将接受两个参数 - DateFrom和DateTo,并检查这个间隔。例如,如果函数将DateFrom = 2015-03-03
和DateTo = 2015-03-08
作为参数,则应返回true
,因为此时间间隔内的每一天都在表中。
但如果表格是这样的:
+-----------+------------+
| FROM | TO |
+-----------+------------+
|2015-03-01 | 2015-03-04 |
|2015-03-06 | 2015-03-09 |
+-----------+------------+
该函数应返回false
,因为2015-03-05
不在表格中。算法的任何想法?谢谢你的帮助。
答案 0 :(得分:5)
如果您还没有日历表,可以使用计数表:
DECLARE @t TABLE
(
FromDate DATE ,
ToDate DATE
)
INSERT INTO @t
VALUES ( '2015-03-01', '2015-03-04' ),
( '2015-03-05', '2015-03-09' )
DECLARE @from DATE = '2015-03-03', @to DATE = '2015-03-08'
;WITH cte1 AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS d
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)),
cte2 AS(SELECT DATEADD(dd, d - 1, @from) AS d
FROM cte1
WHERE DATEADD(dd, d - 1, @from) <= @to)
SELECT CASE WHEN EXISTS ( SELECT *
FROM cte2
WHERE NOT EXISTS ( SELECT *
FROM @t t
WHERE d BETWEEN t.FromDate AND t.ToDate ) )
THEN 0
ELSE 1
END AS IntervalExists
它将适用于1000天差异的间隔。如果需要更多,只需添加更多cross joins
(一个cross join
会将间隔乘以10)。
答案 1 :(得分:0)
如果要对表执行此操作以传递表中的每一行。我建议你使用usp(用户存储过程)
以下是执行相同操作的示例代码:
CREATE PROC usp_CheckInterval
(
@DateFrom Date ,
@DateTo Date ,
@ReturnStatus bit OUTPUT
)
AS
BEGIN TRY
SET NOCOUNT ON;
IF(EXISTS (SELECT TOP 1 * FROM [YourTable] WHERE [StartDate] < @DateFrom AND [EndDate] > @DateTo))
BEGIN
SET @ReturnStatus = 0
END
ELSE
BEGIN
SET @ReturnStatus = 1
END
END TRY
BEGIN CATCH
--Catch Here
END CATCH;
示例执行:
DECLARE @RC int
DECLARE @DateFrom date = GETDATE()
DECLARE @DateTo date = GETDATE()
DECLARE @ReturnStatus bit
-- TODO: Set parameter values here.
EXECUTE @RC = [dbo].[usp_CheckInterval]
@DateFrom
,@DateTo
,@ReturnStatus OUTPUT
SELECT @ReturnStatus
让我知道它是如何实现的。
答案 2 :(得分:0)
我的建议是创建正在运行的数字的功能:
CREATE FUNCTION [dbo].[GetRunningNumbers](@counter INT=10000000, @StartAt INT=0)
RETURNS TABLE
AS
RETURN
WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally AS(
SELECT TOP(ISNULL(@counter,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(@StartAt,0) As Nmbr
FROM E8
)
SELECT * FROM CteTally;
GO
使用此功能,您可以将其作为单行:
DECLARE @tbl TABLE(d1 DATETIME, d2 DATETIME);
INSERT INTO @tbl VALUES({d'2015-03-01'},{d'2015-03-04'})
,({d'2015-03-05'},{d'2015-03-09'});
DECLARE @CheckStart DATETIME={d'2015-03-03'};
DECLARE @CheckEnd DATETIME={d'2015-03-08'};
--Check if all dates between these two checkdates are coverd within your table (be aware of the possible time component of a DATETIME!):
WITH AllCheckDates AS
(
SELECT DATEADD(DAY,Nmbr,@CheckStart) AS RunningDate
FROM dbo.GetRunningNumbers(DATEDIFF(DAY,@CheckStart,@CheckEnd)+1,0)
)
SELECT CASE WHEN EXISTS ( SELECT * FROM AllCheckDates WHERE NOT EXISTS ( SELECT * FROM @tbl AS t WHERE RunningDate BETWEEN t.d1 AND t.d2 ) )
THEN 0 ELSE 1 END AS AllDatesCoveredInIntervall
现在再次与2015-04-05的差距:
DELETE FROM @tbl;
INSERT INTO @tbl VALUES({d'2015-03-01'},{d'2015-03-04'})
,({d'2015-03-06'},{d'2015-03-09'});
--Same query, should deliver false now...
WITH AllCheckDates AS
(
SELECT DATEADD(DAY,Nmbr,@CheckStart) AS RunningDate
FROM dbo.GetRunningNumbers(DATEDIFF(DAY,@CheckStart,@CheckEnd)+1,0)
)
SELECT CASE WHEN EXISTS ( SELECT * FROM AllCheckDates WHERE NOT EXISTS ( SELECT * FROM @tbl AS t WHERE RunningDate BETWEEN t.d1 AND t.d2 ) )
THEN 0 ELSE 1 END AS AllDatesCoveredInIntervall
GO
只是为了清理...
DROP FUNCTION dbo.GetRunningNumbers;
GO