Sıralar arasında 90 günün geçtiği boşluklar tekrar tekrar nasıl bulunur?


17

Bu benim C # homeworld bir tür önemsiz görev, ama ben henüz SQL yapmaz ve set tabanlı (imleçler olmadan) çözmeyi tercih ederim. Bir sonuç kümesi böyle bir sorgudan gelmelidir.

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

Nasıl Çalışmalı

Bu üç parametreyi bir UDF'ye gönderiyorum.
UDF, bir görünümden ilgili <= 90 günlük daha eski satırları getirmek için dahili olarak parametreler kullanır.
UDF 'MyDate' üzerinden geçer ve toplam hesaplamaya dahil edilmesi gerekiyorsa 1 döndürür.
Değilse, 0 değerini döndürür. Burada "eleme" olarak adlandırılır.

UDF ne yapacak

Satırları tarih sırasına göre listeleyin. Satırlar arasındaki günleri hesaplayın. Sonuç kümesindeki ilk satır varsayılan olarak Vuruş = 1 ise. Fark 90'a kadarsa, - boşlukların toplamı 90 gün olana kadar (90. gün geçmelidir) Ulaşın, 1'e ayarlayın ve boşluğu 0'a sıfırlayın. Bunun yerine satırı sonuçtan çıkarmak da işe yarayacaktır.

                                          |(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

Yukarıdaki tabloda, MaxDiff sütunu önceki satırdaki tarihteki boşluktur. Şimdiye kadar denemelerim ile ilgili sorun yukarıdaki örnekte ikinci son satırı göz ardı edemez olmasıdır.

[EDIT]
Yorum başına bir etiket eklemek ve ben de şimdi derledim udf yapıştırın. Yine de, sadece bir yer tutucudur ve yararlı sonuç vermez.

;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
)

Ben ayrı ayrı tanımladığım başka bir sorgu var, daha yakın ne gerek ama pencereli sütunlarda hesaplayamıyorum gerçeği ile engellendi. Ben de bir dateiff ile çevrili MyDate üzerinde sadece bir LAG () ile az çok aynı çıktı veren bir benzer denedim.

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;

Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Paul White Monica'yı eski

Yanıtlar:


22

Soruyu okurken, gerekli temel özyinelemeli algoritma:

  1. Setteki en erken tarihle satırı döndür
  2. Bu tarihi "geçerli" olarak ayarla
  3. En erken tarihin geçerli tarihten 90 günden fazla olduğu satırı bulun
  4. Başka satır bulunana kadar 2. adımdan itibaren tekrarlayın

Bu, özyinelemeli bir ortak tablo ifadesiyle uygulanması nispeten kolaydır.

Örneğin, aşağıdaki örnek verileri kullanarak (soruya göre):

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');

Özyinelemeli kod:

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);

Sonuçlar:

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

Bir dizinin TheDateönde gelen anahtarı olduğu için, yürütme planı çok etkilidir:

Yürütme planı

Bunu bir işlevde sarmayı ve doğrudan soruda belirtilen görünüme karşı yürütmeyi seçebilirsiniz, ancak içgüdülerim buna karşı. Genellikle, görünümden geçici bir tabloya satırları seçtiğinizde, geçici tabloda uygun dizini sağladıktan sonra yukarıdaki mantığı uyguladığınızda performans daha iyidir. Ayrıntılar, görüşün ayrıntılarına bağlıdır, ancak bu benim genel deneyimimdir.

Tamlık için (ve ypercube'un cevabı tarafından istendiğinde), bu tür bir sorun için diğer go-to çözümümün (T-SQL uygun sıralı set fonksiyonlarını alana kadar) bir SQLCLR imleci olduğunu belirtmeliyim ( tekniğin bir örneği için cevabımı buraya bakın) ). Bu , bir T-SQL imlecinden çok daha iyi performans gösterir ve .NET dillerinde becerilere ve SQLCLR'yi üretim ortamlarında çalıştırabilenlere uygundur. Bu senaryoda özyinelemeli çözüm üzerinde fazla bir şey sunmayabilir, çünkü maliyetin çoğunluğu sıralamadır, ancak söz etmeye değer.


9

Bu olduğu için bir SQL Server 2014 soru Ben de bir "imleç" bir doğal derlenmiş saklı yordam versiyonunu ekleyebilir.

Bazı veriler içeren kaynak tablosu:

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');

Saklı yordamın parametresi olan bir tablo türü. Uygun şekilde ayarlayınbucket_count .

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);

Ve tablo değerli parametresi ile döngüler ve satırları toplayan saklı bir yordam @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

Yerel olarak derlenmiş saklı yordamın bir parametresi olarak kullanılan, bellek için optimize edilmiş bir tablo değişkenini doldurmak ve yordamı çağırmak için kod.

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;

Sonuç:

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

Güncelleme:

Herhangi bir nedenle tablodaki her satırı ziyaret etmeniz gerekmiyorsa, Paul White'ın özyinelemeli CTE'de uygulanan "sonraki tarihe atla" sürümünün eşdeğerini yapabilirsiniz.

Veri türünün kimlik sütununa ihtiyacı yoktur ve bir karma dizini kullanmamalısınız.

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

Ve saklı yordam bir select top(1) ..sonraki değeri bulmak için a kullanı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 @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

DATEADD ve DATEDIFF kullanan çözümleriniz, ilk veri kümesine bağlı olarak farklı sonuçlar döndürebilir.
Pavel Nefyodov

@ PavelNefyodov Bunu görmüyorum. Bir örnek verebilir misin?
Mikael Eriksson

Böyle tarihlerde ('2014-01-01 00: 00: 00.000'), ('2014-04-01 01: 00: 00.000') kontrol edebilir misiniz? Cevabımda daha fazla bilgi bulunabilir.
Pavel Nefyodov

@ PavelNefyodov Ah, anlıyorum. Eğer ikincisini T.TheDate >= dateadd(day, 91, @CurDate)herkese değiştirirsem iyi olur mu?
Mikael Eriksson

Veya OP uygun olursa, veri türünü değiştirmek TheDatede TTypehiç Date.
Mikael Eriksson

5

İmleç kullanan bir çözüm.
(ilk olarak, bazı gerekli tablolar ve değişkenler) :

-- 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' ;

Gerçek imleç:

-- 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;

Ve sonuçları almak:

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

SQLFiddle'da test edildi


Bu çözümü + 1'leyin ama bir şeyler yapmanın en etkili yolu olduğu için değil.
Pavel Nefyodov

@PavelNefyodov o zaman performansı test etmeliyiz!
ypercubeᵀᴹ

Paul White'a bu konuda güveniyorum. Performans testi ile ilgili deneyimim o kadar etkileyici değil. Bu da cevabınızı oylamamı engellemiyor.
Pavel Nefyodov

Teşekkürler ypercube. Beklendiği gibi sınırlı sayıda satırda hızlı. 13000 satırda, CTE ve bu aşağı yukarı aynı performansı gösterdi. 130.000 satırda% 600 fark vardı. 13m'de test ekipmanımda 15 dakika geçiyor. Ayrıca, performansı biraz etkileyebilecek birincil anahtarı kaldırmak zorunda kaldım.
Bağımsız

Test için teşekkürler. Ayrıca, INSERT @cdyalnızca @Qualify=1çıktıda tüm bunlara ihtiyacınız yoksa 13M satırları eklemeyecek şekilde değiştirerek test edebilirsiniz . Ve çözüm bir indeks bulmaya bağlıdır TheDate. Bir tane yoksa, verimli olmayacaktır.
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 

Sonuç

resim açıklamasını buraya girin

Ayrıca SQL Server'da Çalışan Toplamı Hesaplama

güncelleme: lütfen performans testi sonuçlarına bakın.

"90 günlük boşluk" bulmak için kullanılan farklı mantık nedeniyle ypercube's ve sağlam kalırsa çözümlerim Paul White'ın çözümüne farklı sonuçlar döndürebilir. Bunun nedeni DATEDIFF ve DATEADD kullanımıdır sırasıyla işlevlerinin kullanılmasıdır.

Örneğin:

SELECT DATEADD(DAY, 90, '2014-01-01 00:00:00.000')

'2014-04-01 00: 00: 00.000' değerini döndürür; '2014-04-01 01: 00: 00.000' 90 günlük boşluğun üzerindedir

fakat

SELECT DATEDIFF(DAY, '2014-01-01 00:00:00.000', '2014-04-01 01:00:00.000')

Hâlâ boşluk içinde olduğu anlamına gelen '90' değerini döndürür.

Bir perakendeci örneği düşünün. Bu durumda, '2014-01-01 23: 59: 59: 999'da' 2014-01-01 'tarihine kadar satılan bozulabilir bir ürün satmak iyidir. Dolayısıyla bu durumda DATEDIFF (DAY, ...) değeri TAMAM.

Başka bir örnek görülmeyi bekleyen bir hastadır. '2014-01-01 00: 00: 00: 000' 'a gelen ve' 2014-01-01 23: 59: 59: 999 '' da ayrılan biri için DATEDIFF kullanılsa bile 0 (sıfır) gündür. gerçek bekleme neredeyse 24 saat oldu. Yine '2014-01-01 23:59:59' 'da gelen ve' 2014-01-02 00:00:01 '' de gelen hasta, DATEDIFF kullanılıyorsa bir gün bekledi.

Ama konuţuyorum.

DATEDIFF çözümlerinden ayrıldım ve hatta performans bunları test etti, ancak gerçekten kendi liglerinde olmalılar.

Ayrıca, büyük veri kümeleri için aynı gün değerlerinden kaçınmanın imkansız olduğu belirtildi. Dolayısıyla, 2 yıllık veriye yayılan 13 Milyon kayıt diyelim, o zaman birkaç gün boyunca birden fazla kayıt elde edeceğiz. Bu kayıtlar benim ve ypercube'un DATEDIFF çözümlerindeki en erken fırsatta filtreleniyor. Umarım ypercube buna aldırmaz.

Çözeltiler aşağıdaki tabloda test edilmiştir

CREATE TABLE [dbo].[vGetVisits](
    [id] [int] NOT NULL,
    [mydate] [datetime] NOT NULL,
) 

iki farklı kümelenmiş dizinle (bu durumda tarih):

CREATE CLUSTERED INDEX CI_mydate on vGetVisits(mydate) 
GO

Tablo aşağıdaki şekilde dolduruldu

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

Multimilyon sıra halinde INSERT, 0-20 dakikalık girişler rastgele eklenecek şekilde değiştirildi.

Tüm çözeltiler dikkatlice aşağıdaki kodlara sarıldı

SET NOCOUNT ON
GO

DECLARE @StartDate DATETIME

SET @StartDate = GETDATE()

--- Code goes here

PRINT 'Total milliseconds: ' + CONVERT(varchar, DATEDIFF(ms, @StartDate, GETDATE()))

Test edilen gerçek kodlar (belirli bir sırayla):

Ypercube'un DATEDIFF çözümü ( 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 ;

Ypercube'un DATEADD çözümü ( 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 ;

Paul White'ın çözümü ( 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);

DATEADD çözümüm ( 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 ;

DATEDIFF Çözümüm ( 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]

SQL Server 2012 kullanıyorum, bu yüzden Mikael Eriksson'a özür dilerim, ama kodu burada test edilmeyecek. Yine de DATADIFF ve DATEADD ile yaptığı çözümlerin bazı veri kümelerinde farklı değerler döndürmesini bekliyorum.

Ve gerçek sonuçlar: resim açıklamasını buraya girin


Teşekkürler Pavel. Zaman içinde çözümünüzün sonucunu gerçekten alamadım. 25 saniyede bir yürütme süresi elde edene kadar test verilerimi 1000 satıra küçülttüm. Tarihe göre bir grup ekleyip seçimdeki tarihlere dönüştürdüğümde, doğru çıktıyı aldım! Sadece uğruna, küçük test veri tablom (13k satır) ile sorgunun devam etmesine izin verdim ve 12 dakikadan fazla sürdüm, bu da o (nx) 'den daha yüksek bir performans anlamına geliyor! Bu yüzden kesin olarak küçük olacak setler için yararlı görünüyor.
Bağımsız

Testlerde kullandığınız tablo neydi? Kaç satır? Doğru çıktıyı almak için neden tarihe göre grup eklemeniz gerektiğinden emin değilim. Lütfen fonlarınızı sorunuzun bir parçası olarak yayınlamaktan çekinmeyin (güncellendi).
Pavel Nefyodov

Selam! Bunu yarın ekleyeceğim. Grup, yinelenen tarihleri ​​birleştirmekti. Ama acelem vardı (gece yarısı) ve belki de zaten dönüştürme (tarih, z) ekleyerek yapıldı. Satırların miktarı benim yorumumda. Çözümünüzle 1000 satır denedim. Ayrıca 12.000 yürütme ile 13.000 satır denedi. Pauls ve Ypercubes de 130.000 ve 13 milyon masaya ayarlanmıştı. Tablo, dünden ve -2 yıl öncesinden oluşturulmuş rastgele tarihleri ​​olan düz bir tablodur. Tarih alanında kümelenmiş dizin.
Bağımsız

0

Tamam, bir şey mi kaçırdım yoksa neden sadece özyinelemeyi atlayıp kendinize geri dönmeyesiniz? Tarih birincil anahtarsa, bir sonraki satıra ofseti hesaplamayı planlıyorsanız benzersiz ve kronolojik sırada olmalıdır.

    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]) 

Verim

resim açıklamasını buraya girin

Tamamen önemli bir şey kaçırmadıkça ....


2
Muhtemelen WHERE [TheDate] > [T1].[TheDate]90 günlük fark eşiğini dikkate almak için bunu değiştirmek istersiniz . Ama yine de, çıktınız aranan değil.
ypercubeᵀᴹ

Önemli: Kodunuzun bir yerde "90" olması gerekir.
Pavel Nefyodov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.