如何从数据库中删除几乎相同记录的连续序列

时间:2014-02-26 22:13:01

标签: sql sql-server

我有一个包含实时股票报价的SQL Server数据库。

有一个报价表,其中包含您期望的内容 - 序列号,股票代码,时间,价格,出价,出价大小,询问,询问大小等。

序列号对应于收到的消息,其中包含正在跟踪的一组股票代码符号的数据。每当被跟踪的任何符号发生任何变化时,就会收到一条新消息(带有一个新的递增序列号)。该消息包含所有符号的数据(即使是没有任何更改的符号)。

当数据被放入数据库时​​,为每条消息中的每个符号插入一条记录,即使对于自上一条消息以来没有任何变化的符号也是如此。因此,许多记录包含冗余信息(仅更改序列号),我想删除这些冗余记录。

这与从相同列的组合(已经回答)中删除整个数据库中除一条记录之外的所有记录不同。相反,我想将相同记录的每个连续块(除了序列号相同)压缩成单个记录。完成后,可能会有重复的记录,但它们之间的记录不同。

我的方法是找到连续的记录范围(对于股票代码),除了序列号之外,所有记录都相同。

在以下示例数据中,我通过仅显示序列,符号和价格来简化事物。复合主键是Sequence + Symbol(每个符号在消息中只出现一次)。我想删除价格与先前记录相同的记录(对于给定的股票代码)。对于股票代码X,这意味着我想要删除范围[1,6],对于股票代码Y我想删除范围[1,2],[4,5]和[7,7]:

之前:

Sequence  Symbol  Price
   0        X      $10
   0        Y      $ 5
   1        X      $10
   1        Y      $ 5
   2        X      $10
   2        Y      $ 5
   3        X      $10
   3        Y      $ 6
   4        X      $10
   4        Y      $ 6
   5        X      $10
   5        Y      $ 6
   6        X      $10
   6        Y      $ 5
   7        X      $11
   7        Y      $ 5

之后:

Sequence  Symbol  Price
   0        X      $10
   0        Y      $ 5
   3        Y      $ 6
   6        Y      $ 5
   7        X      $11

请注意,(Y,$ 5)出现两次但是(Y,$ 6)之间。

以下内容生成我需要的范围。左外连接确保我选择第一组记录(其中没有先前记录不同),BETWEEN旨在减少需要搜索以查找下一个更早的不同记录的记录数(没有BETWEEN,结果是相同的,但速度较慢)。我只需要添加类似“DELETE FROM Quotes WHERE Sequence BETWEEN StartOfRange和EndOfRange”之类的东西。

SELECT
   GroupsOfIdenticalRecords.Symbol,
   MIN(GroupsOfIdenticalRecords.Sequence)+1 AS StartOfRange,
   MAX(GroupsOfIdenticalRecords.Sequence) AS EndOfRange
FROM
   (
   SELECT
      Q1.Symbol,
      Q1.Sequence,
      MAX(Q2.Sequence) AS ClosestEarlierDifferentRecord
   FROM
      Quotes AS Q1
   LEFT OUTER JOIN
      Quotes AS Q2
   ON
          Q2.Sequence BETWEEN Q1.Sequence-100 AND Q1.Sequence-1
      AND Q2.Symbol=Q1.Symbol
      AND Q2.Price<>Q1.Price
   GROUP BY
      Q1.Sequence,
      Q1.Symbol
   ) AS GroupsOfIdenticalRecords
GROUP BY
   GroupsOfIdenticalRecords.Symbol,
   GroupsOfIdenticalRecords.ClosestEarlierDifferentRecord

问题在于,对于数据库中的200多万条记录来说,这种方式太慢而且内存不足(SSMS显着崩溃)。即使我将“-100”更改为“-2”,它仍然很慢并且内存不足。我期望LEFT OUTER JOIN的“ON”子句限制处理和内存使用(200万次迭代,每次处理大约100条记录,这应该是易处理的),但似乎SQL Server可能首先生成所有组合的根据ON子句中指定的标准进行选择之前,表格的第2个实例Q1和Q2(约4e12个组合)。

如果我在较小的数据子集上运行查询(例如,使用“(SELECT TOP 100000 FROM Quotes)AS Q1”,并且类似于Q2),它将在合理的时间内完成。我试图弄清楚如何使用“WHERE Sequence BETWEEN 0 AND 99999”,然后“...... BETWEEN 100000 AND 199999”等自动运行20次左右(实际上我会使用重叠范围,如[0, 99999],[99900,19999]等,以删除跨越边界的范围)。

以下生成范围集以将数据拆分为100000个记录块([0,99999],[100000,199999]等)。但是如何重复应用上述查询(每个范围一次)?我一直卡住,因为你不能使用“BETWEEN”对这些进行分组而不应用聚合函数。因此,我只知道如何获得MIN(),MAX()等(单个值)而不是选择记录块,而不是上述查询(如Q1和Q2)。有没有办法做到这一点?是否有完全不同(和更好)的方法解决问题?

SELECT
   CONVERT(INTEGER, Sequence / 100000)*100000 AS BlockStart,
   MIN(((1+CONVERT(INTEGER, Sequence / 100000))*100000)-1) AS BlockEnd
FROM
   Quotes
GROUP BY
   CONVERT(INTEGER, Sequence / 100000)*100000

2 个答案:

答案 0 :(得分:1)

你可以用一个不错的小技巧来做到这一点。您想要的组可以定义为两个数字序列之间的差异。按顺序按顺序为每个符号分配一个。另一个是为每个符号和价格分配的。这就是您的数据的样子:

Sequence  Symbol  Price    seq1    seq2   diff
   0        X      $10      1       1       0
   0        Y      $ 5      1       1       0
   1        X      $10      2       2       0
   1        Y      $ 5      2       2       0
   2        X      $10      3       3       0
   2        Y      $ 5      3       3       0
   3        X      $10      4       4       0
   3        Y      $ 6      4       1       3
   4        X      $10      5       5       0
   4        Y      $ 6      5       2       3
   5        X      $10      6       6       0
   5        Y      $ 6      6       3       3
   6        X      $10      7       7       0
   6        Y      $ 5      7       4       3
   7        X      $11      8       1       7
   7        Y      $ 5      8       5       3

您可以盯着这一点,并确定符号,差异和价格的组合定义了每个组。

以下内容将此内容放入SQL查询中以返回所需的数据:

select min(q.sequence) as sequence, symbol, price
from (select q.*,
             (row_number() over (partition by symbol order by sequence) -
              row_number() over (partition by symbol, price order by sequence)
             ) as grp
      from quotes q
     ) q
group by symbol, grp, price;

如果要替换原始表中的数据,我建议您将查询结果存储在临时表中,截断原始表,然后重新插入临时表中的值。

答案 1 :(得分:0)

回答我自己的问题。我想补充一些额外的评论,以补充Gordon Linoff的优秀答案。

你是对的。这是一个不错的小技巧。我不得不盯着它看一段时间才能理解它是如何工作的。这是我对他人利益的看法。

序列/符号(seq1)的编号总是增加,而符号/价格(seq2)的编号有时会增加(在每个组内,只有当Symbol的记录包含组的价格时)。因此seq1要么保持与seq2的锁定步骤(即,diff保持不变,直到符号或价格改变),或seq1从seq2“逃跑”(当它忙于“计算”其他价格和其他符号 - 这增加了给定符号和价格的seq1和seq2之间的差异)。一旦seq2落后,它就永远不会“赶上”seq1,所以一旦diff移动到下一个更大的值(对于给定的价格),就不会再看到给定的diff值。通过获取每个符号/价格组中的最小值,您将获得每个连续块中的第一条记录,这正是我所需要的。

我不经常使用SQL,所以我不熟悉OVER子句。我只是认为第一个子句生成seq1而第二个子句生成seq2。我可以看到它是如何工作的,但这不是有趣的部分。

我的数据不仅包含Price。将其他字段(Bid,Ask等)添加到第二个OVER子句和最后的GROUP BY是一件简单的事情:

row_number() over (partition by Symbol, Price, Bid, BidSize, Ask, AskSize, Change, Volume, DayLow, DayHigh, Time order by Sequence)

group by Symbol, grp, price, Bid, BidSize, Ask, AskSize, Change, Volume, DayLow, DayHigh, Time

另外,我能够使用&gt; MIN(...)和&lt; = MAX(...)来定义要删除的记录范围。