Sql server 对于此特定场景,替换表中特定列中的值的最有效方法是什么?
我使用的是SQL Server 2014,我的数据库中有一个名为t1 extract的表,只有2列,如下所示: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
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。检查是否已更新。