0

大家好,感谢阅读

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

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

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

用一个例子更容易解释。

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

带有 VALUES(用户 ID、IPA、分数)的表

  • 用户 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, 11

高分查询将为您呈现

  1. 用户 1、IP3、10
  2. 用户 5, IP6, 11
  3. 用户 2, IP5, 15

得分值极不可能被复制,但我想这是可能的。上面的数字被简化以解释我的难题!

任何人都可以建议一种有效的方法来删除这些重复项,因为我的表现在有超过 15,000 条记录并且视图正在吱吱作响!

非常感谢。

4

1 回答 1

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
于 2013-01-26T23:58:03.470 回答