如何删除 MYSQL table 上的重复数据以便能够应用唯一值约束

How to delete repeated data on MYSQL table to be able to apply an unique value constraint

我有一个包含一些字段的数据库,我想将唯一值约束应用于 table:

ALTER TABLE assessment_submissions
ADD CONSTRAINT UC_Question UNIQUE (evaluated_user, evaluator_user, question_id);

但是 table 中有些数据不允许我设置此约束。

我在尝试应用约束时遇到错误:

SQL error 1062: Duplicate entry 154-154-45 for key UC_Question

看看下图:

以 id 131271 和 id 131413 开头的行的结果在字段 evaluated_userevaluator_userquestion_id.

上具有相同的值

这样就无法应用约束。

我删除了重复的行,但仍然无法应用约束。

我想里面有更多重复数据table。如何找到 table 中所有重复的数据?我可以使用哪个查询来做到这一点?

我不知道从哪里开始。

此查询将 return 所有重复行,以及属于同一“组”的几个 ID。

select evaluated_user, evaluator_user, question_id, count(*), min(id), max(id)
from assessment_submissions
group by evaluated_user, evaluator_user, question_id
having count(*) > 1

这在一定程度上取决于您是否要在决定删除哪些行之前检查这些行(分别参见下面的 1. 2.)。

假设您有以下示例数据:

id  evaluated_user  evaluator_user  question_id   answer_id
1   262             275             157            573
2   262             275             162            593
3   262             275             332           1260
4   262             275             161            589
5   262             275             157            573
6   262             275             157           1425
7   262             275             167            726
8   262             275             167           4593
  1. 如果您想查看行以获取需要删除哪些行的信息,仅按分组不会获得 ID。

    如果您有 MySQL 8.0,您可以使用 window 函数计算每个唯一 (evaluated_user, evaluator_user, question_id) 组合的重复数,如下所示(顺序是可选的):

      select *, count(*) over (partition by evaluated_user, evaluator_user, question_id) as cnt
        from example e
    order by cnt desc, evaluated_user, evaluator_user, question_id
    

    这会给你

    id   evaluated_user  evaluator_user  question_id   answer_id  cnt
    1    262             275             157            573       3
    5    262             275             157            573       3
    6    262             275             157           1425       3
    7    262             275             167            726       2
    8    262             275             167           4593       2
    4    262             275             161            589       1
    2    262             275             162            593       1
    3    262             275             332           1260       1
    

    在此 table 中,所有 cnt > 1 的条目都是您感兴趣的行。如果您只需要它们,请将其包装到 select * from ... where cnt > 1.

    对于 MySQL 的早期版本(不支持 window 函数),您可以使用类似@DNNX 答案中的查询并将结果与​​原始结果相结合来实现相同的目的table:

    select e.*
      from example e
      join (select evaluated_user, evaluator_user, question_id
              from example
          group by evaluated_user, evaluator_user, question_id
            having count(*) > 1) f
    on e.evaluated_user = f.evaluated_user and
       e.evaluator_user = f.evaluator_user and
       e.question_id = f.question_id
    

    其中任何一个都会为您提供您可能想要检查以决定要删除哪些行的行:

    id   evaluated_user  evaluator_user  question_id   answer_id 
    1    262             275             157            573      
    5    262             275             157            573      
    6    262             275             157           1425      
    7    262             275             167            726      
    8    262             275             167           4593       
    
  2. 如果您不需要先检查数据来决定删除哪些记录,只要保留一行,您就可以使用 RANK() 函数(同样,MySQL 8.0):

    with subtab as (select id, rank() over (partition by evaluated_user, evaluator_user, question_id order by id) as rnk
                      from example)
    delete from example e
    where e.id in (select id 
                    from subtab 
                   where rnk > 1)
    

    此示例将针对每个唯一组合批量删除所有重复行,但具有最小 id 的行除外。您可以修改 order by 语句来影响要删除的记录。例如,要保留具有最高 ID 的记录,您可以 order by id desc。或者,如果您想保留 answer_id 最小的那个,您 order by answer_id。注意:如果您在排序时所依据的列中有重复的条目,您最终可能会剩下不止一行。为避免这种情况,请使用 row_number() 而不是 rank()

    要在没有 window 功能的情况下获得相同的效果,您可以使用

    delete e from example e 
      join example f 
        on e.evaluated_user = f.evaluated_user 
       and e.evaluator_user = f.evaluator_user 
       and e.question_id = f.question_id
       and e.id > f.id ;
    

    同样,可以根据要保留的行修改语句。例如,要保留最高 answer_id 的条件,您将最后一个条件更改为 and e.answer_id < f.answer_id.

请参阅 this db<>fiddle 以了解上述所有操作。

请根据手头的需要使用这两个中的任何一个。本质上,您需要决定是保留最先到达数据库的副本集还是最后到达的副本集。

DELETE t1 FROM assessment_submissions t1 INNER JOIN assessment_submissions t2 
WHERE t1.id < t2.id 
AND t1.evaluated_user=t2.evaluated_user AND t1.evaluator_user = t2.evaluator_user 
AND t1.question_id=t2.question_id;
DELETE t1 FROM assessment_submissions t1 INNER JOIN assessment_submissions t2 
WHERE t1.id > t2.id 
AND t1.evaluated_user=t2.evaluated_user AND t1.evaluator_user = t2.evaluator_user 
AND t1.question_id=t2.question_id;

对于 MySQL 版本早于 8.X

的用户环境,我更喜欢上述方法