Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 对于此特定场景,替换表中特定列中的值的最有效方法是什么?_Sql Server_Tsql_Case - Fatal编程技术网

Sql server 对于此特定场景,替换表中特定列中的值的最有效方法是什么?

Sql server 对于此特定场景,替换表中特定列中的值的最有效方法是什么?,sql-server,tsql,case,Sql Server,Tsql,Case,我使用的是SQL Server 2014,我的数据库中有一个名为t1 extract的表,只有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

我使用的是SQL Server 2014,我的数据库中有一个名为t1 extract的表,只有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
有没有更有效的方法来解决这个问题?

您可以使用递归方法:

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;

这是我解决这个问题的方法。我将使用数字表为每个预订ID的新范围内的每个日期生成一条记录。然后,我将根据预订ID对该数据进行分区,并按日期排序。对现有数据执行相同的分区逻辑将允许记录正确地连接在一起

然后执行删除操作,然后执行插入操作。这将为您留下适当数量的记录。唯一需要手动完成的事情是使用扩展的日期范围填充预订的辅助数据。我扩展了你的一个新范围来展示这个场景

我已经在下面的代码中标记了此演示的设置结束位置。下面的所有内容都是我预期的解决方案,应该能够用实际表实现

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

以下代码将t1日期转换为范围,然后使用相应的范围日期计算新的StayDate值。您可以交换其中一条注释语句的最终select,以查看CTE中发生了什么。如果要更改原始表数据,可以使用更新替换最终选择

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

您有单独的日期范围表吗?还没有,但我可以为这些日期范围创建一个表。请阅读以获得一些改进您的问题的提示。@HABO感谢您的链接。我一定会看一看。我正在寻找答案,但有几个问题。您提到t1表还有其他列。在这些辅助列中,数据是ResaID和StayDate独有的,还是仅ResaID独有的?另外,每次的停留时间是否都一样?正如在ResaID中一样,新表中提供了3个停留日期和3天的范围。这是否总是一致的,或者更新的数据是否包含较短/较长的停留时间?感谢您的输入,但我对您的解决方案中使用的别名有点困惑。假设我需要更新的表名为t1,那么它在T-SQl查询中的位置是什么?我还对查询中的cte c和r.cte感到困惑。@user3115933。cte_seq是可更新的cte,您可以在其中使用t1。检查是否已更新。