我将有三个表,因为用户和许可证之间似乎存在 N:M(多对多关系):
users
------------
user_id [PK]
...
users_has_licenses
----------------------
user_id [PK] (references users.user_id)
license_id [PK] (references licenses.license_id)
issued [PK]
expire_date
licenses
-------------------
license_id [PK]
...
表格users
和licenses
相当简单。它们将用户和许可证固有的信息存储为独立实体。
在users_has_licenses
交叉引用表中,行在三个字段中是唯一的:user_id
、license_id
和issued
。issued
是一个日期时间字段,表示许可证的颁发日期或开始日期。由于用户可以多次续订许可证,并且您需要保留许可证续订的历史记录,因此我们将此字段作为复合主键的一部分。
您可以不使用expired
字段,因为您已经可以从数据本身判断它是否已过期,并且您不必进行例行更新以使数据保持最新。例如,要查找用户的过期许可证,您只需执行以下查询:
SELECT
a.license_id
FROM
(
SELECT user_id, license_id, MAX(issued) AS issued
FROM users_has_licenses
WHERE user_id = <user_id here>
GROUP BY user_id, license_id
) a
INNER JOIN
users_has_licenses b ON
a.user_id = b.user_id AND
a.license_id = b.license_id AND
a.issued = b.issued AND
b.expire_date < NOW()
这比您预期的要忙一些,但这只是因为您保留了过去许可证续订的数据。在这种情况下,您必须按组进行最大值,以确保您获得最新的许可期限。第一个子选择找出每个特定用户的最新许可证,然后在expire_date
已经通过的条件下加入。
如果您想获取所有用户的许可证(最近一段时间),无论它们是否已过期,并且仍想知道它们是否已过期,只需将 更改INNER JOIN
为 a LEFT JOIN
,所有未过期的许可证都将具有NULL
值连接的表。
要更新用户的许可证,您只需要一个INSERT
而不是一个INSERT
和一个UPDATE
,就像您当前的设计一样:
INSERT INTO users_has_licenses VALUES (<user_id here>, <license_id here>, NOW(), NOW() + INTERVAL 4 YEAR)
编辑:解决提问者对此答案的评论:
我还有一个关于更新许可证的问题。如果用户在第一个许可证 6 个月后续订许可证,您将如何在上面列出的 INSERT INTO 语句中包含额外的时间?我认为最后一个值需要是 now() +remaining time + expiry period(或类似的东西)。除非我误解了什么。我只是不确定如何获得剩余时间。
假设您选择不保留过去许可期的历史记录,您可以UPDATE
对当前记录执行以下操作:
UPDATE users_has_licenses a SET
a.issued = NOW(),
a.expire_date = (
SELECT
NOW()
+ INTERVAL CASE WHEN DATEDIFF(expire_date, NOW()) < 0 THEN 0 ELSE DATEDIFF(expire_date, NOW()) END DAY
+ INTERVAL 4 YEAR
FROM
users_has_licenses
WHERE
user_id = a.user_id AND
license_id = a.license_id
)
WHERE
a.user_id = <user_id here> AND
a.license_id = <license_id here>
这将更新issued
为当前日期和expire_date
当前日期+旧期限的剩余天数+常规到期期限(无论它可能是什么)。如果过去的许可证已经过期,那么剩余天数将为负数,在这种情况下,我们只需添加常规过期时间。
作为关于我发布到原始问题的上述架构的旁注,您不再需要将issued
其作为主键的一部分。