【问题标题】:Removing matching sets删除匹配集
【发布时间】:2018-07-31 23:43:16
【问题描述】:

这是我的架构和数据的示例:

declare @temp table (rowid int identity(1,1), groupNumber int, typeName varchar(10), valueA int, valueB int, effectiveDate datetime, expiredDate datetime)

insert @temp values (234545, 'Upper', 1, 1000, '1/1/18 11:31:00', '2/1/18 22:01:00')
insert @temp values (234545, 'Lower', 2, 0, '1/1/18 11:31:00', '2/1/18 22:01:00')
insert @temp values (234545, 'Upper', 1, 1000, '2/1/18 22:01:00', '4/15/18 05:39:00')
insert @temp values (234545, 'Lower', 2, 0, '2/1/18 22:01:00', '4/15/18 05:39:00')
insert @temp values (234545, 'Upper', 1, 900, '4/15/18 05:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Lower', 2, 0, '4/15/18 05:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Upper', 1, 900, '4/15/18 06:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Lower', 2, 0, '4/15/18 06:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Upper', 1, 800, '6/1/18 10:32:00', null)
insert @temp values (234545, 'Lower', 2, 0, '6/1/18 10:32:00', null)

select *
from @temp

对于每个组号,有多个组,一组由生效日期定义,生效日期始终等于前一组的过期日期。所以在这个例子中,有 5 个集合,我想要做的是删除多余的集合,即第 3/4 行和第 5/6 行。我只关心 valueA 或 valueB 发生变化时的新集合。最终我的目标是通过一次一个循环遍历组来清理生产数据库中的这些数据,因为大约 60% 的行反映了从一组到下一组在任何重要方面都没有变化(即 typeName、valueA , 值 B)。

问题是,当我删除这 4 行时,我还需要将前两行的 expiredDate 设置为等于第 7 行和第 8 行的有效日期,因为它们总是需要排队。

另一个问题是我想运行一个脚本(可能通过 SQL 代理作业)循环遍历表(obvs 不是生产中的表变量)并删除行并使用新事务更新 expiredDates每个组号。如果我在作业完成之前停止作业并且它处于事务的中间(很可能),有没有办法让它自动回滚?

【问题讨论】:

  • 为什么不想删除前两行(按照INSERT 语句的顺序),它们也过期了?以及为什么要首先删除它们。您可以将它们保留为历史记录,仅通过将时间戳与现在进行比较来检索有效行。
  • 请向我们展示预期的结果并更详细地解释所需的逻辑。
  • 我的代码大部分都可以工作,但问题是你描述的这些对似乎只是通过身份密钥彼此相邻的事实来链接。这是我们知道例如最后一行不是第二行的更高版本的唯一方法吗?除了日期匹配外,两者都有。如果组号不同,我可以让它工作!
  • 它们以日期链接,即一组的生效日期等于前一组的失效日期。

标签: sql sql-server tsql sql-server-2014


【解决方案1】:

您可以使用生成两个等级的窗口函数来识别具有较高和较低值的行,一个升序,另一个降序。

然后你可以只过滤排名第一的值:

;WITH summary AS (
    SELECT  p.rowid 
           ,p.groupNumber 
           ,p.typeName 
           ,p.valueA 
           ,p.valueB 
           ,p.effectiveDate 
           ,p.expiredDate 
           ,rank() OVER(PARTITION BY p.groupNumber ORDER BY p.effectiveDate ) AS rk_min
           ,rank() OVER(PARTITION BY p.groupNumber ORDER BY p.effectiveDate desc) AS rk_max
      FROM @temp p)
SELECT s.rowid, s. groupNumber, s.typeName, s.valueA, s.valueB,
       s.effectiveDate, s.expiredDate 
FROM summary s
WHERE s.rk_min = 1 or s.rk_max=1
ORDER BY s.rowid

结果:

如果您想识别极端值之间的内部值,只需将 where 条件更改为 WHERE s.rk_min > 1 and s.rk_max > 1

;WITH summary AS (
    SELECT  p.rowid 
           ,p.groupNumber 
           ,p.typeName 
           ,p.valueA 
           ,p.valueB 
           ,p.effectiveDate 
           ,p.expiredDate 
           ,rank() OVER(PARTITION BY p.groupNumber ORDER BY p.effectiveDate ) AS rk_min
           ,rank() OVER(PARTITION BY p.groupNumber ORDER BY p.effectiveDate desc) AS rk_max
      FROM @temp p)
SELECT s.rowid, s. groupNumber, s.typeName, s.valueA, s.valueB, 
       s.effectiveDate, s.expiredDate 
FROM summary s
WHERE s.rk_min > 1 and s.rk_max > 1
ORDER BY s.rowid

结果:

【讨论】:

  • 这行得通!它没有回答关于优雅停止或处理 expiredDate 更新的问题,但它回答了重要的部分。
  • 经过检查,这是不正确的,因为它删除了第一个和最后一个之间的所有集合,无论是否有更改。我正在更新我的问题以澄清我需要保留对 valueA 和 valueB 的所有更改。
【解决方案2】:

这不是最终脚本,因为有一些疑问。

@Doubts 1:什么是多余的行/集?为什么 3/4 和 5/6 行是多余的?答案应该涵盖所有可能的情况。

@Doubt 2 :前 2 行 expiredDate 将更新为后 2 行中的哪一行的 expiredDate。更新时前 2 行和后 2 行是什么关系?

CREATE table #temp (rowid int identity(1,1), groupNumber int, typeName varchar(10), valueA int, valueB int
, effectiveDate datetime, expiredDate datetime,isLineup int default(0))

insert #temp values (234545, 'Upper', 1, 1000, '1/1/18 11:31:00', '2/1/18 22:01:00',0)
insert #temp values (234545, 'Lower', 2, 0, '1/1/18 11:31:00', '2/1/18 22:01:00',0)
insert #temp values (234545, 'Upper', 1, 1000, '2/1/18 22:01:00', '4/15/18 05:39:00',0)
insert #temp values (234545, 'Lower', 2, 0, '2/1/18 22:01:00', '4/15/18 05:39:00',0)
insert #temp values (234545, 'Upper', 1, 900, '4/15/18 05:39:00', '6/1/18 10:32:00',0)
insert #temp values (234545, 'Lower', 2, 0, '4/15/18 05:39:00', '6/1/18 10:32:00',0)
insert #temp values (234545, 'Upper', 1, 900, '6/1/18 10:32:00', null,0)
insert #temp values (234545, 'Lower', 2, 0, '6/1/18 10:32:00', null,0)



CREATE table #temp1 (rowid int,effectiveDate datetime,Flag int )
--select * from #temp

-- Main Script

Begin Try
BEGIN TRANSACTION

-- Criteria to decide superflous rows
insert into #temp1 (rowid ,effectiveDate ,Flag  )
select top 2 rowid,effectiveDate,0 Flag from #temp where isLineup=0 ORDER by rowid
insert into #temp1 (rowid ,effectiveDate ,Flag  )
select top 2 rowid,effectiveDate,1 Flag from #temp where isLineup=0 ORDER by rowid desc
--- End

delete FROM #temp 
where not EXISTS(select 1 from #temp1 c where c.rowid=#temp.rowid )

update C 
set expiredDate=ca.effectiveDate
,isLineup=1
from #temp c
cross apply(select top 1 effectiveDate from #temp1 c1 where c1.Flag=1 )ca
where c.isLineup=0

COMMIT

End Try
begin Catch

if (@@trancount>0)
ROLLBACK TRAN

-- log error

end Catch

-- End Main

select * from #temp
select * from #temp1

drop TABLE #temp
drop table #temp1

【讨论】:

  • 我更新了我的问题,以便更清楚应该删除的内容。
  • @influent,抱歉,事实上任何人都不清楚。你为什么不把它们放在文字里。解决方案越清晰越好。
【解决方案3】:

我想出了答案:

declare @temp table (rowid int identity(1,1), groupNumber int, typeName varchar(10), valueA int, valueB int, effectiveDate datetime, expiredDate datetime)

insert @temp values (234545, 'Upper', 1, 1000, '1/1/18 11:31:00', '2/1/18 22:01:00')
insert @temp values (234545, 'Lower', 2, 0, '1/1/18 11:31:00', '2/1/18 22:01:00')
insert @temp values (234545, 'Upper', 1, 1000, '2/1/18 22:01:00', '4/15/18 05:39:00')
insert @temp values (234545, 'Lower', 2, 0, '2/1/18 22:01:00', '4/15/18 05:39:00')
insert @temp values (234545, 'Upper', 1, 900, '4/15/18 05:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Lower', 2, 0, '4/15/18 05:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Upper', 1, 900, '4/15/18 06:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Lower', 2, 0, '4/15/18 06:39:00', '6/1/18 10:32:00')
insert @temp values (234545, 'Upper', 1, 800, '6/1/18 10:32:00', null)
insert @temp values (234545, 'Lower', 2, 0, '6/1/18 10:32:00', null)

select * from @temp

DECLARE MY_CURSOR Cursor STATIC 
FOR SELECT DISTINCT groupNumber FROM @temp 

Open My_Cursor 
DECLARE @groupNumber int
Fetch NEXT FROM MY_Cursor INTO @groupNumber
While (@@FETCH_STATUS <> -1)
BEGIN
            IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp  

            SELECT RANK() OVER (PARTITION BY rp2.groupNumber ORDER BY rp2.EffectiveDate) AS TheRank, rp2.groupNumber, rp2.EffectiveDate,
                    TotalvalueA = SUM(rp2.valueA), ChecksumTotal = SUM(ISNULL(rp2.valueA,0) + ISNULL(rp2.valueB,0)), --assumes valueA and valueB can never be reversed
                                    (
                                        Select CAST(rp.typeName as varchar(2)) + ',' AS [text()]
                                        From @temp rp
                                        Where rp.groupNumber = rp2.groupNumber AND rp.groupNumber = @groupNumber
                                            and rp.EffectiveDate = rp2.EffectiveDate
                                        GROUP BY rp.typeName
                                        ORDER BY MIN(rp.typeName)                    
                                        For XML PATH ('')
                                    ) typesXML,
                    DeleteSet = 0
            INTO #temp
            FROM @temp rp2
            WHERE rp2.groupNumber = @groupNumber
            GROUP BY rp2.groupNumber, rp2.EffectiveDate

            UPDATE t2
            SET DeleteSet = 1
            From #temp t1
            LEFT JOIN #temp t2 ON t1.TheRank = t2.TheRank - 1
            WHERE t1.TotalvalueA = t2.TotalvalueA AND t1.ChecksumTotal = t2.ChecksumTotal AND t1.typesXML = t2.typesXML
            AND t2.TheRank <> (SELECT MAX(TheRank) FROM #temp)

            BEGIN TRAN

            DELETE rp
            FROM @temp rp 
            JOIN #temp t ON t.groupNumber = rp.groupNumber AND rp.EffectiveDate = t.EffectiveDate AND t.DeleteSet = 1

            if @@error != 0 raiserror('Script failed', 20, -1) with log

            UPDATE rp
            SET ExpiredDate = t2.NewExpiredDate
            FROM @temp rp
            JOIN (SELECT * , NewExpiredDate = LEAD(EffectiveDate) OVER (ORDER BY TheRank) FROM #temp WHERE DeleteSet = 0) t2 ON t2.groupNumber = rp.groupNumber AND rp.EffectiveDate = t2.EffectiveDate
            JOIN #temp t ON t.TheRank = t2.TheRank + 1
            WHERE rp.groupNumber = @groupNumber AND t2.NewExpiredDate IS NOT NULL AND rp.ExpiredDate <> t2.NewExpiredDate
            AND t.DeleteSet = 1

            if @@error != 0 raiserror('Script failed', 20, -1) with log

            PRINT 'No Errors ... Committing changes for ' + CAST(@groupNumber as varchar(15))
            COMMIT
            --select * from @temp
            --ROLLBACK
            --dbcc opentran
    WAITFOR DELAY '00:00:00:005';
    FETCH NEXT FROM MY_CURSOR INTO @groupNumber
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
GO

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-10
    • 2013-03-08
    • 2018-01-01
    • 2016-09-06
    • 2022-11-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-05
    相关资源
    最近更新 更多