我的团队在一个学校项目的 php/MySQL 网站上工作。我有一个包含典型信息(ID、名字、姓氏等)的用户表。我还有一个问题表,其中包含如下示例数据。对于这个简化的例子,所有问题的答案都是数字的。
表问题:
qid | questionText
1 | 'favorite number'
2 | 'gpa'
3 | 'number of years doing ...'
等等
用户将能够填写表格来回答任何或所有这些问题。注意:用户不需要回答所有问题,问题本身可能会在未来发生变化。
答案表如下所示:
表答案:
uid | qid | value
37 | 1 | 42
37 | 2 | 3.5
38 | 2 | 3.6
等等
现在,我正在处理该网站的搜索页面。我希望用户选择他们想要搜索的标准。我有一些工作,但我不确定它是否有效,或者它是否会扩展(并不是说这些表会很大——就像我说的,这是一个学校项目)。例如,我可能想列出所有喜欢的数字在 100 到 200 之间且 GPA 高于 2.0 的用户。目前,我有一个可以工作的查询生成器(据我所知,它创建了一个返回准确结果的有效查询)。此示例的查询生成器的结果如下所示:
SELECT u.ID, u.name (etc)
FROM User u
JOIN Answer a1 ON u.ID=a1.uid
JOIN Answer a2 ON u.ID=a2.uid
WHERE 1
AND (a1.qid=1 AND a1.value>100 AND a1.value<200)
AND (a2.qid=2 AND a2.value>2.0)
我添加 WHERE 1 以便在 for 循环中添加“AND (...)”。我意识到我可以删除 '1' 并使用 implode(and,array) 并添加 where if 数组不为空,但我认为这是等价的。如果没有,我可以很容易地改变它。
如您所见,我为搜索者要求的每个条件添加了一个 JOIN。这也允许我按 a1.value ASC 或 a2.value 等进行排序。
第一个问题:这个餐桌组织至少有点像样吗?我们认为,由于问题的数量是可变的,而且并不是每个用户都回答了每个问题,所以这样的事情是必要的。
主要问题:查询方式是否效率太低?我想将同一张桌子加入到自己身上多达十几次或两次(如果我们最终提出这么多问题的话)并不理想。我做了一些搜索,发现这两个帖子似乎有点触及我正在寻找的东西:
这在 EXISTS 中使用了多个嵌套(正确的术语?)查询
youssef azari 的评论之一提到使用'query 1' UNION 'query 2'
对于我正在尝试做的事情,这些中的任何一个会表现更好/更有意义吗?
奖金问题:
为简单起见,我在上面省略了,但实际上我有 3 个表(用于数值问题、布尔值和文本)决定有单独的表是因为(据我所知)它要么就是那个,要么有一个具有 3 个不同类型的值列的大答案表,其中 2 个始终为空。
这适用于我当前的查询生成器 - 示例查询是
SELECT u.ID,...
FROM User u
JOIN AnswerBool b1 ON u.ID=b1.uid
JOIN AnswerNum n1 ON u.ID=n1.uid
JOIN AnswerText t1 ON u.ID=t1.uid
WHERE 1
AND (b1.qid=1 AND b1.value=true)
AND (n1.qid=16 AND n1.value<999)
AND (t1.qid=23 AND t1.value LIKE '...')
考虑到这一点,获得结果的最佳方法是什么?
最后一个背景:我提到这是一个学校项目。虽然这是真的,但最终目标(这是一个本科生高级设计项目)是让一个部门使用我们的网站,让学生为他们的高级设计创建团队。对于规模的粗略估计,每个学期,该部门将有大约 200 名左右的学生使用我们的网站来组建团队。显然,当我们完成后,该部门将(希望)检查我们的网站是否存在安全问题和他们需要担心的其他问题(FERPA 和所有问题)。我们正在尝试考虑所有常见的安全实践和可扩展性问题,但最终,我们的代码可能会被其他人改进。
更新 根据 nnichols 的建议,我输入了相当数量的数据并对不同的查询进行了一些测试。我在表中放置了大约 250 个用户,并且在 3 个表中的每一个中放置了大约 2000 个答案。我发现提供的链接非常有用
(链接被删除,因为我不能超链接超过两次)链接在 nnichols 的回应中
以及我发现的这个:
http://phpmaster.com/using-explain-to-write-better-mysql-queries/
我尝试了 3 种不同类型的查询,最后,我提出的查询效果最好。
第一:使用 EXISTS
SELECT u.ID,...
FROM User u WHERE 1
AND EXISTS
(SELECT * FROM AnswerNumber
WHERE uid=u.ID AND qid=# AND value>#) -- or any condition on value
AND EXISTS
(SELECT * FROM AnswerNumber
WHERE uid=u.ID AND qid=another # AND some_condition(value))
AND EXISTS
(SELECT * FROM AnswerText
...
我在 3 个答案表中的每一个上使用了 10 个条件(导致 30 个 EXISTS)
第二:使用 IN - 一种非常相似的方法(甚至可能完全一样?)产生相同的结果
SELECT u.ID,...
FROM User u WHERE 1
AND (u.ID) IN (SELECT uid FROM AnswerNumber WHERE qid=# AND ...)
...
再次使用 30 个子查询。
我尝试的第三个与上述相同(使用 30 个 JOIN)
对前两个使用 EXPLAIN 的结果如下:(相同)
表 u 上的主要查询类型为 ALL(不好,尽管用户表并不大),搜索的行大约是用户表大小的两倍(不知道为什么)。EXPLAIN 输出中的每一行都是对相关答案表的依赖查询,类型为 eq_ref (good),使用 WHERE 和 key=PRIMARY KEY 并且只搜索 1 行。总体还不错。
对于我建议的查询(JOINing):
主要查询实际上是在您首先加入的任何表上(在我的情况下是 AnswerBoolean),其类型为 ref(优于 ALL)。搜索的行数等于任何人回答的问题数(因为任何人回答了 50 个不同的问题)(这将远少于用户数)。对于 EXPLAIN 输出中的每个附加行,它是一个简单的查询,类型为 eq_ref (good),使用 WHERE 和 key=PRIMARY KEY 并且只搜索 1 行。总体几乎相同,但起始乘数较小。
JOIN 方法的最后一个优点:它是我唯一能弄清楚如何按各种值(例如 n1.value)排序的方法。由于其他两个查询使用子查询,我无法访问特定子查询的值。添加 order by 子句确实将第一个查询中的额外字段更改为也具有“使用临时”(我相信,对于 order by 是必需的)和“使用文件排序”(不知道如何避免这种情况)。然而,即使有这些减速,行数仍然少得多,另外两个(据我所知)不能使用 order by。