【问题标题】:What is the most effcient way to replace values in a specific column of a table for this specific scenario?对于这种特定情况,替换表特定列中的值的最有效方法是什么?
【发布时间】:2020-04-23 16:07:25
【问题描述】:

我正在使用SQL Server 2014,并且我的数据库中有一个名为t1 的表(仅提取下面显示的两列):

 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

有没有更有效的方法来解决这个问题?

【问题讨论】:

  • 您有单独的日期范围表格吗?
  • 还没有,但我可以为这些日期范围创建一个表格。
  • 请阅读this,了解一些改进问题的技巧。
  • @HABO 感谢您的链接。我一定会看看的。
  • 我正在寻找答案,但有几个问题。您提到您的 t1 表还有其他列。在这些“辅助”列中,数据是 ResaID 和 StayDate 独有的,还是仅仅是 ResaID?另外,每次的停留时间都一样吗?与 ResaID 一样,有 3 个停留日期,并且在新表中还提供了 3 天的范围。这是否始终一致,或者更新的数据是否包含更短/更长的停留时间?

标签: sql-server tsql case


【解决方案1】:

你可以使用递归方法:

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;

【讨论】:

  • 感谢您的输入,但我对您的解决方案中使用的别名有点困惑。假设我需要更新的表叫做 t1,那么它在那个 T-SQl 查询中去哪里了?
  • 我也对查询中的“cte c”和“r.cte”感到困惑。
  • @user3115933。 . . cte_seq 是可更新的 cte,您可以在其中使用 t1。检查更新。
【解决方案2】:

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

然后我会执行 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

【讨论】:

    【解决方案3】:

    以下代码将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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-29
      • 1970-01-01
      • 2016-09-05
      • 1970-01-01
      • 2016-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多