仅在SQL中的列中选择重复值的第一行

时间:2011-12-30 20:06:13

标签: sql sqlite ms-access duplicates

我的表中有一列可能在突发中具有相同的值。像这样:

+----+---------+
| id |   Col1  | 
+----+---------+
| 1  | 6050000 |
+----+---------+
| 2  | 6050000 |
+----+---------+
| 3  | 6050000 |
+----+---------+
| 4  | 6060000 |
+----+---------+
| 5  | 6060000 |
+----+---------+
| 6  | 6060000 |
+----+---------+
| 7  | 6060000 |
+----+---------+
| 8  | 6060000 |
+----+---------+
| 9  | 6050000 |
+----+---------+
| 10 | 6000000 |
+----+---------+
| 11 | 6000000 |
+----+---------+

现在我想修剪重复Col1值的行,只选择第一次出现 对于上表,结果应为:

+----+---------+
| id |   Col1  | 
+----+---------+
| 1  | 6050000 |
+----+---------+
| 4  | 6060000 |
+----+---------+
| 9  | 6050000 |
+----+---------+
| 10 | 6000000 |
+----+---------+

我怎样才能在SQL中执行此操作? 请注意,只应删除突发行,并且可以在非突发行中重复值! id=1&在样本结果中重复id=9

编辑:
我用它实现了它:

select id,col1 from data as d1
where not exists (
    Select id from data as d2
    where d2.id=d1.id-1 and d1.col1=d2.col1 order by id limit 1)

但这只适用于ID是顺序的。由于ID(已删除的)之间存在间隙,因此查询中断。我该如何解决这个问题?

4 个答案:

答案 0 :(得分:8)

您可以使用EXISTS半联接来识别候选人:

选择想要的行:

SELECT * FROM tbl
WHERE NOT EXISTS (
    SELECT *
    FROM tbl t
    WHERE t.col1 = tbl.col1
    AND t.id = tbl.id - 1
    )
ORDER BY id

摆脱不需要的行:

DELETE FROM tbl
-- SELECT * FROM tbl
WHERE EXISTS (
    SELECT *
    FROM   tbl t
    WHERE  t.col1 = tbl.col1
    AND    t.id   = tbl.id - 1
    )

这有效地删除了前一行在col1中具有相同值的每一行,从而达到了您设定的目标:只有每个突发的第一行存活。

我离开了评论SELECT语句,因为您应该始终检查在执行契约之前要删除的内容。


非顺序ID的解决方案:

如果你的RDBMS支持CTEwindow functions(比如PostgreSQL,Oracle,SQL Server,......但不是 SQLite,MS Access或MySQL),那么优雅的方式:

WITH x AS (
    SELECT *, row_number() OVER (ORDER BY id) AS rn
    FROM tbl
    )
SELECT id, col1
FROM   x
WHERE NOT EXISTS (
    SELECT *
    FROM   x x1
    WHERE  x1.col1 = x.col1
    AND    x1.rn   = x.rn - 1
    )
ORDER BY id;

还有一种不那么优雅的方式来完成没有那些细节的工作。
应该适合您:

SELECT id, col1
FROM   tbl
WHERE (
    SELECT t.col1 = tbl.col1
    FROM   tbl AS t
    WHERE  t.id < tbl.id
    ORDER  BY id DESC
    LIMIT  1) IS NOT TRUE
ORDER BY id

用于测试套件非顺序ID的工具

(在PostgreSQL中测试)

CREATE TEMP TABLE tbl (id int, col1 int);
INSERT INTO tbl VALUES
 (1,6050000),(2,6050000),(6,6050000)
,(14,6060000),(15,6060000),(16,6060000)
,(17,6060000),(18,6060000),(19,6050000)
,(20,6000000),(111,6000000);

答案 1 :(得分:2)

select min(id), Col1 from tableName group by Col1 

答案 2 :(得分:2)

如果您的RDBMS支持Window Aggregate函数和/或LEAD()和LAG()函数,您可以利用它们来完成您尝试报告的内容。以下SQL将帮助您开始正确的道路:

SELECT id
     , Col AS CurCol
     , MAX(Col)
       OVER(ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PrevCol
     , MIN(COL)
       OVER(ORDER BY id ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) AS NextCol
FROM MyTable

从那里你可以将SQL放在一个带有一些CASE逻辑的派生表中,如果NextColPrevColCurCol相同,则设置CurCol = NULL。然后你可以折叠消除所有id记录CurCol IS NULL

如果您无法使用窗口聚合或LEAD / LAG功能,那么您的任务就会复杂一些。

希望这有帮助。

答案 3 :(得分:1)

由于id始终是连续的,没有间隙或重复,根据您的评论,您可以使用以下方法:

SELECT t1.*
FROM atable t1
  LEFT JOIN atable t2 ON t1.id = t2.id + 1 AND t1.Col1 = t2.Col1
WHERE t2.id IS NULL

表格是(外部)与自身连接,条件是左侧的id比右侧的大{1}并且它们的Col1值相同。换句话说,条件是'前一行包含与当前行'相同的Col1值。如果右边没有匹配项,则应选择当前记录。


<强>更新

要考虑非顺序id(但是,假设它们是唯一的并定义Col1的更改顺序),您还可以尝试以下查询:

SELECT t1.*
FROM atable t1
  LEFT JOIN atable t2 ON t1.id > t2.id
  LEFT JOIN atable t3 ON t1.id > t3.id AND t3.id > t2.id
WHERE t3.id IS NULL
  AND (t2.id IS NULL OR t2.Col1 <> t1.Col1)

第三个自连接用于确保第二个自连接产生直接在t1之前的行。也就是说,如果t3没有匹配,那么t2包含前一行或者它也没有匹配,后者意味着t1的当前行是最前一行。

相关问题