我在 MySQL 中有三个相关的表,但在技术上并未通过外键相互链接。它们是:用户、级别和类别。
users 表有一个 karma 列,一个数字类型。根据这个业力数字,我想知道用户的级别,这是我从级别表中检索的。这不是一个硬性关系,因为一个级别与一个范围的业力值相关联,如下所示:
- 1 级(最低业力:0)
- 2 级(最低业力:100)
- 3 级(最低业力:500)
- ...
因此,如果用户的业力为 400,则返回值应为 2。为了使事情稍微复杂一些,级别编号表示用户的类别,其定义存储在类(class)表。这又是一个范围关系:
- Ant 等级(最低等级 1)
- 猫头鹰等级(最低等级 5)
- 狮子级(最低等级 100)
- ...
总之,我们讨论的是基于范围值而彼此具有隐式关系的三个表。我的问题涉及如何有效地查询这些表。我的一个非常常见的需求是根据条件获取一个或多个用户的信息,结果集应该包含用户详细信息,而且还包含用户的级别和类别。
对于单个用户,我设法编写了这个运行良好的查询:
SELECT u.*, lv.num as level, lvc.title as class, lvc.id as classid
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = ? AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1;
但是,如果我通过保留 WHERE u.id = 来扩大结果集?并删除 LIMIT 1,从而查询用户列表,我得到了所有三个表的组合。通常,您可以通过对键进行内部联接来降低行数,但由于这是范围检查, 那行不通。我尝试在内连接条件中使用范围检查,但这会带来相同的结果。即使使用分组我也无法得到我想要的结果。
在绝望的尝试中,我想出了这个查询,它有效:
SELECT usr.*,
(SELECT lv.num as level
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as level,
(SELECT lvc.title as class
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as class,
(SELECT lvc.image as class_image
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as class_image,
(SELECT lvc.id as classid
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as classid
FROM user as usr
ORDER BY usr.$sortby $direction LIMIT ?,?
但是,对我来说,这似乎效率很低。基本上,我在这里所做的就是为级别表和类表中需要的每个列(!)编写一个子查询。如果我在一个子查询中查询级别表或类表的多列,它会再次返回所有值的组合。我觉得我的数据库技能存在差距,我明显缺少一些东西,我不知道一个功能......
你能帮我吗?如何为一组行编写有效的查询,该查询组合了三个表中的列,但不通过键(而不是范围)链接?
PS:我知道,如果我将此架构非规范化以将级别和类组合到用户表中,我可以大大简化场景,但我需要这个有一个特定的原因,相信我.
最佳答案
如果您可以修改 Level 和 Class 表以在行中具有实际范围(即 Level 表中具有 minkarma 和 maxkarma,Class 表中具有 minlevel 和 maxlevel),那么您应该能够在您的连接条件,它只会为每个用户返回一行。
示例:
SELECT * FROM user as u
INNER JOIN level as lv on
u.karma >= lv.min_karma AND u.karma <= lv.max_karma
INNER JOIN levelclass as lvc on
lv.num >= lvc.min_level AND lv.num <= lvc.max_level
您获得多行的原因是您通过仅过滤最小值来匹配多个级别和类别。
关于MySQL:如何为基于范围而不是外键的关系编写查询?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/912078/