如何从第一个重复项开始删除具有相同 ID 的其余行?
How to remove rest of the rows with the same ID starting from the first duplicate?
我对 table DataTable
有以下结构:每一列都是数据类型 int,RowID
是标识列和主键。 LinkID
是外键并链接到另一个 table.
的行
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
列排序
- 将
Data
和DataSpecifier
列看成一个比较单元(可以认为是一列,称为dataunit
):
- 从
Order
1 开始保留尽可能多的行,直到出现重复的 dataunit
- 从第一个重复项开始删除每一行
LinkID
所以对于 LinkID
120
:
- 对批次进行排序(已在此处排序,但仍应这样做)
- 从顶部开始查看(所以
Order=1
此处),只要您没有看到重复项即可。
- 停在第一个重复项
Order = 5
(dataunit
1 10
已经看到)。
- 删除所有包含
LinkID=120 AND Order>=5
的内容
在对 LinkID
371
(以及 table 中的所有其他 LinkID
)进行类似处理后,处理后的 table 将如下所示:
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 中使用一个 executable(并且可重用)查询来执行此操作。
我们可以在这里尝试使用 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;
这里的逻辑是使用COUNT
作为解析函数来识别重复记录。我们使用 LinkID
的分区以及 Data
和 DataSpecifier
。任何 Order
值大于或等于第一个非零计数记录的记录都将被删除。
这里有一个演示,说明CTE的逻辑是正确的:
您可以使用 ROW_NUMBER()
window 函数来识别原始行之后的任何行。之后,您可以删除具有匹配 LinkID
和大于或等于任何遇到的行号大于 1 的 Order
的行。
(我最初使用第二个 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 |
+-------+--------+-------+------+---------------+
此解决方案使用 APPLY
找到每个 Link 的最小顺序。
设置:
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
.
的点
我对 table DataTable
有以下结构:每一列都是数据类型 int,RowID
是标识列和主键。 LinkID
是外键并链接到另一个 table.
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
列排序 - 将
Data
和DataSpecifier
列看成一个比较单元(可以认为是一列,称为dataunit
):- 从
Order
1 开始保留尽可能多的行,直到出现重复的dataunit
- 从第一个重复项开始删除每一行
LinkID
- 从
所以对于 LinkID
120
:
- 对批次进行排序(已在此处排序,但仍应这样做)
- 从顶部开始查看(所以
Order=1
此处),只要您没有看到重复项即可。 - 停在第一个重复项
Order = 5
(dataunit
1 10
已经看到)。 - 删除所有包含
LinkID=120 AND Order>=5
的内容
在对 LinkID
371
(以及 table 中的所有其他 LinkID
)进行类似处理后,处理后的 table 将如下所示:
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 中使用一个 executable(并且可重用)查询来执行此操作。
我们可以在这里尝试使用 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;
这里的逻辑是使用COUNT
作为解析函数来识别重复记录。我们使用 LinkID
的分区以及 Data
和 DataSpecifier
。任何 Order
值大于或等于第一个非零计数记录的记录都将被删除。
这里有一个演示,说明CTE的逻辑是正确的:
您可以使用 ROW_NUMBER()
window 函数来识别原始行之后的任何行。之后,您可以删除具有匹配 LinkID
和大于或等于任何遇到的行号大于 1 的 Order
的行。
(我最初使用第二个 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 |
+-------+--------+-------+------+---------------+
此解决方案使用 APPLY
找到每个 Link 的最小顺序。
设置:
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
.