Group continuous date ranges from same table SQL Server

siv3szwd  于 2023-11-16  发布在  SQL Server
关注(0)|答案(3)|浏览(180)

I have following data:

CREATE TABLE #Rate
(
    RateId Bigint
    ,PropertyId Bigint
    ,StartDate DATETIME
    ,EndDate DATETIME
)

INSERT INTO #Rate VALUES (100,1000,'2015-01-01','2010-01-11')
INSERT INTO #Rate VALUES (100,1000,'2015-01-12','2015-02-02')
INSERT INTO #Rate VALUES (100,1000,'2015-02-11','2015-02-25')
INSERT INTO #Rate VALUES (100,1002,'2015-01-01','2010-01-11')
INSERT INTO #Rate VALUES (100,1002,'2015-01-12','2015-02-02')
INSERT INTO #Rate VALUES (101,1000,'2015-02-11','2015-02-25')
INSERT INTO #Rate VALUES (101,1000,'2015-01-01','2010-01-11')
INSERT INTO #Rate VALUES (101,1000,'2015-01-12','2015-02-02')

And I need this result set

100 1000 '2015-01-01'  '2015-02-02'
100 1000 '2015-02-11'  '2015-02-25'
100 1002 '2015-01-01'  '2015-02-02'
101 1002 '2015-01-01'  '2015-02-02'

I need to group by RateId and propertyId and continuous date range for this. I have done this using cursor but I don't want cursor because we have lots of records.

If we can create view out of it that will be great :)

Thanks.

laik7k3q

laik7k3q1#

Changing all the 2010 with 2015 in your data the actual resultset you can expect is

RateId               PropertyId           StartDate  EndDate
-------------------- -------------------- ---------- ----------
100                  1000                 2015-01-01 2015-02-02
100                  1000                 2015-02-11 2015-02-25
100                  1002                 2015-01-01 2015-02-02
101                  1000                 2015-01-01 2015-02-02
101                  1000                 2015-02-11 2015-02-25

this question is quite similar to find start and stop date for contiguous dates in multiple rows so I'll use my answer to that one as a template

WITH D AS (
  SELECT RateId, PropertyId, StartDate, EndDate
       , _Id = ROW_NUMBER() OVER (PARTITION BY  RateId, PropertyId 
                                  ORDER BY StartDate, EndDate)
  FROM   #Rate
), N AS (
  SELECT m.RateId, m.PropertyId, m.StartDate, m.EndDate
       , LastStop = p.EndDate 
  FROM   D m
         LEFT JOIN D p ON m.RateID = p.RateId 
                      AND m.PropertyId = p.PropertyId 
                      AND m._Id = p._Id + 1
), B AS (
  SELECT RateId, PropertyId, StartDate, EndDate, LastStop
       , Block = SUM(CASE WHEN LastStop Is Null Then 1
                          WHEN LastStop + 1 < StartDate Then 1
                          ELSE 0
                    END)
                 OVER (PARTITION BY RateId, PropertyId ORDER BY StartDate, EndDate)
  FROM   N
)
SELECT RateId, PropertyId
     , MIN(StartDate) StartDate
     , MAX(EndDate) EndDate
FROM   B
GROUP BY RateId, PropertyId, Block
ORDER BY RateId, PropertyId, Block;

D generates a row counter to avoid to use triangular join.
N get the previous EndDate in the same RateID, PropertyID group for every row.
B generate a sequence number for every block
The main query aggregates the data in B to get the wanted resultset.

hmtdttj4

hmtdttj42#

Assuming you are using SQL Server 2012+, you can take the following approach:

  • Find all the records that do not overlap with the prev record. These begin a range.
  • Count the number of such records before any given record. These assign a constant value to the range.
  • Use this as a grouping factor.

The query looks like:

select rateid, propertyid, min(startdate) as startdate, max(enddate) as enddate
from (select r.*,
             sum(case when preved < startdate then 1 else 0 end) over (partition by rateid, propertyid order by startdate) as grp
      from (select r.*,
                   lag(enddate) over (partition by rateid, propertyid order by enddate) as preved
            from #Rate r
           ) r
     ) r
group by rateid, propertyid, grp;

EDIT:

In SQL Server 2008, you can do something similar:

with r as (
      select r.*,
             (case when exists (select 1
                                from #rate r2
                                where r2.rateid = r.rateid and r2.propertyid = r.propertyid and
                                      (r2.startdate <= dateadd(1 day, r.enddate) and
                                       r2.enddate >= r.startdate)
                               ) then 0 else 1 end) as isstart
      from #Rate r join
           #Rate r2
     )
select rateid, propertyid, min(startdate) as startdate, max(enddate) as enddate
from (select r.*,
             (select sum(isstart)
              from r r2
              where r2.rateid = r.rateid and r2.propertyid = r.propertyid
                    r2.startdate <= r.startdate) as grp
      from r
     ) r
group by rateid, propertyid, grp;
kyvafyod

kyvafyod3#

Based on @Serpiton answer, here is a shorter solution :

CREATE TABLE #Rate
(
    RateId Bigint
    ,PropertyId Bigint
    ,StartDate DATETIME
    ,EndDate DATETIME
)

INSERT INTO #Rate VALUES (100,1000,'2015-01-01','2015-01-11')
INSERT INTO #Rate VALUES (100,1000,'2015-01-12','2015-02-02')
INSERT INTO #Rate VALUES (100,1000,'2015-02-03','2015-02-04')
INSERT INTO #Rate VALUES (100,1000,'2015-02-05','2015-02-06')
INSERT INTO #Rate VALUES (100,1000,'2015-02-11','2015-02-25')
INSERT INTO #Rate VALUES (100,1000,'2015-02-27','2015-03-02')
INSERT INTO #Rate VALUES (100,1000,'2015-03-03','2015-03-13')
INSERT INTO #Rate VALUES (100,1002,'2015-01-01','2015-01-11')
INSERT INTO #Rate VALUES (100,1002,'2015-01-12','2015-02-02')
INSERT INTO #Rate VALUES (101,1003,'2015-02-11','2015-02-25')
INSERT INTO #Rate VALUES (101,1003,'2015-01-01','2015-01-11')
INSERT INTO #Rate VALUES (101,1003,'2015-01-12','2015-02-02')

WITH B AS (
  SELECT 
      m.RateId, m.PropertyId, m.StartDate, m.EndDate,
      SUM(
        CASE WHEN p.EndDate Is Null Then 1
             WHEN p.EndDate + 1 < m.StartDate Then 1
             ELSE 0
        END
      ) OVER (PARTITION BY m.RateId, m.PropertyId ORDER BY m.StartDate, m.EndDate) AS Block
  FROM   #rate m
  LEFT JOIN #rate p ON 
      p.RateID = m.RateId AND 
      p.PropertyId = m.PropertyId AND
      p.enddate+1 = m.startdate
)
SELECT RateId, PropertyId, MIN(StartDate) StartDate, MAX(EndDate) EndDate
FROM B
GROUP BY RateId, PropertyId, Block
ORDER BY RateId, PropertyId, Block

相关问题