【问题标题】:MySQL intersectionMySQL 交集
【发布时间】:2013-09-17 06:24:03
【问题描述】:

我有一个现有站点,其数据库设计不正确且包含大量记录,因此我们无法更改数据库结构。

当前问题的数据库主要包含4个表,用户,问题,选项和答案。有一组标准的问题和选项,但对于每个用户,每组问题和选项的答案表中都有一行。数据库结构和示例数据可在SQL fiddle 获得。

现在作为高级搜索的新要求,我需要通过应用多个搜索过滤器来查找用户。示例输入和预期输出在 SQL Fiddle 上的 cmets 中给出。

我尝试应用所有类型的连接、交集,但它总是以某种方式失败。有人可以帮我编写正确的查询,最好是轻量级/优化连接,因为数据库包含大量记录(10000 多个用户、100 多个问题、500 多个选项和 500000 多个答案表中的记录)?

编辑:基于两个答案,我使用了以下查询

SELECT u.id, u.first_name, u.last_name
FROM users u
    JOIN answers a ON a.user_id = u.id
WHERE (a.question_id = 1 AND a.option_id IN (3, 5))
    OR (a.question_id = 2 AND a.option_id IN (8))
GROUP BY u.id, u.first_name, u.last_name
HAVING
    SUM(CASE WHEN (a.question_id = 1 AND a.option_id IN (3, 5)) THEN 1 ELSE 0 END) >=1
    AND SUM(CASE WHEN (a.question_id = 2 AND a.option_id IN (8)) THEN 1 ELSE 0 END) >= 1;

请注意:在真实数据库中,answers 表的 user_idquestion_idoption_id 列已编入索引。

SQL Fiddle 上给出的运行查询。

SQL Fiddle dnoeth 的回答。

SQL Foddle 为 calcinai 的回答。

【问题讨论】:

    标签: mysql sql select intersection


    【解决方案1】:

    使用 OR 将所有 n 个过滤器添加到 WHERE 中,并使用 AND 在 HAVING(SUM(CASE)) 中重复它们:

    SELECT u.id, u.first_name, u.last_name
    FROM users u JOIN answers a
      ON a.user_id = u.id
    JOIN questions q
      ON a.question_id = q.id
    JOIN question_options o
      ON a.option_id = o.id
    WHERE (q.question = 'Language known' AND o.OPTION IN ('French','Russian'))
       OR (q.question = 'height' AND o.OPTION = '1.51 - 1.7')
    GROUP BY u.id, u.first_name, u.last_name
    HAVING
      SUM(CASE WHEN (q.question = 'Language known' AND o.OPTION IN ('French','Russian')) THEN 1 ELSE 0 END) >=1
    AND 
      SUM(CASE WHEN (q.question = 'height'         AND o.OPTION = '1.51 - 1.7')          THEN 1 ELSE 0 END) >= 1
    ;
    

    我将您的联接更改为更易读的标准 SQL 语法。

    【讨论】:

    • 感谢@dnoeth,+1 工作查询。我必须承认,到目前为止,我不明白您在HAVING 之后写的内容。我必须浏览一下文档。我将应用您和@calcinai andwer 并接受哪个更优化。再次非常感谢您的回答,更重要的是,对我来说是新事物(SUM/CASE WHEN 等)
    • @Kapil Sharma:当然我的版本更高效 :-) 添加附加条件很容易,向 WHERE/HAVING 添加新条件而不是另一个联接。关于逻辑:检查每个条件是否至少一次评估为 TRUE
    • 我也觉得实时可能有 30-50 个过滤器和 30-50 个连接显然会杀死数据库服务器,特别是当查询发生得太频繁时。我们仍然需要在具有 500000 多条记录的真实数据库上实施一些基准测试。请给我一些时间,可能需要几个小时。我会接受这两个答案,但不允许这样做。
    • 我很想知道您的基准测试结果。我同意@dnoeth 在给定数据集上的查询可能会更快,但我认为当有大量数据时它会崩溃。拥有INNER JOINS 会产生开销,但我认为这与创建一个巨大的派生表然后在本质上不可索引的列上执行HAVING 相比意义不大。
    • 如果表被正确索引并且每个单独的选择将使用基于索引的访问,或者如果行数快速减少(高选择性)它可能会更快。
    【解决方案2】:

    这需要对动态过滤器进行一些修改,但您真正想要做的是按 ID 搜索,因为这意味着更少的连接和更快的查询。

    这会产生您期望的结果。我假设搜索过滤器是根据数据库中的选项生成的,因此不要将实际值传递回查询,而是传递 ID。

    多个内连接是为了支持多个 AND 条件并自动减少您的结果集。

    SELECT * FROM users u
    INNER JOIN answers a ON a.user_id = u.id
      AND (a.question_id, a.option_id) IN ((1,3),(1,5)) # q 1: Lang, answer 3/5: En/Ru
    INNER JOIN answers a2 ON a2.user_id = u.id
      AND (a2.question_id, a2.option_id) = (2,8) # q 2: Height, answer 8: 1.71...
    GROUP BY u.id;
    

    我建议确保 (user_id, question_id, option_id) 上有一个用于搜索的索引:

    ALTER TABLE `answers` ADD INDEX idx_search(`user_id`, `question_id`, `option_id`);
    

    否则它应该使用连接的主键(如果定义正确),这样它会很快。

    【讨论】:

    • 感谢@calcinai +1 的回答,是的,所有 FK 列都已编入索引,我传递的是 id,而不是值。在小提琴上,我试着把一个复杂的问题简单化。一个问题,Group by 中的“i”是什么意思?
    • 那是一个错字。我愚蠢的电脑自动将“u”更正为“i”!
    • GT8。非常感谢。它正在处理简单的小提琴示例。让我尝试一下原始数据库。如果有任何问题我会回来的。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 2020-01-24
    • 1970-01-01
    • 2012-04-15
    • 1970-01-01
    • 2019-07-10
    • 1970-01-01
    • 1970-01-01
    • 2014-12-19
    相关资源
    最近更新 更多