【问题标题】:How to delete all duplicate records except one from a table? [duplicate]如何从表中删除除一条之外的所有重复记录? [复制]
【发布时间】:2017-01-10 06:00:15
【问题描述】:

假设有一个表包含 10 条记录,其中 5 条完全相同(这意味着该表中没有主键或唯一键),所以问题是“写一个SQL 查询删除所有重复记录,在这 5 条重复记录中只留下一条记录”,因此最终该表中必须有 6 条不同的记录。 其实今天面试被问到这个问题,我也答不上来。谁能帮我解决这个问题?

【问题讨论】:

  • 这是mysql还是postgres?它目前标记为两者。
  • 什么都可以。我只需要逻辑。
  • 随便搜一下,this之类的方法很多

标签: mysql sql postgresql rdbms


【解决方案1】:

您可以通过以下步骤实现它。

1) 在临时表中存储不同的记录。

2) 截断原始表。

3) 将临时数据插入原始数据。

select * into #tmp from original_table where 1=2 insert into #tmp select distinct * from original_table . truncate table original_table insert into original_table select * from #tmp

【讨论】:

  • 这当然是所有 RDBMS 的保证方法,但对于特定版本,有更有效的就地方法依赖 row_number 或保证唯一的伪列(PostgreSQL 的“ctid”, “rowid”代表 Oracle)。
【解决方案2】:

PostgreSQL:

以下是示例表/数据

CREATE TABLE ident (
    a INT
    ,b INT
    ,c INT
    );

INSERT INTO ident
SELECT generate_series(10, 14)
    ,generate_series(100, 104)
    ,generate_series(210, 214);

INSERT INTO ident
SELECT unnest(array [1,1,1,1,1])
    ,unnest(array [1,1,1,1,1])
    ,unnest(array [1,1,1,1,1]);

由于表格没有primary/unique,我们可以使用ctid

ctid

行版本在其表中的物理位置。注意 虽然 ctid 可用于非常快速地定位行版本, 如果一行被 VACUUM FULL 更新或移动,它的 ctid 将会改变。 因此 ctid 作为长期行标识符是无用的。 OID,或 更好的是用户定义的序列号,应该用于识别 逻辑行。

select ctid,* from  ident;

将为您提供以下行

ctid   a  b   c   
------ -- --- --- 
(0,1)  10 100 210 
(0,2)  11 101 211 
(0,3)  12 102 212 
(0,4)  13 103 213 
(0,5)  14 104 214 
(0,6)  1  1   1   
(0,7)  1  1   1   
(0,8)  1  1   1   
(0,9)  1  1   1   
(0,10) 1  1   1   

我们应该使用windows函数找到相同行的ctid

SELECT ctid
        FROM (
            SELECT ctid
                ,row_number() OVER (
                    PARTITION BY a
                    ,b
                    ,c ORDER BY ctid
                    ) rn
            FROM ident
            ) t
        WHERE rn > 1

并从表中删除行

DELETE
FROM ident
WHERE ctid IN (
        SELECT ctid
        FROM (
            SELECT ctid
                ,row_number() OVER (
                    PARTITION BY a
                    ,b
                    ,c ORDER BY ctid
                    ) rn
            FROM ident
            ) t
        WHERE rn > 1
        );

sqlfiddle

你可以简单地使用

delete from ident where  ctid not in (
select min(ctid) from ident group by a,b,c
)

【讨论】:

  • 类似的方法也适用于 Oracle,使用 ROWID 代替 CTID
  • @DavidAldridge 感谢提醒,我没有使用 Oracle
【解决方案3】:

这里我使用了一个窗口函数,与其他不同的 5 行不同的一列或两列,以使重复的 5 行中的记录唯一可以在partition by 以逗号分隔之后进入窗口函数。
注意:使用 PostgreSQL 语法。

 ALTER TABLE table_name ADD COLUMN id SERIAL;
 UPDATE table_name SET id = DEFAULT;
 ALTER TABLE table_name ADD PRIMARY KEY (id);

DELETE FROM table_name
WHERE id IN 
(SELECT id  FROM (SELECT id, ROW_NUMBER() OVER (partition BY column_name ORDER BY id) AS rnum
                     FROM table_name) t
              WHERE t.rnum > 1);

检查这个SQLFiddle

【讨论】:

  • 问题表明没有钥匙;所有行都完全相同
  • @Bohemian 是的,我更正了描述。如果他说 5 条记录是相同的,而 5 条是完全相同的。必须有一列来区分它们。这样就可以使用该列。
  • 不,必须有另一列来区分行。可以创建没有键的表。
  • @Bohemian 是的,我同意这是可能的。我创建了一个类似于您的 SQL Fiddle 的表(检查我的 SQL fiddle)并执行了我的查询,方法是通过在您的表中命名的列 a 来区分它们。 (我的column_name1)。它提供了所需的输出。我假设列 column_name1 在删除重复项后是唯一的。如果需要,也可以使用其他列,以便删除后组合。
  • 我没有将问题解读为暗示每一列都是唯一的,而是 select distinct * 返回的行数与 select * 相同。
【解决方案4】:

既然你不知道,这是一个真正的帮助请求......

首先,这个问题是:

  1. 有点可笑。应该解雇没有密钥创建表的人
  2. 面试题相当难

如果将多个用分号分隔的查询算作一个“查询,这里有一个mysql解决方案:

alter table mytable add column id int primary key auto_increment;
delete t1
from mytable t1
join mytable t2 on t1.id < t2.id
  and t1.a = t2.a and t1.b = t2.b and t1.c = t2.c;
alter table mytable drop column id

SQLFiddle

【讨论】:

  • Re (1),那里有很多无钥匙桌子。几乎我使用过的每个系统都有很多表,根本没有自然键。
猜你喜欢
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 1970-01-01
  • 2018-02-17
  • 1970-01-01
  • 2017-02-02
相关资源
最近更新 更多