从SQL Server表中删除重复行的更简单、更高效的方法

从SQL Server表中删除重复行的更简单、更高效的方法,sql,sql-server,tsql,sql-server-2008-r2,duplicates,Sql,Sql Server,Tsql,Sql Server 2008 R2,Duplicates,概述 我在SQLServer2008R2中有一个表EODBalances,它有大约2亿行。从本质上讲,它是会计系统中的总账,其作用是存储会计系统中每个账户的期末余额 表定义 任务 帐户的数量呈指数增长,因此导致EODBalances表中的行数量也以类似的方式增长。除了指数增长之外,现有的一个问题是,我们每天为每个账户添加一行,即使账户余额没有变化。我的任务是通过删除每个帐户的重复行来减少此表中的行数。我已经重构了每晚更新这个表的存储过程,这样它只会在余额发生变化时添加新行。当然,这只有在未来才会

概述 我在SQLServer2008R2中有一个表EODBalances,它有大约2亿行。从本质上讲,它是会计系统中的总账,其作用是存储会计系统中每个账户的期末余额

表定义

任务 帐户的数量呈指数增长,因此导致EODBalances表中的行数量也以类似的方式增长。除了指数增长之外,现有的一个问题是,我们每天为每个账户添加一行,即使账户余额没有变化。我的任务是通过删除每个帐户的重复行来减少此表中的行数。我已经重构了每晚更新这个表的存储过程,这样它只会在余额发生变化时添加新行。当然,这只有在未来才会发生

问题 我面临挑战的任务是清理表中的历史行,这是与重复删除相关的一种特定类型的问题。我需要保留表中任何账户余额的原始第一项,但删除期末余额不变的任何后续行。一旦它发生变化,我需要保留该特定行,然后再次删除后续行,直到它再次发生变化。等等

我尝试了几种不同的方法来实现这一点,但所有这些方法都非常低效,而且除了运行所需的时间外,还有一些副作用,如大量日志文件,这在数据库被日志发送时是一种痛苦。当前的解决方案是创建表的副本,将要保留的行复制到副本中,然后将它们从原始表中删除。完成后,我将删除原始表并将副本重命名回原始名称。这是可行的,但比我在升级窗口中可用的时间要长


有没有人遇到过类似的问题并找到了更好的解决方法?

以下是我针对类似情况提出的流程概要:

设计一种算法来识别要删除的重复行。使用groupby、min、max、row_number,不管怎样,有几种方法可以做到这一点,它们都已经发布过很多次了,听起来你已经有了一种

正如你所注意到的,这是一个很大的工作要做

将大块的工作分成几块,然后一次处理一块。随着时间的推移,将此工作分散开来,以控制事务日志的大小。如果假设您每小时进行一次t-log备份,那么一小时只运行几次此过程,以保持事务日志较小,并且t-log备份文件不会太失控

如何分割?根据你的数据,我会说是AccountId。处理数字1、10、100、1000?在每个批次中,根据您的条件,无论大小都是合理的,请参考上面的事务日志

如何管理这一切?创建“清除日志”表。使用需要检查的所有AccountId填充它,即您不必向其中添加新帐户。执行某种形式的循环,即每个帐户运行一次删除例程,或每10个帐户运行一次删除例程,等等。清除后,将清除日志表中的帐户标记为“已处理”,不再处理该帐户。记录删除了多少行以及工作何时完成,以便跟踪进度

最后一步是计划。将其全部设置为存储过程,并将SQL代理作业配置为每X次t-log备份周期调用此过程。如果它是“非侵入式”的,则将其安排在可行的窗口中全天运行,如果系统足够清晰,则安排在周日上午运行。我现在有一个在周末运行了16个小时,介于“最后一次”差异备份和每周完整备份之间。 让它运行直到工作完成。如果必须尽快完成工作,则可能必须对原木尺寸、工作时间内的性能以及其他任何方面做出让步


以下是我针对类似情况提出的流程大纲:

设计一种算法来识别要删除的重复行。使用groupby、min、max、row_number,不管怎样,有几种方法可以做到这一点,它们都已经发布过很多次了,听起来你已经有了一种

正如你所注意到的,这是一个很大的工作要做

将大块的工作分成几块,然后一次处理一块。随着时间的推移,将此工作分散开来,以控制事务日志的大小。如果假设您每小时进行一次t-log备份,那么一小时只运行几次此过程,以保持事务日志较小,并且t-log备份文件不会太失控

如何分割?根据你的数据,我会说是AccountId。处理数字1、10、100、1000?在每个批次中,根据您的条件,无论大小都是合理的,请参考上面的事务日志

如何管理这一切?Cre 创建一个“清除日志”表。使用需要检查的所有AccountId填充它,即您不必向其中添加新帐户。执行某种形式的循环,即每个帐户运行一次删除例程,或每10个帐户运行一次删除例程,等等。清除后,将清除日志表中的帐户标记为“已处理”,不再处理该帐户。记录删除了多少行以及工作何时完成,以便跟踪进度

最后一步是计划。将其全部设置为存储过程,并将SQL代理作业配置为每X次t-log备份周期调用此过程。如果它是“非侵入式”的,则将其安排在可行的窗口中全天运行,如果系统足够清晰,则安排在周日上午运行。我现在有一个在周末运行了16个小时,介于“最后一次”差异备份和每周完整备份之间。 让它运行直到工作完成。如果必须尽快完成工作,则可能必须对原木尺寸、工作时间内的性能以及其他任何方面做出让步


我将为此创建一个新表,然后重新加载数据。识别这一行并不难。您需要确定组。事情是这样的:

select e.*,
       row_number() over (partition by AccountId, balance, grp order by created) as seqnum
from (select e.*,
             (row_number() over (partition by AccountId order by created) -
              row_number() over (partition by AccountId, balance order by created)
             ) as grp
      from EODBalances e
     ) e;
select *
into temp_EODBalances
from (select e.*,
             row_number() over (partition by AccountId, balance, grp order by created) as seqnum
      from (select e.*,
                   (row_number() over (partition by AccountId order by created) - 
                    row_number() over (partition by AccountId, balance order by created)
                   ) as grp
            from EODBalances e
           ) e
      ) e
where seqnum = 1;
带有seqnum的行首先进入

然后我会这样做:

select e.*,
       row_number() over (partition by AccountId, balance, grp order by created) as seqnum
from (select e.*,
             (row_number() over (partition by AccountId order by created) -
              row_number() over (partition by AccountId, balance order by created)
             ) as grp
      from EODBalances e
     ) e;
select *
into temp_EODBalances
from (select e.*,
             row_number() over (partition by AccountId, balance, grp order by created) as seqnum
      from (select e.*,
                   (row_number() over (partition by AccountId order by created) - 
                    row_number() over (partition by AccountId, balance order by created)
                   ) as grp
            from EODBalances e
           ) e
      ) e
where seqnum = 1;
然后,我会在桌上进行测试。最后,当我感到满意并备份了原始表后,我将执行以下操作:

truncate table EODBalances;

insert into EODBalances(. . . )
    select . . .
    from temp_EODBalances;

我将为此创建一个新表,然后重新加载数据。识别这一行并不难。您需要确定组。事情是这样的:

select e.*,
       row_number() over (partition by AccountId, balance, grp order by created) as seqnum
from (select e.*,
             (row_number() over (partition by AccountId order by created) -
              row_number() over (partition by AccountId, balance order by created)
             ) as grp
      from EODBalances e
     ) e;
select *
into temp_EODBalances
from (select e.*,
             row_number() over (partition by AccountId, balance, grp order by created) as seqnum
      from (select e.*,
                   (row_number() over (partition by AccountId order by created) - 
                    row_number() over (partition by AccountId, balance order by created)
                   ) as grp
            from EODBalances e
           ) e
      ) e
where seqnum = 1;
带有seqnum的行首先进入

然后我会这样做:

select e.*,
       row_number() over (partition by AccountId, balance, grp order by created) as seqnum
from (select e.*,
             (row_number() over (partition by AccountId order by created) -
              row_number() over (partition by AccountId, balance order by created)
             ) as grp
      from EODBalances e
     ) e;
select *
into temp_EODBalances
from (select e.*,
             row_number() over (partition by AccountId, balance, grp order by created) as seqnum
      from (select e.*,
                   (row_number() over (partition by AccountId order by created) - 
                    row_number() over (partition by AccountId, balance order by created)
                   ) as grp
            from EODBalances e
           ) e
      ) e
where seqnum = 1;
然后,我会在桌上进行测试。最后,当我感到满意并备份了原始表后,我将执行以下操作:

truncate table EODBalances;

insert into EODBalances(. . . )
    select . . .
    from temp_EODBalances;

从大型表中删除行非常耗时。我不知道您是否尝试过使用分区。如果没有,我建议您以适当的方式对表进行分区,然后在每个不同的分区上更新和删除数据。您需要使用分区切换技术。搜索它是未来“重新设计”工作的好主意-但我最近发现分区仅在Enterprise SQL*Server中可用,并不是所有人都能做到这一点。从大型表中删除行非常耗时。我不知道您是否尝试过使用分区。如果没有,我建议您以适当的方式对表进行分区,然后在每个不同的分区上更新和删除数据。您需要使用分区切换技术。搜索它是未来“重新设计”工作的好主意-但我最近发现分区仅在Enterprise SQL*Server中可用,并非我们所有人都有这种功能。@FLICKER的评论也适用,但设置表分区非常重要,而且鉴于您当前的生产和使用状态,似乎不切实际,FLICKER的评论也适用,但设置表分区非常重要,而且考虑到您当前的生产和使用状态,似乎不切实际,我想说的是,我认为最有效的方法是使用OVER语法对SQL语句进行窗口化,正如您在上面详细介绍的那样。如果Hamish有必要的时间窗口和存储空间来几乎复制这个巨大的表并处理生成的日志,那么鉴于我的知识有限,我看不到比这更好的方法了。如果他不这样做,那么我同意下面菲利普的观点,在许多时间窗口内分块做是一个很好的选择。谢谢戈登。我尝试在本地计算机上的live副本上运行您建议的t-sql,但由于内存不足而失败。我想说,我认为最有效的方法是使用OVER语法作为一个窗口sql语句,正如您在上面详细介绍的那样。如果Hamish有必要的时间窗口和存储空间来几乎复制这个巨大的表并处理生成的日志,那么鉴于我的知识有限,我看不到比这更好的方法了。如果他不这样做,那么我同意下面菲利普的观点,在许多时间窗口内分块做是一个很好的选择。谢谢戈登。我尝试在本地计算机上的live副本上运行您建议的t-sql,但由于内存不足而失败。