MySQL根据多个条件选择用户

时间:2012-04-06 18:42:24

标签: mysql

我的团队在一个学校项目的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循环中,我可以添加&#34;和(...)&#34;。我意识到我可以放弃&#39; 1&#39;并使用implode(和数组)并添加where数组不为空,但我认为这是等效的。如果没有,我可以很容易地改变它。

如您所见,我为搜索者要求的每个条件添加了JOIN。这也允许我通过a1.value ASC或a2.value等订购。

第一个问题: 这个表组织至少有点体面吗?我们认为,由于问题的数量是可变的,并非每个用户都回答每个问题,因此这样的事情是必要的。

主要问题: 查询方式效率太低吗?我想,加入同一张桌子并不是很理想,可能要十几次或两次(如果我们最后把这么多问题放进去)。我做了一些搜索,发现这两个帖子似乎有点触及我正在寻找的东西:

Mutiple criteria in 1 query

这在EXISTS中使用了多个嵌套(正确的术语?)查询

Search for products with multiple criteria

youssef azari的一条评论提及使用&#39;查询1&#39; UNION&#39;查询2&#39;

对于我想要做的事情,这些中的任何一个会更好/更有意义吗?

奖金问题:

为了简单起见,我遗漏了上面的内容,但实际上我有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&#39;响应

以及我发现的那个:

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输出中的每一行都是相关答案表上的依赖查询,使用WHERE和key = PRIMARY KEY的类型为eq_ref(good),仅搜索1行。整体还不错。

对于我建议的查询(JOINing):

主查询实际上是你首先加入的任何表(在我的例子中是AnswerBoolean),其类型为ref(优于ALL)。搜索的行数等于任何人回答的问题数(因为50个不同的问题已被任何人回答)(这将远远小于用户数)。对于EXPLAIN输出中的每个附加行,它是使用WHERE和key = PRIMARY KEY并且仅搜索1行的类型为eq_ref(good)的SIMPLE查询。整体几乎相同,但起始倍数较小。

JOIN方法的最后一个优点:它是唯一一个我能弄清楚如何通过各种值(例如n1.value)进行排序的方法。由于其他两个查询使用子查询,我无法访问特定子查询的值。添加order by子句确实会改变第一个查询中的额外字段,以便使用临时&#39; (需要,我相信,对于'和#)的命令以及&#39;使用filesort&#39; (不知道如何避免这种情况)。然而,即使有那些减速,行数仍然少得多,而另外两行(据我所知)不能使用order by。

1 个答案:

答案 0 :(得分:0)

您可以使用适当大的测试数据集并使用EXPLAIN和/或the profiler来自己回答大部分问题。

您的INNER JOIN几乎肯定会比切换到EXISTS更好,但同样可以使用合适的测试数据集和EXPLAIN进行测试。