我正在使用一个包含三个表的数据库:
users
、features
和一个 feature_user
连接表来管理哪些用户有权访问哪些功能。以下是包含其他更有用的列的表格:
users
表:
features
表:
id | 名字 | 描述 | 已启用 |
---|---|---|---|
1 | 通知 | 向用户发送通知 | 正确 |
2 | csv 导出 | 将数据导出为 CSV 格式 | 错误 |
3 | 重新设计登陆页面 | 修改登陆页面布局 | 正确 |
feature_user
数据透视表:
用户 ID | 功能_id |
---|---|
1 (约翰) | 1 (通知) |
1 (约翰) | 2 (csv 导出) |
1 (约翰) | 3 (重新设计登陆页面) |
2 (简) | 1 (通知) |
2 (简) | 2 (csv 导出) |
3 (约拿) | 3 (重新设计登陆页面) |
当我运行联接查询来列出所有用户及其功能时,对于具有多个功能的用户,我最终会得到冗余行。这是我当前使用的查询:
SELECT fu.user_id, u.username, u.email, u.status, fu.feature_id, f.name AS feature_name, f.description AS feature_description, f.is_enabled
FROM feature_user fu
LEFT JOIN users u ON fu.user_id = u.id
LEFT JOIN features f ON fu.feature_id = f.id;
这将返回以下结果,其中包括具有多个功能的用户的冗余行:
用户 ID | 用户名 | 电子邮件 | 状态 | 功能_id | 功能名称 | 功能描述 | 已启用 |
---|---|---|---|---|---|---|---|
1 | 约翰 | [电子邮件受保护] | 活跃 | 1 | 通知 | 向用户发送通知 | 正确 |
1 | 约翰 | [电子邮件受保护] | 活跃 | 2 | csv 导出 | 将数据导出为 CSV 格式 | 错误 |
1 | 约翰 | [电子邮件受保护] | 活跃 | 3 | 重新设计登陆页面 | 修改登陆页面布局 | 正确 |
2 | 简 | [电子邮件受保护] | 不活动 | 1 | 通知 | 向用户发送通知 | 正确 |
2 | 简 | [电子邮件受保护] | 不活动 | 2 | csv 导出 | 将数据导出为 CSV 格式 | 错误 |
3 | 约拿 | [电子邮件受保护] | 活跃 | 3 | 重新设计登陆页面 | 修改登陆页面布局 | 正确 |
注意
john
并且他的所有数据都重复了3次。
我希望避免查询结果中出现这种冗余,特别是因为分页不是我的用例的选项,并且此查询将在更大的数据集上运行。此外,这些数据最终将通过网络发送回客户端,因此减少冗余将有助于节省带宽并提高性能。
构建查询以避免每个功能重复用户数据,同时保持结果高效和可读的最佳方法是什么?
不幸的是,这就是连接的工作原理。如果您想选择所有用户,包括他们的功能,您将得到所谓的“冗余行”。
通过连接,数据库构建所谓的“交叉积”。在您的情况下,数据库正在构建其中两个:
feature_user
×
user
x feature
在构建两个叉积时,数据库会应用您在查询中指定的过滤器(行为可能会因数据库而异)。在你的情况下,这些是:
fu.user_id = u.id
fu.feature_id = f.id
SELECT
u.id AS user_id,
u.username,
u.email,
u.status,
STRING_AGG(f.name, ', ') AS feature_names
FROM users u
LEFT JOIN feature_user fu ON fu.user_id = u.id
LEFT JOIN features f ON fu.feature_id = f.id
GROUP BY u.id, u.username, u.email, u.status;
结果应该是这样的: