IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[vGetVisits]') AND type in (N'U'))
DROP TABLE [dbo].[vGetVisits]
GO
CREATE TABLE [dbo].[vGetVisits](
[id] [int] NOT NULL,
[mydate] [datetime] NOT NULL,
CONSTRAINT [PK_vGetVisits] PRIMARY KEY CLUSTERED
(
[id] ASC
)
)
GO
INSERT INTO [dbo].[vGetVisits]([id], [mydate])
VALUES
(1, '2014-01-01 11:00'),
(2, '2014-01-03 10:00'),
(3, '2014-01-04 09:30'),
(4, '2014-04-01 10:00'),
(5, '2014-05-01 11:00'),
(6, '2014-07-01 09:00'),
(7, '2014-07-31 08:00');
GO
-- Clean up
IF OBJECT_ID (N'dbo.udfLastHitRecursive', N'FN') IS NOT NULL
DROP FUNCTION udfLastHitRecursive;
GO
-- Actual Function
CREATE FUNCTION dbo.udfLastHitRecursive
( @MyDate datetime)
RETURNS TINYINT
AS
BEGIN
-- Your returned value 1 or 0
DECLARE @Returned_Value TINYINT;
SET @Returned_Value=0;
-- Prepare gaps table to be used.
WITH gaps AS
(
-- Select Date and MaxDiff from the original table
SELECT
CONVERT(Date,mydate) AS [date]
, DATEDIFF(day,ISNULL(LAG(mydate, 1) OVER (ORDER BY mydate), mydate) , mydate) AS [MaxDiff]
FROM dbo.vGetVisits
)
SELECT @Returned_Value=
(SELECT DISTINCT -- DISTINCT in case we have same date but different time
CASE WHEN
(
-- It is a first entry
[date]=(SELECT MIN(CONVERT(Date,mydate)) FROM dbo.vGetVisits))
OR
/*
--Gap between last qualifying date and entered is greater than 90
Calculate Running sum upto and including required date
and find a remainder of division by 91.
*/
((SELECT SUM(t1.MaxDiff)
FROM (SELECT [MaxDiff] FROM gaps WHERE [date]<=t2.[date]
) t1
)%91 -
/*
ISNULL added to include first value that always returns NULL
Calculate Running sum upto and NOT including required date
and find a remainder of division by 91
*/
ISNULL((SELECT SUM(t1.MaxDiff)
FROM (SELECT [MaxDiff] FROM gaps WHERE [date]<t2.[date]
) t1
)%91, 0) -- End ISNULL
<0 )
/* End Running sum upto and including required date */
OR
-- Gap between two nearest dates is greater than 90
((SELECT SUM(t1.MaxDiff)
FROM (SELECT [MaxDiff] FROM gaps WHERE [date]<=t2.[date]
) t1
) - ISNULL((SELECT SUM(t1.MaxDiff)
FROM (SELECT [MaxDiff] FROM gaps WHERE [date]<t2.[date]
) t1
), 0) > 90)
THEN 1
ELSE 0
END
AS [Qualifying]
FROM gaps t2
WHERE [date]=CONVERT(Date,@MyDate))
-- What is neccesary to return when entered date is not in dbo.vGetVisits?
RETURN @Returned_Value
END
GO
SELECT
dbo.udfLastHitRecursive(mydate) AS [Qualifying]
, [id]
, mydate
FROM dbo.vGetVisits
ORDER BY mydate
Kết quả
Ngoài ra, hãy xem Cách tính Tổng chạy trong SQL Server
cập nhật: vui lòng xem bên dưới kết quả kiểm tra hiệu suất.
Do logic khác nhau được sử dụng trong việc tìm kiếm "khoảng cách 90 ngày" của ypercube và các giải pháp của tôi nếu còn nguyên vẹn có thể trả về các kết quả khác nhau cho giải pháp của Paul White. Điều này là do việc sử dụng các hàm DATEDIFF và DATEADD tương ứng.
Ví dụ:
SELECT DATEADD(DAY, 90, '2014-01-01 00:00:00.000')
trả về '2014-04-01 00: 00: 00.000' có nghĩa là '2014-04-01 01: 00: 00.000' vượt quá khoảng cách 90 ngày
nhưng
SELECT DATEDIFF(DAY, '2014-01-01 00:00:00.000', '2014-04-01 01:00:00.000')
Trả về '90' có nghĩa là nó vẫn nằm trong khoảng trống.
Hãy xem xét một ví dụ về một nhà bán lẻ. Trong trường hợp này, bán một sản phẩm dễ hỏng đã bán theo ngày '2014-01-01' vào '2014-01-01 23: 59: 59: 999' là ổn. Vì vậy, giá trị DATEDIFF (NGÀY, ...) trong trường hợp này là OK.
Một ví dụ khác là một bệnh nhân đang chờ được nhìn thấy. Đối với ai đó đến vào '2014-01-01 00: 00: 00: 000' và rời đi vào '2014-01-01 23: 59: 59: 999' thì đó là 0 (không) ngày nếu DATEDIFF được sử dụng ngay cả khi chờ đợi thực tế là gần 24 giờ. Một lần nữa, bệnh nhân đến vào '2014-01-01 23:59:59' và bỏ đi lúc '2014-01 / 02 00:00:01' chờ đợi một ngày nếu DATEDIFF được sử dụng.
Nhưng tôi lạc đề.
Tôi đã để lại các giải pháp DATEDIFF và thậm chí hiệu suất đã thử nghiệm những giải pháp đó nhưng chúng thực sự nên ở trong giải đấu của riêng họ.
Ngoài ra, lưu ý rằng đối với các bộ dữ liệu lớn, không thể tránh các giá trị trong cùng ngày. Vì vậy, nếu chúng ta đã nói 13 triệu bản ghi trong 2 năm dữ liệu thì cuối cùng chúng ta sẽ có nhiều hơn một bản ghi trong một số ngày. Những hồ sơ đó đang được lọc ra trong cơ hội sớm nhất trong các giải pháp DATEDIFF của tôi và ypercube. Hy vọng ypercube không bận tâm điều này.
Các giải pháp đã được thử nghiệm trên bảng sau
CREATE TABLE [dbo].[vGetVisits](
[id] [int] NOT NULL,
[mydate] [datetime] NOT NULL,
)
với hai chỉ mục được nhóm khác nhau (mydate trong trường hợp này):
CREATE CLUSTERED INDEX CI_mydate on vGetVisits(mydate)
GO
Bảng được điền theo cách sau
SET NOCOUNT ON
GO
INSERT INTO dbo.vGetVisits(id, mydate)
VALUES (1, '01/01/1800')
GO
DECLARE @i bigint
SET @i=2
DECLARE @MaxRows bigint
SET @MaxRows=13001
WHILE @i<@MaxRows
BEGIN
INSERT INTO dbo.vGetVisits(id, mydate)
VALUES (@i, DATEADD(day,FLOOR(RAND()*(3)),(SELECT MAX(mydate) FROM dbo.vGetVisits)))
SET @i=@i+1
END
Đối với trường hợp hàng triệu triệu, INSERT đã được thay đổi theo cách sao cho các mục nhập 0-20 phút được thêm ngẫu nhiên.
Tất cả các giải pháp được gói cẩn thận trong mã sau đây
SET NOCOUNT ON
GO
DECLARE @StartDate DATETIME
SET @StartDate = GETDATE()
--- Code goes here
PRINT 'Total milliseconds: ' + CONVERT(varchar, DATEDIFF(ms, @StartDate, GETDATE()))
Mã thực tế được kiểm tra (không theo thứ tự cụ thể):
Giải pháp DATEDIFF của Ypercube ( YPC, DATEDIFF )
DECLARE @cd TABLE
( TheDate datetime PRIMARY KEY,
Qualify INT NOT NULL
);
DECLARE
@TheDate DATETIME,
@Qualify INT = 0,
@PreviousCheckDate DATETIME = '1799-01-01 00:00:00'
DECLARE c CURSOR
LOCAL STATIC FORWARD_ONLY READ_ONLY
FOR
SELECT
mydate
FROM
(SELECT
RowNum = ROW_NUMBER() OVER(PARTITION BY cast(mydate as date) ORDER BY mydate)
, mydate
FROM
dbo.vGetVisits) Actions
WHERE
RowNum = 1
ORDER BY
mydate;
OPEN c ;
FETCH NEXT FROM c INTO @TheDate ;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Qualify = CASE WHEN DATEDIFF(day, @PreviousCheckDate, @Thedate) > 90 THEN 1 ELSE 0 END ;
IF @Qualify=1
BEGIN
INSERT @cd (TheDate, Qualify)
SELECT @TheDate, @Qualify ;
SET @PreviousCheckDate=@TheDate
END
FETCH NEXT FROM c INTO @TheDate ;
END
CLOSE c;
DEALLOCATE c;
SELECT TheDate
FROM @cd
ORDER BY TheDate ;
Giải pháp DATEADD của Ypercube ( YPC, DATEADD )
DECLARE @cd TABLE
( TheDate datetime PRIMARY KEY,
Qualify INT NOT NULL
);
DECLARE
@TheDate DATETIME,
@Next_Date DATETIME,
@Interesting_Date DATETIME,
@Qualify INT = 0
DECLARE c CURSOR
LOCAL STATIC FORWARD_ONLY READ_ONLY
FOR
SELECT
[mydate]
FROM [test].[dbo].[vGetVisits]
ORDER BY mydate
;
OPEN c ;
FETCH NEXT FROM c INTO @TheDate ;
SET @Interesting_Date=@TheDate
INSERT @cd (TheDate, Qualify)
SELECT @TheDate, @Qualify ;
WHILE @@FETCH_STATUS = 0
BEGIN
IF @TheDate>DATEADD(DAY, 90, @Interesting_Date)
BEGIN
INSERT @cd (TheDate, Qualify)
SELECT @TheDate, @Qualify ;
SET @Interesting_Date=@TheDate;
END
FETCH NEXT FROM c INTO @TheDate;
END
CLOSE c;
DEALLOCATE c;
SELECT TheDate
FROM @cd
ORDER BY TheDate ;
Giải pháp của Paul White ( PW )
;WITH CTE AS
(
SELECT TOP (1)
T.[mydate]
FROM dbo.vGetVisits AS T
ORDER BY
T.[mydate]
UNION ALL
SELECT
SQ1.[mydate]
FROM
(
SELECT
T.[mydate],
rn = ROW_NUMBER() OVER (
ORDER BY T.[mydate])
FROM CTE
JOIN dbo.vGetVisits AS T
ON T.[mydate] > DATEADD(DAY, 90, CTE.[mydate])
) AS SQ1
WHERE
SQ1.rn = 1
)
SELECT
CTE.[mydate]
FROM CTE
OPTION (MAXRECURSION 0);
Giải pháp DATEADD của tôi ( PN, DATEADD )
DECLARE @cd TABLE
( TheDate datetime PRIMARY KEY
);
DECLARE @TheDate DATETIME
SET @TheDate=(SELECT MIN(mydate) as mydate FROM [dbo].[vGetVisits])
WHILE (@TheDate IS NOT NULL)
BEGIN
INSERT @cd (TheDate) SELECT @TheDate;
SET @TheDate=(
SELECT MIN(mydate) as mydate
FROM [dbo].[vGetVisits]
WHERE mydate>DATEADD(DAY, 90, @TheDate)
)
END
SELECT TheDate
FROM @cd
ORDER BY TheDate ;
Giải pháp DATEDIFF của tôi ( PN, DATEDIFF )
DECLARE @MinDate DATETIME;
SET @MinDate=(SELECT MIN(mydate) FROM dbo.vGetVisits);
;WITH gaps AS
(
SELECT
t1.[date]
, t1.[MaxDiff]
, SUM(t1.[MaxDiff]) OVER (ORDER BY t1.[date]) AS [Running Total]
FROM
(
SELECT
mydate AS [date]
, DATEDIFF(day,LAG(mydate, 1, mydate) OVER (ORDER BY mydate) , mydate) AS [MaxDiff]
FROM
(SELECT
RowNum = ROW_NUMBER() OVER(PARTITION BY cast(mydate as date) ORDER BY mydate)
, mydate
FROM dbo.vGetVisits
) Actions
WHERE RowNum = 1
) t1
)
SELECT [date]
FROM gaps t2
WHERE
( ([Running Total])%91 - ([Running Total]- [MaxDiff])%91 <0 )
OR
( [MaxDiff] > 90)
OR
([date]=@MinDate)
ORDER BY [date]
Tôi đang sử dụng SQL Server 2012, vì vậy xin lỗi Mikael Eriksson, nhưng mã của anh ấy sẽ không được kiểm tra ở đây. Tôi vẫn mong đợi các giải pháp của anh ấy với DATADIFF và DATEADD sẽ trả về các giá trị khác nhau trên một số bộ dữ liệu.
Và kết quả thực tế là: