Расчет диапазона дат SQL Server - PullRequest
       1

Расчет диапазона дат SQL Server

0 голосов
/ 19 сентября 2018

У меня есть таблица, как показано ниже.

Workid  StartDate   EndDate
101     2012-03-01  2013-03-20
101     2013-03-21  2014-08-01
102     2014-01-01  2014-12-31
103     2012-01-01  2012-10-11
103     2012-10-12  2014-12-31
104     2012-01-01  2012-12-25
105     2012-01-01  2014-06-30
105     2014-07-01  2014-12-31

Мне нужно проверить, полностью ли занято рабочее место для данного года.Например, если я выберу год как 2012, то результат должен быть таким, как показано ниже.

WorkID  STATUS
101     Not Occupied
102     Not Occupied
103     Occupied
104     Not Occupied
105     Occupied

Если я выберу год как 2013, результат должен быть как ниже.

WorkID  STATUS
101     Occupied
102     Not Occupied
103     Occupied
104     Not Occupied
105     Occupied

Может ли кто-нибудь помочь с этим?

ниже приведен пример сценария таблицы.

CREATE TABLE #temp
(
    Workid INT,
    StartDate DATE,
    EndDate DATE
)
go
INSERT INTO #temp
(
    Workid,
    StartDate,
    EndDate
)
VALUES
(101, '2012-03-01', '2013-03-20'),
(101, '2013-03-21', '2014-08-01'),
(102, '2014-01-01', '2014-12-31'),
(103, '2012-01-01', '2012-10-11'),
(103, '2012-10-12', '2014-12-31'),
(104, '2012-01-01', '2012-12-25'),
(105, '2012-01-01', '2014-06-30'),
(105, '2014-07-01', '2014-12-31')
;

Ответы [ 4 ]

0 голосов
/ 19 сентября 2018

Вы можете использовать рекурсивный cte, чтобы найти диапазоны дат для каждого рабочего идентификатора и сравнить их, это также будет учитывать пробелы в работе (демонстрация: http://rextester.com/NUEW6510)

DECLARE @year INT = 2013;
WITH calendarSearch (Workid,StartDate, EndDate, StartDate2, EndDate2) AS
(
    SELECT Workid,StartDate, EndDate, CAST('1900-01-01' AS DATE), CAST('1900-01-01' AS DATE)
    FROM #temp
    UNION ALL
    SELECT c.Workid, c.StartDate, t.EndDate, t.StartDate, c.EndDate
    FROM #temp t
    JOIN calendarSearch c
        ON c.Workid = t.Workid
        AND t.EndDate > c.EndDate
        AND t.StartDate <= DATEADD(DAY,1,c.EndDate)
), occupiedRange AS
(
    SELECT Workid,StartDate, EndDate FROM calendarSearch c
        WHERE c.StartDate NOT IN (SELECT StartDate2 FROM calendarSearch WHERE calendarSearch.Workid = c.Workid)
            AND c.EndDate NOT IN (SELECT EndDate2 FROM calendarSearch WHERE calendarSearch.Workid = c.Workid)
)

SELECT Workid, 
CASE WHEN EXISTS (SELECT 1 FROM occupiedRange WHERE Workid = o1.Workid and StartDate <= datefromparts(@year,1,1) AND EndDate >= DATEFROMPARTS(@year,12,31)) THEN 'Occupied' ELSE 'Not Occupied' END AS Status
FROM occupiedRange o1
GROUP BY o1.Workid
ORDER BY Workid
0 голосов
/ 19 сентября 2018

Сначала нужно определить минимальную начальную дату и максимальную конечную дату для каждого рабочего идентификатора:

       SELECT   [WorkId]
              , MIN([StartDate]) AS [StartDate]
              , MAX([EndDate]) AS [EndDate]
       FROM     [#temp]
       GROUP BY [WorkId]

При небольшом манипулировании датами вам необходимо выяснить даты начала и окончания годавы оцениваете что-то вроде:

    DECLARE @Year INT = 2012;

    DECLARE @YearBegin DATE;
    DECLARE @YearEnd date;

    SET @YearBegin = DATEADD(yy, @Year - 1900, 0);
    SET @YearEnd = DATEADD(DAY, -1, DATEADD(yy, 1, @YearBegin));

    SELECT @YearBegin
         , @YearEnd;

После этого мы можем сравнить начальную и конечную даты с этими @YearBegin и @yearEnd, чтобы определить, что они «полностью заняты».

Это предполагаетнет пробелов в диапазонах данных для каждого идентификатора:

    DECLARE @Year INT = 2012;

    DECLARE @YearBegin DATE;
    DECLARE @YearEnd DATE;

    SET @YearBegin = DATEADD(yy, @Year - 1900, 0);
    SET @YearEnd = DATEADD(DAY, -1, DATEADD(yy, 1, @YearBegin));

    SELECT [WorkId]
         , CASE WHEN [fr].[StartDate] <= @YearBegin
                     AND [fr].[EndDate] >= @YearEnd THEN 'Occupied'
                ELSE 'Not Occupied'
           END AS [Status]
    FROM   (
               SELECT   [WorkId]
                      , MIN([StartDate]) AS [StartDate]
                      , MAX([EndDate]) AS [EndDate]
               FROM     [#temp]
               GROUP BY [WorkId]
           ) AS [fr];

Если потенциально могут быть пробелы в диапазонах дат и или перекрывающихся диапазонах дат для каждого идентификатора, вот пример рекурсивного CTE, который будет учитыватьэти ситуации:

    DECLARE @Year INT = 2013
          , @TotalDayYear INT;

    --For the year we are evalutaing, how many days total in that year
    --We'll use for comparison later
    SET @TotalDayYear = DATEDIFF(
                                    DAY
                                  , CONCAT(@Year - 1, '-12-31')
                                  , CONCAT(@Year, '-12-31')
                                );


    DECLARE @YearOccupied TABLE
        (
            [WorkId] INT
          , [YearOccupied] INT
          , [CountDaysOccupied] INT
        );


    --Recursive CTE to get list of every day a particular ID occupied based on the date ranges.
    WITH [Cte]
    AS ( SELECT [WorkId]
              , [StartDate]
              , [EndDate]
              , [StartDate] AS [DateOccupied]
         FROM   [#temp]
         UNION ALL
         SELECT     [a].[WorkId]
                  , [a].[StartDate]
                  , [a].[EndDate]
                  , DATEADD(DAY, 1, [b].[DateOccupied]) AS [DateOccupied]
         FROM       [#temp] [a]
         INNER JOIN [Cte] [b]
             ON [b].[WorkId] = [a].[WorkId]
                AND [b].[StartDate] = [a].[StartDate]
                AND [b].[EndDate] = [a].[EndDate]
                AND [b].[DateOccupied] < [a].[EndDate] )
    --We'll insert into a table variable a count by year for each Id
    INSERT INTO @YearOccupied (
                                  [WorkId]
                                , [YearOccupied]
                                , [CountDaysOccupied]
                              )
                SELECT   [do].[WorkId]
                       , YEAR([DateOccupied]) AS [YearOccupied]
                       , COUNT(*) AS [DaysOccupiedCount]
                FROM     (
                             --Use a sub-query here to get a distinct Date Occupied
                             --To account for overlapping data ranges
                             SELECT DISTINCT [WorkId]
                                           , [DateOccupied]
                             FROM   [Cte]
                         ) AS [do]
                GROUP BY [do].[WorkId]
                       , YEAR([DateOccupied])
    OPTION ( MAXRECURSION 0 );


    SELECT          [a].[WorkId]
                  , @Year AS [YearOccupied]
                  --Coaleace as we may have a year an ID didn't occupy at all
                  --compared to the total days of the year to determine fully occupied or not
                  , CASE WHEN COALESCE([b].[CountDaysOccupied], 0) = @TotalDayYear THEN
                             'Occupied'
                         ELSE 'Not Occupied'
                    END AS [Status]
    FROM            (
                        --We could have a year we want data for that a particular id didn't occupy.
                        --We'll build a distinct list of ID and our evalution year for doing a left outer to
                        --accually see if the year was occupied.
                        SELECT DISTINCT [WorkId]
                                      , @Year AS [YearOccupied]
                        FROM   @YearOccupied
                    ) AS [a]
    LEFT OUTER JOIN @YearOccupied [b]
        ON [b].[WorkId] = [a].[WorkId]
           AND [b].[YearOccupied] = @Year;
0 голосов
/ 19 сентября 2018

Это заняло у меня некоторое время, но запрос будет работать.Возможно, есть лучшие способы, но, как я уже сказал, это работает.Проверьте это, удачи!

USE tempdb
DECLARE @StartDate DATE
DECLARE @EindDate DATE
SET @StartDate = '1990-01-01'  -- << user input >> --
SET @EindDate  = '2018-12-31'  -- << user input >> --

IF OBJECT_ID ('TEMPDB..#Date') IS NOT NULL DROP TABLE #Date
IF OBJECT_ID ('TEMPDB..#Date') IS NULL CREATE TABLE #Date (Date_ DATE)

INSERT INTO #Date VALUES (@StartDate)

WHILE @StartDate < @EindDate

BEGIN
INSERT INTO #Date
SELECT DATEADD (DD, 1, @StartDate) AS Date
SET @StartDate = DATEADD (DD, 1, @StartDate)
END;


SELECT * FROM #Date

CREATE TABLE #temp
(
    Workid INT,
    StartDate DATE,
    EndDate DATE
)
GO
INSERT INTO #temp
(
    Workid,
    StartDate,
    EndDate
)
VALUES
       (101, '2012-03-01', '2013-03-20'),
       (101, '2013-03-21', '2014-08-01'),
       (102, '2014-01-01', '2014-12-31'),
       (103, '2012-01-01', '2012-10-11'),
       (103, '2012-10-12', '2014-12-31'),
       (104, '2012-01-01', '2012-12-25'),
       (105, '2012-01-01', '2014-06-30'),
       (105, '2014-07-01', '2014-12-31');

CREATE TABLE #WorkID (WorkID INT)
INSERT INTO #WorkID VALUES (101), (102), (103), (104), (105)

CREATE TABLE #Yrs (Yrs INT)
INSERT INTO #Yrs VALUES (2012), (2013), (2014)

SELECT * 
INTO #ResultSet
FROM #WorkID
CROSS JOIN #Yrs;

WITH CTE AS 
(
    SELECT Workid, YEAR (Date_) AS Yr, COUNT(*) AS WorkedDays
    FROM #Date AS D
    INNER JOIN #temp AS T ON D.Date_ >= T.StartDate AND D.Date_ <= T.EndDate
    GROUP BY Workid, YEAR (Date_)
)

SELECT R.WorkID,
      Yrs AS Year_, 
      CASE WHEN Yr % 4 = 0 AND WorkedDays = 366 THEN 'Occupied'
          WHEN Yr % 4 <> 0 AND WorkedDays = 365 THEN 'Occupied'
          ELSE 'Not Occupied'
       END AS OccupiedStatus
FROM #ResultSet AS R
LEFT JOIN CTE AS C ON C.Workid = R.WorkID AND Yrs = Yr
ORDER BY 2, 1
0 голосов
/ 19 сентября 2018

Используйте запрос ниже

select b.WORKID, (case when b.SD_COUNT > 0 and b.ED_COUNT > 0 then 'Occupied' else 'Not Occupied' end ) as STATUS from 
(select a.Workid as WORKID, Sum(a.Status_StartDate) as SD_COUNT, Sum(a.Status_EndDate) as ED_COUNT from 
(Select Workid, (case when StartDate <= '2012-01-01' then 1 else 0 end as Status_StartDate), (case when EndDate >= '2012-12-31' then 1 else 0 end) as Status_EndDate from tablename)a group by a.Workid)b;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...