SQL 用于根据到期日期有选择地删除重复记录

SQL for selectively deleting duplicate records based on expiry dates

我有一个 MariaDB table 如下:

userid username email expirydate
1 jackd jackd@example.com 2018-10-09
2 jillf jillf@example.com 2022-12-19
3 aaron aaron@someone.com 2022-09-29
4 aaron1 aaron@someone.com 2021-12-19
5 jackd2 jackd@example.com 2017-11-03
6 jackd3 jackd@example.com 2019-10-09
7 simd simd@somewhere.com 2023-03-13
8 simdb simd@somewhere.com 2024-10-09

它允许用户使用他们的用户名登录,这是唯一的。这 其他列不是唯一的。具体来说,一个用户可以有多个 使用不同用户名的帐户,但每个帐户都可以拥有 相同的电子邮件地址。大多数用户只有一个用户名。

我们正在转向基于电子邮件而非用户名的登录系统 这意味着电子邮件现在需要是唯一的,我们需要清理 table 因为我们有重复的电子邮件。将用户与电子邮件关联 意味着用户可以有一些未过期的帐户,一些 已过期以及这两个属性的任意组合。为了 例如,电子邮件为 jackd@example.com 的用户有三个帐户,其中 都过期了,而 aaron@someone.com 有一个过期的帐户和 一个活跃账户。 simd@somewhere.com 有两个帐户都是 积极的。过期和有效的其他组合也是可能的。

我想做以下事情:

结尾 table 看起来像这样:

userid username email expirydate
2 jillf jillf@example.com 2022-12-19
3 aaron aaron@someone.com 2022-09-29
6 jackd3 jackd@example.com 2019-10-09
7 simd simd@somewhere.com 2023-03-13
8 simdb simd@somewhere.com 2024-10-09

我已经尝试了所有我能想到的方法,但我总是失败。 任何帮助将不胜感激。非常感谢!

您可以通过使用 CTE 确定每个用户的到期日期顺序并确定该用户是否有任何未到期的帐户来获得您想要的结果(利用 MIN逻辑值相当于 AND)。然后你 select 来自该 CTE 的行,其中帐户尚未过期,或者 - 如果电子邮件只有过期帐户 - 该帐户具有最新的到期日期:

WITH rns AS (
  SELECT *, 
         ROW_NUMBER() OVER (PARTITION BY email ORDER BY expirydate DESC) AS rn,
         MIN(expirydate < CURDATE()) OVER (PARTITION BY email) AS allexpired
  FROM users
)
SELECT userid, username, email, expirydate
FROM rns
WHERE expirydate >= CURDATE()
   OR allexpired AND rn = 1
ORDER BY userid

输出(对于您的样本数据):

userid  username    email               expirydate
2       jillf       jillf@example.com   2022-12-19
3       aaron       aaron@someone.com   2022-09-29
6       jackd3      jackd@example.com   2019-10-09
7       simd        simd@somewhere.com  2023-03-13
8       simdb       simd@somewhere.com  2024-10-09

Demo on dbfiddle

要从 table 中删除不需要的记录,您可以使用 DELETE 和 CTE,只要您是 运行 MariaDB 10.4 或更高版本(有一个错误这会阻止它在 MariaDB 10.3 及更早版本中工作。

DELETE users FROM users
JOIN (
  WITH rns AS (
    SELECT *, 
           ROW_NUMBER() OVER (PARTITION BY email ORDER BY expirydate DESC) AS rn,
           MIN(expirydate < CURDATE()) OVER (PARTITION BY email) AS allexpired
    FROM users
  )
  SELECT userid
  FROM rns
  WHERE expirydate < CURDATE() AND (NOT allexpired OR rn > 1)
) del
WHERE users.userid = del.userid

对于 MariaDB 10.3 及更早版本,最简单的解决方法是创建一个包含所需行的新 table,删除旧的 table,然后将新的 table 重命名为旧的.这将是 CREATE TABLE 命令:

CREATE TABLE users2 (
  `userid` INTEGER,
  `username` VARCHAR(6),
  `email` VARCHAR(18),
  `expirydate` DATE
) AS
WITH rns AS (
  SELECT *, 
         ROW_NUMBER() OVER (PARTITION BY email ORDER BY expirydate DESC) AS rn,
         MIN(expirydate < CURDATE()) OVER (PARTITION BY email) AS allexpired
  FROM users
)
SELECT userid, username, email, expirydate
FROM rns
WHERE expirydate >= CURDATE()
   OR allexpired AND rn = 1
ORDER BY userid

Demo on dbfiddle