mysql查询可以有效地删除重复项

时间:2013-01-26 23:39:29

标签: mysql performance duplicate-removal

嗨大家好,感谢您的阅读

我的网站上有一个测验功能,它将分数,用户名和IP地址存储为最重要的列。我目前有一系列可怕的观点根据我需要的标准带回高分......

最低得分,但......只是每个测验用户的最低得分。

如果用户更改了ip,即保持相同的用户名但具有不同的IP或者如果用户保留相同的IP地址但更改用户名,则复杂性就在于此。

通过示例更容易解释。

  • 第一位访客有4个条目,但来自3个不同的IP地址
  • 来自2个IP地址的第二位用户
  • 使用一个IP地址但使用3个用户名的第三个用户

Table with VALUES(UserID, IPA, Score)

  • 用户1,IP1,13
  • 用户1,IP1,20
  • 用户1,IP2,30
  • 用户1,IP3,10
  • 用户2,IP4,20
  • 用户2,IP5,22
  • 用户2,IP5,15
  • 用户3,IP6,12
  • 用户3,IP6,20
  • 用户4,IP6,15
  • 用户5,IP6,1

高分查询会向您显示

  1. 用户1,IP3,10
  2. 用户5,IP6,11
  3. 用户2,IP5,15
  4. 得分值极不可能重复,但我想这是可能的。上面的数字被简化以解释我的难题!

    任何人都可以提出一种有效的方法来删除这些重复项,因为我的表现已超过15,000条记录并且视图嘎然而止!

    非常感谢。

1 个答案:

答案 0 :(得分:3)

识别重复(UserID,IPA)元组的出现非常简单:

SELECT s.UserID
     , s.IPA
  FROM mytable s
 GROUP
    BY s.UserID
     , s.IPA
HAVING COUNT(1) > 1

要获得最低分,您可以将MIN(s.Score)添加到选择列表中。

删除重复项有点困难,因为您似乎没有任何唯一性保证。有些人会建议您将要保留的行复制到单独的表中,然后使用重命名交换表,或者截断原始表并从新表重新加载。 (这通常是最有效的方法。)

CREATE TABLE newtable LIKE mytable ;

INSERT INTO newtable (UserID,IPA,Score)
SELECT s.UserID
     , s.IPA
     , MIN(Score) AS Score
  FROM mytable s
 GROUP
    BY s.UserID
     , s.IPA ;

如果您只想通过UserID识别重复项,则可以使用相同的方法。如果IPA值来自得分最低的行并不重要,那就更容易了。我可以将获取用户得分最低的行的查询放在一起。


如果要从现有表中删除行,而不在每行上添加唯一标识符(如AUTO_INCREMENT id列),也可以这样做。

这会让你中途,删除分数高于最低分的给定(UserID,IPA)的所有行:

DELETE t.*
  FROM mytable t
  JOIN ( SELECT s.UserID
              , s.IPA
              , MIN(s.Score)
           FROM mytable s
          GROUP
             BY s.Userid
              , s.IPA
       ) k
    ON k.UserID = t.UserID
   AND k.IPA = t.IPA
   AND k.Score < t.Score

但是仍然会出现重复(UserID,IPA,Score)元组重复出现的情况。如果表中没有其他列使行唯一,则删除重复项会更困难一些。 (同样,一种常见的技术是将要保留的行复制到另一个表,并交换表或从保存的行重新加载原始表。


后续

请注意,使用MySQL,视图(存储和内联)的性能都很昂贵,因为视图实现为临时MyISAM表(MySQL称之为“派生表”)。

但相关的子查询在大型集合上可能更成问题。

所以,选择你的毒药。

如果表中有索引ON (userID, Score, IPA),那么我将如何得到结果集:

SELECT IF(@prev_user=t.UserID,@i:=@i+1,@i:=1) AS seq
     , @prev_user := t.UserID AS UserID
     , t.IPA
     , t.Score
  FROM mytable t
  JOIN (SELECT @i := NULL, @prev_user := NULL) i
 GROUP
    BY t.UserID ASC
     , t.Score ASC
     , t.IPA ASC
HAVING seq = 1

这利用了一些特定于MySQL的功能:user_variables并保证GROUP BY将返回已排序的结果集。 (EXPLAIN输出将显示“Using index”,这意味着我们避免了排序操作,但查询仍将创建派生表。我们使用user_variables来标识每个UserID的“第一”行,并且HAVING子句将全部删除但是第一行。


测试用例:

create table mytable (UserID VARCHAR(6), IPA varchar(3), Score INT);
create index mytable_IX ON mytable (UserID, Score, IPA);
insert into mytable values ('User 1','IP1',13)
,('User 1','IP1',20)
,('User 1','IP2',30)
,('User 1','IP3',10)
,('User 2','IP4',20)
,('User 2','IP5',22)
,('User 2','IP5',15)
,('User 3','IP6',12)
,('User 3','IP6',20)
,('User 4','IP6',15)
,('User 5','IP6',11);

另一个后续行动

从结果集中消除“用户4”和“用户5”(完全不清楚为什么你会想要或者需要这样做。如果是因为那些用户在表中只有一行,那么你可以添加JOIN到子查询(内联视图),获取有多个UserID值的列表,其中有多行,如下所示:

SELECT IF(@prev_user=t.UserID,@i:=@i+1,@i:=1) AS seq
     , @prev_user := t.UserID AS UserID
     , t.IPA
     , t.Score
  FROM mytable t
  JOIN ( SELECT d.UserID
           FROM mytable d
          GROUP
             BY d.UserID
         HAVING COUNT(1) > 1
       ) m
    ON m.UserID = t.UserID
 CROSS
  JOIN (SELECT @i := NULL, @prev_user := NULL) i
 GROUP
    BY t.UserID ASC
     , t.Score ASC
     , t.IPA ASC
HAVING seq = 1