识别重复(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