【问题标题】:SQL return results for Table A, based on criteria from Table B表 A 的 SQL 返回结果,基于表 B 中的条件
【发布时间】:2022-01-10 23:07:58
【问题描述】:

我有 2 个共享一对多关系的表。假设如下结构:

users             users_metadata
-------------     -------------
id | email        id | user_id | type | score

一个用户可以有很多元数据。 users 表有 100k 行,users_metadata 表有 300k 行。它可能会增长 10 倍,因此无论我写什么都需要对大量数据进行优化。

我需要编写一个 sql 语句,它只返回通过元数据表中的几个不同分数条件的用户电子邮件。

// if type = 1 and if score > 75 then <1 point> else <0 points>
// if type = 2 and if score > 100 then <1 point> else <0 points>
// if type = 3 and if score > 0 then [-10 points] else <0 points>

// there are other types that we want to ignore in the score calculations

如果用户通过阈值(例如 >= 1 分),那么我希望该用户出现在结果集中,否则我希望忽略该用户。

我已经尝试使用一个存储函数/游标来获取 user_id 并遍历元数据以找出点,但结果执行非常慢(尽管它确实有效)。

目前我有这个,执行大约需要 1 到 3 秒。

SELECT u.id, u.email,

    (
        SELECT 
            SUM(
                IF(k.type = 1, IF(k.score > 75, 1, 0), 0) + 
                IF(k.type = 2, IF(k.score > 100, 1, 0), 0) +
                IF(k.type = 3, IF(k.score > 0, 1, -10), 0)
            ) 
        FROM user_metadata k WHERE k.user_id = u.id
        
    ) AS total

FROM users u GROUP BY u.id HAVING total IS NOT NULL;

我觉得在 10 倍时这会更慢。 1 到 3 秒的查询执行时间对于我已经需要的东西来说太慢了。

更理想的方法是什么?

如果我也为此使用 PHP 之类的语言,将运行 2 个查询,一个从 user_metadata 的仅 passing 用户中获取 user_ids,然后第二个在该 ID 列表上选择 WHERE IN更好?

【问题讨论】:

  • 这是一个报告类型的查询,需要扫描数百万行。你不能指望这个查询运行得非常快。
  • 这很公平。如果 1 到 3 秒的运行速度可以接受,那么我可以接受。我将在下面使用@Barmar 的解决方案,以便更轻松地运行条件检查。我可以将结果限制为 25 并且仅显示每页,因此对于加载此信息的管理页面,查询应该是可以接受的(
  • 实际上,有限选择的连接方法比我上面的嵌套选择查询慢得多。 JOIN 对 25 个结果运行查询需要 500 毫秒。嵌套选择需要 32 毫秒
  • 请为每张桌子提供SHOW CREATE TABLE,以及每张桌子的大致大小。由于ORDER BYLIMIT 可以在优化方面产生很大 的差异,请提供完整的查询!你的时间证明了这一点!
  • type的分布是什么?值是否只有 1、2、3?如果不是,有多少百分比不是 1 或 2 或 3?

标签: php mysql sql query-optimization


【解决方案1】:

尝试使用 JOIN 而不是相关子查询。

SELECT u.id, u.email, t.total
FROM users AS u
JOIN (
    SELECT user_id, SUM(CASE type
        WHEN 1 THEN score > 75
        WHEN 2 THEN score > 100
        WHEN 3 THEN IF(k.score > 0, 1, -10)
        END) AS total
    FROM user_metadata
    GROUP BY user_id
    HAVING total >= 1
) AS t ON u.id = t.user_id

在子查询中进行分组和过滤会使连接变小,从而显着提升性能。

您也无需在查询中使用GROUP BY u.id,因为这是您要查询的表的主键;希望 MySQL 会优化它。

【讨论】:

  • 谢谢,有了这个查询,第一行在 500 毫秒后可用,运行它仍然需要 1 多秒。我写的第一行查询在 90 毫秒时可用,但总执行时间似乎差不多。
  • 嗯,那不是还需要在派生表中使用HAVING sum(...) &gt;= 1 来过滤不合格的用户吗? (或外部查询中的WHERE t.total &gt;= 1。但似乎最好将派生表中的集合保持尽可能小。)
  • 子查询中不会有任何零和,因此这些行不会加入。
  • @Barmar:那么内部查询是错误的。他们想要计算计算分数的总和,因为可能有 0 甚至 -10 的分数,所以分数的总和很可能是 0 甚至是负数......你只是在一个条件下计算出现次数正如我现在看到的那样(我忽略了这一点。)。您不会将与条件相关的分数相加。
  • @Barmar:更正:我应该更好地写“他们想要计算计算点的总和 [根据分数中的条件]”至少这是他们的命名,并保持计算基于语音的分数与计算产生的分数(实际上也是某种分数)分开。
猜你喜欢
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 2013-04-03
  • 2019-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多