Postgres - 降低值后查找重复值

Postgres - Find duplicate values after lowering the values

你好 Whosebug 用户...我有一个棘手的情况,我还没有找到答案。也许你能帮帮我。

数据库:PostgreSQL 8.4(无法升级)

在这个数据库中,有一个userstable。遗憾的是,用户在创建用户配置文件时可以提供的用户名区分大小写,因此用户名 Alex 与用户名 alex[=40 不同=].

新系统即将推出,用户名不再区分大小写。我试图找到所有在旧系统中被视为重复的用户名。这样我们就可以联系他们,让他们手动更新用户名,然后将他们的用户迁移到更新的系统(没有用户名冲突)。

我有以下查询,它将显示每个用户名与 "lower()" 函数匹配的计数。

select count(*), lower(username)
  from users
  where deleted = false
  group by lower(username) having count(*) > 1

这个 returns 结果如下:

|count|lower   |
|-----+--------+
|3    |alex    |
|2    |george  |

我需要做的是将这些数据放入一个临时文件 table 并显示所有这些重复的用户和其他详细信息,以便我们有一个列表来处理。

我找到了部分临时 table,但我的主要问题是:如何获得所有这些重复项的不同值?所以在长 运行 中,我得到的结果如下所示(如果可能,甚至可能没有临时 table):

|lower  |username|
|-------+--------+
|alex   |Alex    |
|alex   |alex    |
|george |georGe  |
|george |George  |

限制:

  • 我无法从 8.4 更改 postgres 的版本
  • 有些重复的结果会超过 2 次(我目前看到的最多是 3 次)
  • 由于必须通知用户,因此除了事先联系他们之外没有其他方法可以更改数据(这就是需要列表的原因)

感谢您提供的任何 suggestions/feedback。

我通常会使用 string_agg,但它似乎在 8.4 中不受支持。似乎有一个解决方法,但请注意,由于没有 8.4 的本地副本,我还没有测试过。这样的事情应该有效:

select
  (max(u1.username)),
  array_to_string(array_agg(u2.username), ',') as duplicates
  from users u1
         inner join users u2 on u1.id < u2.id
         and lower(u1.username) = lower(u2.username)
         left join users u3 on u1.id > u3.id
         and lower(u1.username) = lower(u3.username)
         and u3.deleted = false
 where u1.deleted = false
   and u2.deleted = false
   and u3.id is null
 group by u1.id;

这将通过 ID 获取 "earliest" 用户(假设有一个不是 username 的主键。可以修改它以显示实际的小写用户名,然后在重复列。

编辑:为每个重复项显示一行:

select
  lower(u1.username),
  u2.username
  from users u1
         inner join users u2 on u1.id < u2.id
         and lower(u1.username) = lower(u2.username)
         left join users u3 on u1.id > u3.id
         and lower(u1.username) = lower(u3.username)
         and u3.deleted = false
 where u1.deleted = false
   and u2.deleted = false
   and u3.id is null
order by u1.username;

这个怎么样。只需将上面的列表生成为 CTE,然后在主查询中加入它:

WITH dups AS (
    SELECT lower(username) uname, count(*) ucount 
    FROM users 
    WHERE deleted = false 
    GROUP BY lower(username) HAVING count(*) > 1)
SELECT username, uname, ucount 
FROM users INNER JOIN dups ON lower(username) = uname 
WHERE deleted = false
ORDER BY ucount DESC, uname ASC;

 username | uname  | ucount
----------+--------+--------
 Alex     | alex   |      3
 alex     | alex   |      3
 ALEX     | alex   |      3
 GeorGe   | george |      2
 george   | george |      2
(5 rows)

如果您只想要受影响用户的裸列表,甚至更简单:

SELECT username
FROM users 
WHERE deleted = false AND lower(username) IN (
    SELECT lower(username)
    FROM users
    WHERE deleted = false
    GROUP BY lower(username) HAVING count(*) > 1)
ORDER BY lower(username) ASC;

 username
----------
 Alex
 alex
 ALEX
 GeorGe
 george
(5 rows)