Cách tìm đệ quy các khoảng trống trong 90 ngày trôi qua, giữa các hàng


17

Đây là một loại nhiệm vụ tầm thường trong quê hương C # của tôi, nhưng tôi chưa thực hiện bằng SQL và muốn giải quyết nó dựa trên tập hợp (không có con trỏ). Một kết quả nên đến từ một truy vấn như thế này.

SELECT SomeId, MyDate, 
    dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T

Nó nên hoạt động như thế nào

Tôi gửi ba thông số đó vào UDF.
UDF sử dụng nội bộ các tham số để tìm nạp các hàng cũ hơn có liên quan <= 90 ngày, từ chế độ xem.
UDF đi qua 'MyDate' và trả về 1 nếu cần đưa vào tính toán tổng.
Nếu không nên thì nó trả về 0. Được đặt tên ở đây là "đủ điều kiện".

Udf sẽ làm gì

Liệt kê các hàng theo thứ tự ngày. Tính số ngày giữa các hàng. Hàng đầu tiên trong mặc định kết quả là Hit = 1. Nếu chênh lệch lên tới 90, - sau đó chuyển sang hàng tiếp theo cho đến khi tổng số khoảng trống là 90 ngày (ngày thứ 90 phải vượt qua) Khi đạt được, đặt Hit thành 1 và đặt lại khoảng cách về 0 Thay vào đó, nó cũng hoạt động để bỏ qua hàng từ kết quả.

                                          |(column by udf, which not work yet)
Date              Calc_date     MaxDiff   | Qualifying
2014-01-01 11:00  2014-01-01    0         | 1
2014-01-03 10:00  2014-01-01    2         | 0
2014-01-04 09:30  2014-01-03    1         | 0
2014-04-01 10:00  2014-01-04    87        | 0
2014-05-01 11:00  2014-04-01    30        | 1

Trong bảng trên, cột MaxDiff là khoảng cách từ ngày trong dòng trước. Vấn đề với những nỗ lực của tôi cho đến nay là tôi không thể bỏ qua hàng cuối cùng thứ hai trong mẫu ở trên.

[EDIT]
Theo nhận xét, tôi thêm một thẻ và cũng dán udf tôi vừa biên dịch. Mặc dù, chỉ là một giữ chỗ và sẽ không cho kết quả hữu ích.

;WITH cte (someid, otherkey, mydate, cost) AS
(
    SELECT someid, otherkey, mydate, cost
    FROM dbo.vGetVisits
    WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey 
    AND CONVERT(Date,mydate) = @VisitDate

    UNION ALL

    SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
    FROM dbo.vGetVisits AS E
    WHERE CONVERT(date, e.mydate) 
        BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
        AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey 
        AND CONVERT(Date,e.mydate) = @VisitDate
        order by e.mydate
)

Tôi có một truy vấn khác mà tôi xác định riêng gần với những gì tôi cần, nhưng bị chặn với thực tế tôi không thể tính toán trên các cột có cửa sổ. Tôi cũng đã thử một ứng dụng tương tự cho đầu ra giống nhau hoặc ít hơn chỉ với LAG () trên MyDate, được bao quanh bởi một ngày.

SELECT
    t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM 
(
    SELECT *,
        MaxDiff = LAST_VALUE(Diff.Diff)  OVER (
            ORDER BY Diff.Mydate ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
    FROM 
    (
        SELECT *,
            Diff =  ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
                        ORDER BY r.Mydate ASC
                            ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING), 
                                r.Mydate),0),
            DateDiff =  ISNULL(LAST_VALUE(r.Mydate) OVER (
                        ORDER BY r.Mydate ASC
                            ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING), 
                                r.Mydate)
        FROM dbo.vGetVisits AS r
        WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
    ) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
    AND t.Diff <= 90
ORDER BY
    t.Mydate ASC;

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Paul White nói GoFundMonica

Câu trả lời:


22

Khi tôi đọc câu hỏi, thuật toán đệ quy cơ bản cần có là:

  1. Trả lại hàng có ngày sớm nhất trong bộ
  2. Đặt ngày đó là "hiện tại"
  3. Tìm hàng có ngày sớm nhất hơn 90 ngày sau ngày hiện tại
  4. Lặp lại từ bước 2 cho đến khi không tìm thấy thêm hàng nào

Điều này tương đối dễ thực hiện với biểu thức bảng chung đệ quy.

Ví dụ: sử dụng dữ liệu mẫu sau (dựa trên câu hỏi):

DECLARE @T AS table (TheDate datetime PRIMARY KEY);

INSERT @T (TheDate)
VALUES
    ('2014-01-01 11:00'),
    ('2014-01-03 10:00'),
    ('2014-01-04 09:30'),
    ('2014-04-01 10:00'),
    ('2014-05-01 11:00'),
    ('2014-07-01 09:00'),
    ('2014-07-31 08:00');

Mã đệ quy là:

WITH CTE AS
(
    -- Anchor:
    -- Start with the earliest date in the table
    SELECT TOP (1)
        T.TheDate
    FROM @T AS T
    ORDER BY
        T.TheDate

    UNION ALL

    -- Recursive part   
    SELECT
        SQ1.TheDate
    FROM 
    (
        -- Recursively find the earliest date that is 
        -- more than 90 days after the "current" date
        -- and set the new date as "current".
        -- ROW_NUMBER + rn = 1 is a trick to get
        -- TOP in the recursive part of the CTE
        SELECT
            T.TheDate,
            rn = ROW_NUMBER() OVER (
                ORDER BY T.TheDate)
        FROM CTE
        JOIN @T AS T
            ON T.TheDate > DATEADD(DAY, 90, CTE.TheDate)
    ) AS SQ1
    WHERE
        SQ1.rn = 1
)
SELECT 
    CTE.TheDate 
FROM CTE
OPTION (MAXRECURSION 0);

Kết quả là:

╔═════════════════════════╗
         TheDate         
╠═════════════════════════╣
 2014-01-01 11:00:00.000 
 2014-05-01 11:00:00.000 
 2014-07-31 08:00:00.000 
╚═════════════════════════╝

Với một chỉ mục TheDatelà khóa chính, kế hoạch thực hiện rất hiệu quả:

Kế hoạch thực hiện

Bạn có thể chọn bọc cái này trong một hàm và thực hiện nó trực tiếp với khung nhìn được đề cập trong câu hỏi, nhưng bản năng của tôi chống lại nó. Thông thường, hiệu suất sẽ tốt hơn khi bạn chọn các hàng từ chế độ xem vào bảng tạm thời, cung cấp chỉ mục thích hợp trên bảng tạm thời, sau đó áp dụng logic ở trên. Các chi tiết phụ thuộc vào các chi tiết của chế độ xem, nhưng đây là kinh nghiệm chung của tôi.

Để đầy đủ (và được nhắc bởi câu trả lời của ypercube) Tôi nên đề cập rằng giải pháp xử lý vấn đề khác của tôi cho loại vấn đề này (cho đến khi T-SQL có các hàm tập hợp được sắp xếp đúng) là con trỏ SQLCLR ( xem câu trả lời của tôi ở đây để biết ví dụ về kỹ thuật ). Này thực hiện nhiều hơn một con trỏ T-SQL, và rất thuận tiện cho những người có kỹ năng bằng các ngôn ngữ .NET và khả năng chạy SQLCLR trong môi trường sản xuất của họ. Nó có thể không cung cấp nhiều trong kịch bản này so với giải pháp đệ quy vì phần lớn chi phí là loại, nhưng nó đáng được đề cập.


9

Vì đây câu hỏi SQL Server 2014, tôi cũng có thể thêm phiên bản thủ tục được lưu trữ được biên dịch tự nhiên của một "con trỏ".

Bảng nguồn với một số dữ liệu:

create table T 
(
  TheDate datetime primary key
);

go

insert into T(TheDate) values
('2014-01-01 11:00'),
('2014-01-03 10:00'),
('2014-01-04 09:30'),
('2014-04-01 10:00'),
('2014-05-01 11:00'),
('2014-07-01 09:00'),
('2014-07-31 08:00');

Một kiểu bảng là tham số cho thủ tục được lưu trữ. Điều chỉnh bucket_countphù hợp .

create type TType as table
(
  ID int not null primary key nonclustered hash with (bucket_count = 16),
  TheDate datetime not null
) with (memory_optimized = on);

Và một thủ tục được lưu trữ lặp qua tham số có giá trị của bảng và thu thập các hàng trong @R.

create procedure dbo.GetDates
  @T dbo.TType readonly
with native_compilation, schemabinding, execute as owner 
as
begin atomic with (transaction isolation level = snapshot, language = N'us_english', delayed_durability = on)

  declare @R dbo.TType;
  declare @ID int = 0;
  declare @RowsLeft bit = 1;  
  declare @CurDate datetime = '1901-01-01';
  declare @LastDate datetime = '1901-01-01';

  while @RowsLeft = 1
  begin
    set @ID += 1;

    select @CurDate = T.TheDate
    from @T as T
    where T.ID = @ID

    if @@rowcount = 1
    begin
      if datediff(day, @LastDate, @CurDate) > 90
      begin
        insert into @R(ID, TheDate) values(@ID, @CurDate);
        set @LastDate = @CurDate;
      end;
    end
    else
    begin
      set @RowsLeft = 0;
    end

  end;

  select R.TheDate
  from @R as R;
end

Mã để điền vào một biến bảng được tối ưu hóa bộ nhớ được sử dụng làm tham số cho thủ tục lưu sẵn được biên dịch và gọi thủ tục.

declare @T dbo.TType;

insert into @T(ID, TheDate)
select row_number() over(order by T.TheDate),
       T.TheDate
from T;

exec dbo.GetDates @T;

Kết quả:

TheDate
-----------------------
2014-07-31 08:00:00.000
2014-01-01 11:00:00.000
2014-05-01 11:00:00.000

Cập nhật:

Nếu bạn vì một lý do nào đó không cần phải truy cập vào mỗi hàng trong bảng, bạn có thể thực hiện tương đương với phiên bản "nhảy sang ngày tiếp theo" được thực hiện trong CTE đệ quy của Paul White.

Kiểu dữ liệu không cần cột ID và bạn không nên sử dụng chỉ mục băm.

create type TType as table
(
  TheDate datetime not null primary key nonclustered
) with (memory_optimized = on);

Và thủ tục lưu trữ sử dụng a select top(1) ..để tìm giá trị tiếp theo.

create procedure dbo.GetDates
  @T dbo.TType readonly
with native_compilation, schemabinding, execute as owner 
as
begin atomic with (transaction isolation level = snapshot, language = N'us_english', delayed_durability = on)

  declare @R dbo.TType;
  declare @RowsLeft bit = 1;  
  declare @CurDate datetime = '1901-01-01';

  while @RowsLeft = 1
  begin

    select top(1) @CurDate = T.TheDate
    from @T as T
    where T.TheDate > dateadd(day, 90, @CurDate)
    order by T.TheDate;

    if @@rowcount = 1
    begin
      insert into @R(TheDate) values(@CurDate);
    end
    else
    begin
      set @RowsLeft = 0;
    end

  end;

  select R.TheDate
  from @R as R;
end

Các giải pháp của bạn sử dụng DATEADD và DATEDIFF có thể trả về các kết quả khác nhau tùy thuộc vào tập dữ liệu ban đầu.
Pavel Nefyodov

@PavelNefyodov Tôi không thấy điều đó. Bạn có thể giải thích hoặc đưa ra một ví dụ?
Mikael Eriksson

Bạn có thể kiểm tra nó vào những ngày như thế này không ('2014-01-01 00: 00: 00.000'), ('2014-04-01 01: 00: 00.000'), làm ơn? Thông tin thêm có thể được tìm thấy trong câu trả lời của tôi.
Pavel Nefyodov

@PavelNefyodov À, tôi hiểu rồi. Vì vậy, nếu tôi thay đổi thứ hai thành T.TheDate >= dateadd(day, 91, @CurDate)tất cả sẽ ổn phải không?
Mikael Eriksson

Hoặc nếu phù hợp với OP, thay đổi kiểu dữ liệu của TheDatetrong TTypeđể Date.
Mikael Eriksson

5

Một giải pháp sử dụng một con trỏ.
(đầu tiên, một số bảng và biến cần thiết) :

-- a table to hold the results
DECLARE @cd TABLE
(   TheDate datetime PRIMARY KEY,
    Qualify INT NOT NULL
);

-- some variables
DECLARE
    @TheDate DATETIME,
    @diff INT,
    @Qualify     INT = 0,
    @PreviousCheckDate DATETIME = '1900-01-01 00:00:00' ;

Con trỏ thực tế:

-- declare the cursor
DECLARE c CURSOR
    LOCAL STATIC FORWARD_ONLY READ_ONLY
    FOR
    SELECT TheDate
      FROM T
      ORDER BY TheDate ;

-- using the cursor to fill the @cd table
OPEN c ;

FETCH NEXT FROM c INTO @TheDate ;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @diff = DATEDIFF(day, @PreviousCheckDate, @Thedate) ;
    SET @Qualify = CASE WHEN @diff > 90 THEN 1 ELSE 0 END ;

    INSERT @cd (TheDate, Qualify)
        SELECT @TheDate, @Qualify ;

    SET @PreviousCheckDate = 
            CASE WHEN @diff > 90 
                THEN @TheDate 
                ELSE @PreviousCheckDate END ;

    FETCH NEXT FROM c INTO @TheDate ;
END

CLOSE c;
DEALLOCATE c;

Và nhận được kết quả:

-- get the results
SELECT TheDate, Qualify
    FROM @cd
    -- WHERE Qualify = 1        -- optional, to see only the qualifying rows
    ORDER BY TheDate ;

Đã thử nghiệm tại SQLFiddle


+1 cho giải pháp này lên nhưng không phải vì đó là cách làm hiệu quả nhất.
Pavel Nefyodov

@PavelNefyodov thì chúng ta nên kiểm tra hiệu năng!
ypercubeᵀᴹ

Tôi tin tưởng Paul White về điều đó. Kinh nghiệm của tôi với thử nghiệm hiệu suất không phải là ấn tượng. Một lần nữa, điều này không ngăn tôi bỏ phiếu trả lời của bạn.
Pavel Nefyodov

Cảm ơn ypercube. Như mong đợi nhanh chóng trên số lượng hàng hạn chế. Trên 13000 hàng, CTE và điều này thực hiện ít nhiều giống nhau. Trên 130.000 hàng có chênh lệch 600%. Trên 13m nó vượt qua 15 phút trên thiết bị thử nghiệm của tôi. Ngoài ra tôi đã phải loại bỏ khóa chính, có thể ảnh hưởng đến hiệu suất một chút.
Độc lập

Thnx để thử nghiệm. Bạn cũng có thể kiểm tra bằng cách sửa đổi INSERT @cdchỉ thực hiện khi @Qualify=1(và do đó không chèn 13M hàng nếu bạn không cần tất cả chúng trong đầu ra). Và giải pháp phụ thuộc vào việc tìm kiếm một chỉ số trên TheDate. Nếu không có, nó sẽ không hiệu quả.
ypercubeᵀᴹ

2
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ả

nhập mô tả hình ảnh ở đây

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 DATEDIFFDATEADD 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à: nhập mô tả hình ảnh ở đây


Cảm ơn Pavel. Tôi đã không thực sự nhận được kết quả của giải pháp của bạn trong thời gian. Tôi giảm testdata xuống 1000 hàng cho đến khi tôi có thời gian thực hiện trong 25 giây. Khi tôi thêm một nhóm theo ngày và chuyển đổi thành ngày trong phần chọn, tôi đã nhận được đầu ra chính xác! Chỉ vì lợi ích, tôi để truy vấn tiếp tục với bảng thử nghiệm nhỏ của mình (hàng 13k) và có hơn 12 phút, điều đó có nghĩa là hiệu suất của nhiều hơn o (nx)! Vì vậy, nó có vẻ hữu ích cho các bộ chắc chắn sẽ nhỏ.
Độc lập

Bảng bạn đã sử dụng trong các bài kiểm tra là gì? Có bao nhiêu hàng? Không chắc chắn tại sao bạn phải thêm nhóm theo ngày để có đầu ra chính xác mặc dù. Xin vui lòng xuất bản tài trợ của bạn như là một phần của câu hỏi của bạn (cập nhật).
Pavel Nefyodov

Chào! Tôi sẽ thêm vào ngày mai. Nhóm bằng cách kết hợp ngày trùng lặp. Nhưng tôi đã vội vàng (đêm khuya) và có lẽ nó đã được thực hiện bằng cách thêm convert (ngày, z). Số lượng hàng là trong nhận xét của tôi. Tôi đã thử 1000 hàng với giải pháp của bạn. Cũng đã thử 13.000 hàng với 12 phút thực hiện. Pauls và Ypercubes cũng bị lôi kéo vào bảng 130.000 và 13 cối. Bảng là một bảng đơn giản với ngày ngẫu nhiên được tạo từ ngày hôm qua và -2 năm trước. Chỉ số được bảo đảm trên trường ngày.
Độc lập

0

Ok, tôi đã bỏ lỡ điều gì đó hoặc tại sao bạn không bỏ qua đệ quy và tham gia lại với chính mình? Nếu ngày là khóa chính, nó phải là duy nhất và theo thứ tự thời gian nếu bạn có kế hoạch tính toán bù cho hàng tiếp theo

    DECLARE @T AS TABLE
  (
     TheDate DATETIME PRIMARY KEY
  );

INSERT @T
       (TheDate)
VALUES ('2014-01-01 11:00'),
       ('2014-01-03 10:00'),
       ('2014-01-04 09:30'),
       ('2014-04-01 10:00'),
       ('2014-05-01 11:00'),
       ('2014-07-01 09:00'),
       ('2014-07-31 08:00');

SELECT [T1].[TheDate]                               [first],
       [T2].[TheDate]                               [next],
       Datediff(day, [T1].[TheDate], [T2].[TheDate])[offset],
       ( CASE
           WHEN Datediff(day, [T1].[TheDate], [T2].[TheDate]) >= 30 THEN 1
           ELSE 0
         END )                                      [qualify]
FROM   @T[T1]
       LEFT JOIN @T[T2]
              ON [T2].[TheDate] = (SELECT Min([TheDate])
                                   FROM   @T
                                   WHERE  [TheDate] > [T1].[TheDate]) 

Sản lượng

nhập mô tả hình ảnh ở đây

Trừ khi tôi hoàn toàn bỏ lỡ điều gì đó quan trọng ....


2
Bạn có thể muốn thay đổi điều đó WHERE [TheDate] > [T1].[TheDate]để tính đến ngưỡng chênh lệch 90 ngày. Tuy nhiên, đầu ra của bạn không phải là mong muốn.
ypercubeᵀᴹ

Quan trọng: Mã của bạn phải có "90" ở đâu đó.
Pavel Nefyodov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.