如何从第一个重复项开始删除具有相同ID的其余行?

时间:2019-05-08 13:18:29

标签: sql sql-server tsql duplicates sql-delete

我对表DataTable具有以下结构:每列的数据类型均为int,RowID是标识列和主键。 LinkID是外键,链接到另一个表的行。

RowID   LinkID   Order  Data    DataSpecifier
1       120      1      1       1
2       120      2      1       3
3       120      3      1       10
4       120      4      1       13
5       120      5      1       10
6       120      6      1       13
7       371      1      6       2
8       371      2      3       5
9       371      3      8       1
10      371      4      10      1
11      371      5      7       2
12      371      6      3       3
13      371      7      7       2
14      371      8      17      4
.................................
.................................

我正在尝试执行一个查询,该查询以以下方式更改每个LinkID批次:

  • 以相同的LinkID占据每一行(例如,第一批是这里的前6行)
  • 通过Order列对其进行排序
  • DataDataSpecifier列视为一个比较单元(可以将它们视为一列,称为dataunit):
    • Order 1开始保留尽可能多的行,直到出现重复的dataunit
    • 删除该LinkID的第一个重复项之后的每一行

对于LinkID 120

  • 对批次进行排序(已在此处排序,但仍然应该这样做)
  • 从顶部开始寻找(这里是Order=1),只要没有重复就可以找到。
  • 停止第一个重复的Order = 5(已经看到dataunit 1 10)。
  • 删除所有具有LinkID=120 AND Order>=5的内容

LinkID 371(和表中的每个其他LinkID)进行类似的处理之后,处理后的表将如下所示:

RowID   LinkID   Order  Data    DataSpecifier
1       120      1      1       1
2       120      2      1       3
3       120      3      1       10
4       120      4      1       13
7       371      1      6       2
8       371      2      3       5
9       371      3      8       1
10      371      4      10      1
11      371      5      7       2
12      371      6      3       3
.................................
.................................

我已经做了很多SQL查询,但是从来没有这么复杂的东西。我知道我需要使用类似这样的查询:

DELETE FROM DataTable  
WHERE RowID IN (SELECT RowID
                FROM DataTable
                WHERE -- ?
                GROUP BY LinkID
                HAVING COUNT(*) > 1 -- ?
                ORDER BY [Order]);

但是我似乎无法解决这个问题并使查询正确。我最好在纯SQL中执行一个可执行(可重用)查询。

3 个答案:

答案 0 :(得分:2)

我们可以在此处尝试使用CTE来简化操作:

WITH cte AS (
    SELECT *,
        COUNT(*) OVER (PARTITION BY LinkID, Data, DataSpecifier ORDER BY [Order]) - 1 cnt
    FROM DataTable
),
cte2 AS (
    SELECT *,
        SUM(cnt) OVER (PARTITION BY LinkID ORDER BY [Order]) num
    FROM cte
)

DELETE
FROM cte
WHERE num > 0;

enter image description here

这里的逻辑是使用COUNT作为分析功能来识别重复记录。我们将LinkIDDataDataSpecifier一起使用。 Order值大于或等于第一个非零计数的记录的任何记录都将被删除。

这是一个演示CTE逻辑正确的演示:

Demo

答案 1 :(得分:1)

您可以使用ROW_NUMBER()窗口函数来识别原始行之后的任何行。之后,您可以删除具有匹配的LinkID且大于或等于任何遇到的Order且行号大于1的行。

(我最初使用第二个CTE来获取MIN order,但是我意识到,只要与order的连接大于等于任何order,就没有必要那里有DataUnitId的第二个实例。通过删除MIN,查询计划变得非常简单和高效。)

WITH DataUnitInstances AS (
  SELECT *
    , ROW_NUMBER() OVER
      (PARTITION BY LinkID, [Data], [DataSpecifier] ORDER BY [Order]) DataUnitInstanceId
  FROM DataTable
)
DELETE FROM DataTable
FROM DataTable dt
INNER JOIN DataUnitInstances dup ON dup.LinkID = dt.LinkID 
  AND dup.[Order] <= dt.[Order]
  AND dup.DataUnitInstanceId > 1

这是示例数据的输出,与您的期望结果相符:

+-------+--------+-------+------+---------------+
| RowID | LinkID | Order | Data | DataSpecifier |
+-------+--------+-------+------+---------------+
| 1     | 120    | 1     | 1    | 1             |
| 2     | 120    | 2     | 1    | 3             |
| 3     | 120    | 3     | 1    | 10            |
| 4     | 120    | 4     | 1    | 13            |
| 7     | 371    | 1     | 6    | 2             |
| 8     | 371    | 2     | 3    | 5             |
| 9     | 371    | 3     | 8    | 1             |
| 10    | 371    | 4     | 10   | 1             |
| 11    | 371    | 5     | 7    | 2             |
| 12    | 371    | 6     | 3    | 3             |
+-------+--------+-------+------+---------------+

答案 2 :(得分:1)

此解决方案使用APPLY查找每个链接的最小订单。

设置:

IF OBJECT_ID('tempdb..#YourData') IS NOT NULL
    DROP TABLE #YourData

CREATE TABLE #YourData (
    RowID INT,
    LinkID INT,
    [Order] INT,
    Data INT,
    DataSpecifier INT)

INSERT INTO #YourData (
    RowID,
    LinkID,
    [Order],
    Data,
    DataSpecifier)
VALUES
    ('1', ' 120', '1', '1', ' 1'), 
    ('2', ' 120', '2', '1', ' 3'), 
    ('3', ' 120', '3', '1', ' 10'), 
    ('4', ' 120', '4', '1', ' 13'), 
    ('5', ' 120', '5', '1', ' 10'), 
    ('6', ' 120', '6', '1', ' 13'), 
    ('7', ' 371', '1', '6', ' 2'), 
    ('8', ' 371', '2', '3', ' 5'), 
    ('9', ' 371', '3', '8', ' 1'), 
    ('10', '371', '4', '10', '1'), 
    ('11', '371', '5', '7', ' 2'), 
    ('12', '371', '6', '3', ' 3'), 
    ('13', '371', '7', '7', ' 2'), 
    ('14', '371', '8', '17', '4')

解决方案:

;WITH MinOrderToDeleteByLinkID AS
(
    SELECT
        T.LinkID,
        MinOrder = MIN(C.[Order])
    FROM
        #YourData AS T
        OUTER APPLY (
            SELECT TOP 1
                C.*
            FROM
                #YourData AS C
            WHERE
                C.LinkID = T.LinkID AND
                C.Data = T.Data AND
                C.DataSpecifier = T.DataSpecifier AND
                C.[Order] > T.[Order]
            ORDER BY
                T.[Order]) AS C
    GROUP BY
        T.LinkID
)
DELETE Y FROM
-- SELECT Y.* FROM
    #YourData AS Y
    INNER JOIN MinOrderToDeleteByLinkID AS M ON
        Y.LinkID = M.LinkID AND
        Y.[Order] >= M.MinOrder

要删除的行 如下:

RowID   LinkID  Order   Data    DataSpecifier
5       120     5       1       10
6       120     6       1       13
13      371     7       7       2
14      371     8       17      4

...对应于元组Data-DataSpecified对特定的LinkID开始重复的点。