Какой самый эффективный способ заменить значения в указанном c столбце таблицы для этого конкретного c сценария? - PullRequest
0 голосов
/ 23 апреля 2020

Я использую SQL Server 2014, и в моей базе данных есть таблица с именем t1 (извлечение только из 2 столбцов, показанных ниже):

 ResaID       StayDate
 100          2020-02-03
 100          2020-02-04
 100          2020-02-05
 120          2020-04-06
 120          2020-04-07
 120          2020-04-08
 120          2020-04-09
 120          2020-04-10

Мне нужно изменить даты в StayDate столбец, основанный на следующей информации (извлечение показано точно так, как указано):

 ID        StartDate       EndDate
 100       2020-06-04      2020-06-06
 120       2021-03-01      2021-03-05

Я начал писать свой запрос T- SQL следующим образом (но он становится довольно утомительным, поскольку я должен это сделать для более чем 100 ResaID!):

USE MyDatabase

UPDATE t1

SET StayDate = CASE WHEN ResaID = 100 and StayDate = '2020-02-03' THEN '2020-06-04'
WHEN ResaID = 100 and StayDate = '2020-02-04' THEN '2020-06-05'
WHEN ResaID = 100 and StayDate = '2020-02-05' THEN '2020-06-06'
...    

ELSE StayDate

END

Есть ли более эффективный способ решения этой проблемы?

Ответы [ 3 ]

1 голос
/ 23 апреля 2020

Вы можете использовать рекурсивный подход:

with r_cte as (
     select id, convert(date, startdate) as startdate, convert(date, enddate) as enddate
     from ( values (100, '2020-06-04', '2020-06-06'), 
                   (120, '2021-03-01', '2021-03-03')
          ) t(id, startdate, enddate)
     union all
     select id, dateadd(day, 1, startdate), enddate
     from cte c
     where startdate < enddate
), r_cte_seq as (
     select r.cte.*, row_number() over(partition by id order by startdate) as seq
     from r_cte 
), cte_seq as (
     select t1.*, row_number() over (partition by ResaID order by staydate) as seq
     from t1
)
update cs
       set cs.staydate = rc.startdate
from cte_seq cs inner join
     r_cte_seq rc
     on rc.id = cs.ResaID and rc.seq = cs.seq;
0 голосов
/ 24 апреля 2020

Следующий код преобразует даты t1 в диапазоны, а затем использует соответствующие даты range для вычисления новых значений StayDate. Вы можете поменять окончательное значение select на одно из прокомментированных утверждений, чтобы увидеть, что происходит в CTE. Окончательный select можно заменить на update, если вы хотите изменить исходные данные таблицы.

 -- Thanks to Aaron Hughes for setting up the sample data.
 -- I changed the   DateTime   columns to   Date .

 --Ranges Table
 DECLARE @ranges TABLE
 (
    ID INT
    ,StartDate DATE
    ,EndDate DATE
 )

 DECLARE @t1 TABLE
 (
    ResaID INT
    ,StayDate DATE
    ,ColA INT
    ,ColB NVARCHAR(100)
    ,ColC BIT
 )

 INSERT INTO @t1
 (
    ResaID
    ,StayDate
    ,ColA
    ,ColB
    ,ColC
 )
 VALUES
 (100, '2020-02-03', 1, 'A', 0)
 ,(100, '2020-02-04', 100, 'B', 1)
 ,(100, '2020-02-05', 255, 'C', 1)
 ,(120, '2020-04-06', 34, 'D', 1)
 ,(120, '2020-04-07', 67, 'E', 0)
 ,(120, '2020-04-08', 87, 'F', 0)
 ,(120, '2020-04-09', 545, 'G', 1)
 ,(120, '2020-04-10', 288, 'H', 0)

 INSERT INTO @ranges
 (
    ID
    ,StartDate
    ,EndDate
 )
 VALUES
 (100, '2020-06-04', '2020-06-07')
 ,(120, '2021-03-01', '2021-03-05');

with
  -- Calculate the date range for each stay in   @t1 .
  ResaRanges as (
    select ResaId, Min( StayDate ) as ResaStartDate, Max( StayDate ) as ResaEndDate
      from @t1
      group by ResaId ),
  -- Match up the   @t1   date ranges with the   @ranges   date ranges.
  CombinedRanges as (
    select RR.ResaId, RR.ResaStartDate, RR.ResaEndDate, DateDiff( day, RR.ResaStartDate, RR.ResaEndDate ) + 1 as ResaDays,
      R.StartDate, R.EndDate, DateDiff( day, R.StartDate, R.EndDate ) + 1 as RangeDays,
      DateDiff( day, RR.ResaStartDate, R.StartDate ) as DaysOffset
      from ResaRanges as RR inner join
        @ranges as R on R.ID = RR.ResaId )
  -- Calculate the new   StayDate   values for all   @t1   ranges that are not longer than the corresponding   @range .
  --   The difference between range starting dates is added to each   StayDate .
  select T.ResaId, T.StayDate, DateAdd( day, CR.DaysOffset, T.StayDate ) as NewStayDate
    from @t1 as T inner join
      CombinedRanges as CR on CR.ResaID = T.ResaID
    where CR.RangeDays >= CR.ResaDays;
-- To see the steps you can use one of the following   select   staements to view the intermediate results:
--   select * from ResaRanges;
--   select * from CombinedRanges;
0 голосов
/ 23 апреля 2020

Вот мой подход к этой проблеме. Я бы использовал таблицу чисел для создания записи для каждой даты в новом диапазоне для каждого идентификатора бронирования. Затем я разделил бы эти данные по идентификатору бронирования, упорядоченному по дате. Выполнение того же лога раздела c для существующих данных позволит должным образом объединить записи.

Затем я бы выполнил операцию DELETE, а затем операцию INSERT. Это оставит вас с соответствующим количеством записей. Единственное, что нужно сделать вручную, - это заполнить вспомогательные данные для резервирований расширенными диапазонами дат. Я расширил один из ваших новых диапазонов, чтобы показать этот сценарий.

Я отметил, где установка для этой демонстрации заканчивается в коде ниже. Все, что ниже, - это мое решение, которое должно быть реализовано с вашими реальными таблицами.

 --Ranges Table
 DECLARE @ranges TABLE
 (
    ID INT
    ,StartDate DATETIME
    ,EndDate DATETIME
 )

 DECLARE @t1 TABLE
 (
    ResaID INT
    ,StayDate DATETIME
    ,ColA INT
    ,ColB NVARCHAR(100)
    ,ColC BIT
 )

 INSERT INTO @t1
 (
    ResaID
    ,StayDate
    ,ColA
    ,ColB
    ,ColC
 )
 VALUES
 (100, '2020-02-03', 1, 'A', 0)
 ,(100, '2020-02-04', 100, 'B', 1)
 ,(100, '2020-02-05', 255, 'C', 1)
 ,(120, '2020-04-06', 34, 'D', 1)
 ,(120, '2020-04-07', 67, 'E', 0)
 ,(120, '2020-04-08', 87, 'F', 0)
 ,(120, '2020-04-09', 545, 'G', 1)
 ,(120, '2020-04-10', 288, 'H', 0)

 INSERT INTO @ranges
 (
    ID
    ,StartDate
    ,EndDate
 )
 VALUES
 (100, '2020-06-04', '2020-06-07')
 ,(120, '2021-03-01', '2021-03-05')

--END DEMO SETUP

DROP TABLE IF EXISTS #numbers
DROP TABLE IF EXISTS #newRecords



--GENERATE NUMBERS TABLE
    ;WITH e1(n) AS
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    ), -- 10
    e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
    e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
    e4(n) AS (SELECT 1 FROM e3 CROSS JOIN (SELECT TOP 5 n FROM e1) AS b)  -- 5*10000

     SELECT  ROW_NUMBER() OVER (ORDER BY n) as Num
     INTO #numbers
     FROM e4 
     ORDER BY n;


 ;with oldData --PARTITION THE EXISTING RECORDS
 AS
 (
    SELECT *
    ,ROW_NUMBER() OVER (PARTITION BY ResaID  ORDER BY STAYDATE) as ResPartID
    FROM @t1
 )
 ,newRanges --GENERATE YOUR NEW RANGES AND PARITITION
 AS
 (
 select
    r.ID
    ,CAST(n.num as DATETIME) as StayDate
    ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY n.num) as ResPartID
 from @ranges r
    inner join #numbers n on CAST(r.StartDate as INT) <= n.Num  AND CAST(r.EndDate as INT) >= n.Num
)

SELECT n.ID
      ,n.StayDate
      ,o.ColA
      ,o.ColB
      ,o.ColC
into #newRecords
FROM newRanges n
    left join oldData o on n.ID = o.ResaID and n.ResPartID = o.ResPartID

--DELETE OLD RECORDS
DELETE t
FROM @t1 t
    inner join @ranges r on t.ResaID = r.ID

--INSERT NEW DATA
INSERT INTO @t1
(
    ResaID
    ,StayDate
    ,ColA
    ,ColB
    ,ColC
)
SELECT 
    ID
    ,StayDate
    ,ColA
    ,ColB
    ,ColC
FROM #newRecords

SELECT * FROM @t1
...