C'est une sorte de tâche triviale dans mon monde natal C #, mais je ne le fais pas encore en SQL et préférerais le résoudre en fonction d'un ensemble (sans curseurs). Un jeu de résultats doit provenir d'une requête comme celle-ci.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Comment cela devrait-il fonctionner
J'envoie ces trois params dans un UDF.
L'UDF utilise en interne des paramètres pour récupérer les lignes associées <= 90 jours plus anciennes, à partir d'une vue.
L'UDF traverse 'MyDate' et renvoie 1 s'il doit être inclus dans un calcul total.
Si ce n'est pas le cas, il renvoie 0. Nommé ici comme "qualificatif".
Ce que fera le udf
Liste les lignes dans l'ordre des dates. Calculez les jours entre les rangées. La première ligne du jeu de résultats par défaut est Hit = 1. Si la différence est jusqu'à 90, - passez ensuite à la ligne suivante jusqu'à ce que la somme des intervalles soit de 90 jours (le 90e jour doit passer). Il serait également préférable d'omettre la ligne du résultat.
|(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
Dans le tableau ci-dessus, la colonne MaxDiff est l'écart par rapport à la date de la ligne précédente. Le problème avec mes tentatives jusqu'à présent est que je ne peux pas ignorer l'avant-dernière ligne de l'exemple ci-dessus.
[MODIFIER]
Selon le commentaire, j'ajoute une balise et colle également le udf que j'ai compilé tout à l'heure. Cependant, c'est juste un espace réservé et ne donnera pas de résultat utile.
;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
)
J'ai une autre requête que je définis séparément qui est plus proche de ce dont j'ai besoin, mais bloquée par le fait que je ne peux pas calculer sur des colonnes fenêtrées. J'ai également essayé un similaire qui donne plus ou moins la même sortie juste avec un LAG () sur MyDate, entouré d'un datiff.
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;
En lisant la question, l'algorithme récursif de base requis est:
Ceci est relativement facile à implémenter avec une expression de table commune récursive.
Par exemple, en utilisant les exemples de données suivants (basés sur la question):
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');
Le code récursif est:
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);
Les résultats sont:
╔═════════════════════════╗
║ TheDate ║
╠═════════════════════════╣
║ 2014-01-01 11:00:00.000 ║
║ 2014-05-01 11:00:00.000 ║
║ 2014-07-31 08:00:00.000 ║
╚═════════════════════════╝
Avec un index ayant TheDate
comme clé principale, le plan d'exécution est très efficace:
Vous pouvez choisir de l'envelopper dans une fonction et de l'exécuter directement contre la vue mentionnée dans la question, mais mes instincts sont contre. Habituellement, les performances sont meilleures lorsque vous sélectionnez des lignes d'une vue dans une table temporaire, fournissez l'index approprié sur la table temporaire, puis appliquez la logique ci-dessus. Les détails dépendent des détails de la vue, mais c'est mon expérience générale.
Pour être complet (et invité par la réponse de ypercube), je dois mentionner que mon autre solution de référence pour ce type de problème (jusqu'à ce que T-SQL obtienne les fonctions d'ensemble ordonnées appropriées) est un curseur SQLCLR ( voir ma réponse ici = pour un exemple de la technique). Cela fonctionne beaucoup mieux qu'un curseur T-SQL, et est pratique pour ceux qui ont des compétences dans les langages .NET et la capacité d'exécuter SQLCLR dans leur environnement de production . Il ne peut pas offrir beaucoup dans ce scénario sur la solution récursive parce que la majorité du coût est le genre, mais il convient de le mentionner.
Étant donné que est une question SQL Server 2014, je pourrais aussi bien ajouter une version de procédure stockée compilée en mode natif d'un "curseur".
Tableau source avec quelques données:
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');
Un type de table qui est le paramètre de la procédure stockée. Ajustez le bucket_count
De manière appropriée .
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);
Et une procédure stockée qui parcourt le paramètre de valeur de table et collecte les lignes dans @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
Code pour remplir une variable de table optimisée en mémoire qui est utilisée comme paramètre de la procédure stockée compilée en mode natif et appeler la procédure.
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;
Résultat:
TheDate
-----------------------
2014-07-31 08:00:00.000
2014-01-01 11:00:00.000
2014-05-01 11:00:00.000
Mise à jour:
Si pour une raison quelconque, vous n'avez pas besoin de visiter chaque ligne du tableau, vous pouvez faire l'équivalent de la version "passer à la date suivante" qui est implémentée dans le CTE récursif par Paul White.
Le type de données n'a pas besoin de la colonne ID et vous ne devez pas utiliser d'index de hachage.
create type TType as table
(
TheDate datetime not null primary key nonclustered
) with (memory_optimized = on);
Et la procédure stockée utilise une select top(1) ..
pour trouver la valeur suivante.
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
Une solution qui utilise un curseur.
(d'abord, quelques tableaux et variables nécessaires):
-- 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' ;
Le curseur réel:
-- 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;
Et obtenir les résultats:
-- get the results
SELECT TheDate, Qualify
FROM @cd
-- WHERE Qualify = 1 -- optional, to see only the qualifying rows
ORDER BY TheDate ;
Testé à SQLFiddle
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
Résultat
Jetez également un œil à Comment calculer le total cumulé dans SQL Server
mise à jour: veuillez voir ci-dessous les résultats des tests de performances.
En raison de la logique différente utilisée pour trouver un "écart de 90 jours", les ypercubes et mes solutions s'ils sont laissés intacts peuvent renvoyer des résultats différents à la solution de Paul White. Cela est dû à l'utilisation des fonctions DATEIFF et DATEADD respectivement.
Par exemple:
SELECT DATEADD(DAY, 90, '2014-01-01 00:00:00.000')
renvoie "2014-04-01 00: 00: 00.000", ce qui signifie que "2014-04-01 01: 00: 00.000" est au-delà d'un intervalle de 90 jours
mais
SELECT DATEDIFF(DAY, '2014-01-01 00:00:00.000', '2014-04-01 01:00:00.000')
Renvoie "90", ce qui signifie qu'il est toujours dans l'écart.
Prenons un exemple de détaillant. Dans ce cas, la vente d'un produit périssable qui s'est vendu à la date "2014-01-01" à "2014-01-01 23: 59: 59: 999" est acceptable. La valeur DATEDIFF (DAY, ...) dans ce cas est donc OK.
Un autre exemple est un patient qui attend d'être vu. Pour quelqu'un qui vient à '2014-01-01 00: 00: 00: 000' et part à '2014-01-01 23: 59: 59: 999' c'est 0(zero) = jours si DATEDIFF est utilisé même si l'attente réelle était de près de 24 heures. Encore une fois, le patient qui arrive le '2014-01-01 23:59:59' et repart le '2014-01-02 00:00:01' a attendu pendant un jour si DATEDIFF est utilisé.
Mais je m'égare.
J'ai laissé les solutions DATEDIFF et même les performances testées, mais elles devraient vraiment être dans leur propre ligue.
Il a également été noté que pour les grands ensembles de données, il est impossible d'éviter les valeurs du même jour. Donc, si nous disons 13 millions d'enregistrements couvrant 2 ans de données, nous finirons par avoir plus d'un enregistrement pendant quelques jours. Ces enregistrements sont filtrés dès que possible dans mes solutions DATEDIFF et ypercube. J'espère que ypercube ne s'en soucie pas.
Les solutions ont été testées sur le tableau suivant
CREATE TABLE [dbo].[vGetVisits](
[id] [int] NOT NULL,
[mydate] [datetime] NOT NULL,
)
avec deux index clusterisés différents (mydate dans ce cas):
CREATE CLUSTERED INDEX CI_mydate on vGetVisits(mydate)
GO
Le tableau a été rempli de la manière suivante
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
Pour un cas de plusieurs millions de lignes, INSERT a été modifié de telle manière que des entrées de 0 à 20 minutes ont été ajoutées au hasard.
Toutes les solutions ont été soigneusement emballées dans le code suivant
SET NOCOUNT ON
GO
DECLARE @StartDate DATETIME
SET @StartDate = GETDATE()
--- Code goes here
PRINT 'Total milliseconds: ' + CONVERT(varchar, DATEDIFF(ms, @StartDate, GETDATE()))
Codes réels testés (sans ordre particulier):
La solution DATEDIFF d'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 ;
La solution DATEADD d'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 ;
La solution de 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);
Ma solution DATEADD ( 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 ;
Ma solution DATEDIFF ( 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]
J'utilise SQL Server 2012, donc je m'excuse auprès de Mikael Eriksson, mais son code ne sera pas testé ici. Je m'attendrais toujours à ce que ses solutions avec DATADIFF et DATEADD retournent des valeurs différentes sur certains ensembles de données.
Et les résultats réels sont:
Ok, est-ce que j'ai raté quelque chose ou pourquoi ne sauteriez-vous pas simplement la récursivité et rejoignez-vous? Si la date est la clé primaire, elle doit être unique et dans l'ordre chronologique si vous prévoyez de calculer le décalage à la ligne suivante
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])
Rendements
Sauf si j'ai raté quelque chose d'important ...