【问题标题】:Rebuild MySQL query to stay below MAX_JOIN_SIZE rows重建 MySQL 查询以保持在 MAX_JOIN_SIZE 行以下
【发布时间】:2014-07-25 10:25:21
【问题描述】:

我有一个 SQL 查询由于连接的行太多而失败(大多数情况下)。 MySQL 提供的错误是The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay。我知道我可以通过设置上述变量 SQL_BIG_SELECTS 和 MAX_JOIN_SIZE 来避免错误,但我觉得这不是正确的方法,并且只会在将来稍微推动问题,因为将来连接数可能会增加。

事实:我有一个活动计划工具,可以将用户(=员工)分配给某些任务。这些表是users (userid,username) [ID and name], tasks (taskid,task,start,end) [ID, task name, start as timestamp, end as timestamp] 和 userassignment (id, userid,taskid,deleted) [ID,用户分配给任务,任务,分配是否仍然有效)。

确切的表定义是这样的:

CREATE TABLE users (
 userid INT NOT NULL AUTO_INCREMENT,
 username VARCHAR(250),
 PRIMARY KEY (userid)
);

CREATE TABLE tasks (
 taskid INT NOT NULL AUTO_INCREMENT,
 task VARCHAR(250),
 start INT,
 end INT,
 PRIMARY KEY (taskid),
 INDEX USING BTREE (start),
 INDEX USING BTREE (end)
);

CREATE TABLE userassignment (
 id INT NOT NULL AUTO_INCREMENT,
 userid INT,
 taskid INT,
 deleted TINYINT,
 PRIMARY KEY (id),
 INDEX USING BTREE (userid),
 INDEX USING BTREE (userid),
 UNIQUE KEY `usertasks` (  `userid` ,  `taskid` )
);

我需要知道,分配了哪些用户,以及在活动的哪几天(第 1 天、第 2 天、第 3 天)分配了他们。

我的查询如下所示:

SELECT
    u.userid,
    u.username,
    COUNT(ua.id) AS count_all,
    dayone.c AS count_one,
    daytwo.c AS count_two,
    daythree.c AS count_three
FROM
    users AS u
INNER JOIN
    userassignment AS ua ON ua.userid = u.userid AND ua.deleted = 0
INNER JOIN
    tasks AS t ON ua.taskid = t.taskid

    LEFT JOIN (
        SELECT
            u.userid,
            COUNT(ua.id) AS c
        FROM
            users AS u
        INNER JOIN
            userassignment AS ua ON
            ua.userid = u.userid AND
            ua.deleted = 0
        INNER JOIN
            tasks AS t ON
            ua.taskid = t.taskid
        WHERE
            t.start > UNIX_TIMESTAMP("2014-08-01 00:00:00") AND
            t.start < UNIX_TIMESTAMP("2014-08-02 00:00:00")
        GROUP BY
            u.userid
    ) AS dayone ON dayone.userid = u.userid

    LEFT JOIN (
        SELECT
            u.userid,
            COUNT(ua.id) AS c
        FROM
            users AS u
        INNER JOIN
            userassignment AS ua ON
            ua.userid = u.userid AND
            ua.deleted = 0
        INNER JOIN
            tasks AS t ON
            ua.taskid = t.taskid
        WHERE
            t.start > UNIX_TIMESTAMP("2014-07-31 00:00:00") AND
            t.start < UNIX_TIMESTAMP("2014-08-01 00:00:00")
        GROUP BY
            u.userid
    ) AS daytwo ON daytwo.userid = u.userid

    LEFT JOIN (
        SELECT
            u.userid,
            COUNT(ua.id) AS c
        FROM
            users AS u
        INNER JOIN
            userassignment AS ua ON
            ua.userid = u.userid AND
            ua.deleted = 0
        INNER JOIN
            tasks AS t ON
            ua.taskid = t.taskid
        WHERE
            t.start > UNIX_TIMESTAMP("2014-08-02 00:00:00") AND
            t.start < UNIX_TIMESTAMP("2014-08-04 00:00:00")
        GROUP BY
            u.userid
    ) AS daythree ON daythree.userid = u.userid

WHERE
    t.start > UNIX_TIMESTAMP("2014-07-31 00:00:00") AND
    t.start < UNIX_TIMESTAMP("2014-08-04 00:00:00")
GROUP BY
    u.userid
ORDER BY
    username ASC

首先,我选择在三天中的某一天有分配的所有用户(数据库中的用户比分配给任务的用户多约六倍),然后我离开加入三天中每一天的分配用户。

那么,有没有办法重建查询以连接更少的行?我只需要知道,在哪一天分配给谁,而不是分配的次数。

我已经尝试了 UNION 几个查询,但是没有成功。

SQL Fiddle

真实查询的解释(不在 SQL Fiddle 中)是:

id  select_type table   type    possible_keys   key key_len ref rows    filtered    Extra
1   PRIMARY t   range   PRIMARY,start   start   5   NULL    120 100.00  Using where; Using index; Using temporary; Using filesort
1   PRIMARY ua  ref usertasks,userid,taskid taskid  2   db1154575-helfer.t.id   2   100.00  Using where
1   PRIMARY u   eq_ref  userid  userid  2   db1154575-helfer.ua.userid  1   100.00   
1   PRIMARY <derived2>  ALL NULL    NULL    NULL    NULL    152 100.00   
1   PRIMARY <derived3>  ALL NULL    NULL    NULL    NULL    94  100.00   
1   PRIMARY <derived4>  ALL NULL    NULL    NULL    NULL    147 100.00   
4   DERIVED t   range   PRIMARY,start   start   5   NULL    53  100.00  Using where; Using index; Using temporary; Using filesort
4   DERIVED ua  ref usertasks,userid,taskid taskid  2   db1154575-helfer.t.id   2   100.00  Using where
4   DERIVED u   eq_ref  userid  userid  2   db1154575-helfer.ua.userid  1   100.00  Using index
3   DERIVED t   range   PRIMARY,start   start   5   NULL    21  100.00  Using where; Using index; Using temporary; Using filesort
3   DERIVED ua  ref usertasks,userid,taskid taskid  2   db1154575-helfer.t.id   2   100.00  Using where
3   DERIVED u   eq_ref  userid  userid  2   db1154575-helfer.ua.userid  1   100.00  Using index
2   DERIVED t   range   PRIMARY,start   start   5   NULL    44  100.00  Using where; Using index; Using temporary; Using filesort
2   DERIVED ua  ref usertasks,userid,taskid taskid  2   db1154575-helfer.t.id   2   100.00  Using where
2   DERIVED u   eq_ref  userid  userid  2   db1154575-helfer.ua.userid  1   100.00  Using index

【问题讨论】:

  • 分配表中的代理 PK (id) 似乎是多余的。
  • @Downvoter:我真的很想知道为什么这个问题在发布一年半后没有任何评论就被否决了。

标签: mysql sql query-optimization left-join


【解决方案1】:

那么,这真的只是一种啰嗦的说法吗……

SELECT u.*
     , DATE(FROM_UNIXTIME(t.start)) dt
     , COUNT(t.taskid) total
  FROM users u
  LEFT 
  JOIN userassignment ut
    ON ut.userid = u.userid
   AND ut.deleted = 0
  LEFT
  JOIN tasks t 
    ON t.taskid = ut.taskid
 GROUP
    BY u.userid
     , DATE(FROM_UNIXTIME(t.start))

在上面的示例中,您可以将 COUNT(t.taskid) 更改为 COUNT(CASE WHEN x = 'y' THEN z END) 或 SUM(CASE...

【讨论】:

  • 是的,我认为你是对的 :) 我需要重写它以获得我的查询布局。
  • 为什么?为什么不转头/旋转显示器?
  • SQL 结果用于 MySQL 之外的另一个函数。如果我改变结果的布局,我也必须改变这个函数。
  • 如果是我,我会改变功能 - 但如果你想要一个支点,请参阅我答案末尾的注释。
【解决方案2】:

这应该返回相同的结果集:

    SELECT u.userid, u.username,
           COUNT(ua.id) AS count_all,
           SUM(case when t.start > UNIX_TIMESTAMP('2014-08-01 00:00:00') AND
                         t.start < UNIX_TIMESTAMP('2014-08-02 00:00:00')
                    then 1 else 0
                end) as count_one,
           SUM(case when t.start > UNIX_TIMESTAMP('2014-07-31 00:00:00') AND
                         t.start < UNIX_TIMESTAMP('2014-08-01 00:00:00')
                    then 1 else 0
                end) as count_two,
           SUM(case when t.start > UNIX_TIMESTAMP('2014-08-02 00:00:00') AND
                         t.start < UNIX_TIMESTAMP('2014-08-04 00:00:00')
                    then 1 else 0
                end) as count_three
    FROM users u LEFT JOIN
         userassignment ua 
         ON ua.userid = u.userid AND
            ua.deleted = 0 LEFT JOIN
         tasks t
         ON ua.taskid = t.taskid
    WHERE ua.deleted = 0 AND
          t.start > UNIX_TIMESTAMP('2014-07-31 00:00:00') AND
          t.start < UNIX_TIMESTAMP('2014-08-04 00:00:00')
    GROUP BY u.userid
    ORDER BY u.username;

你的表述有点棘手。例如,外部联接会过滤掉任何分配总是被删除的用户。并且日期期间是重叠的(我不确定这是否是故意的,但这是查询的结构)。

也许这个更简单的查询不会超出内部限制。

【讨论】:

  • 看起来很不错!我稍后会测试它。 filter out any user whose assignments are always deleted --> 如果用户的分配总是 被删除,他不应该被列出,这很好。 the date periods are overlapping --> 我把第 1 天和第 2 天弄混了,但我不认为它们重叠。但它们不包括在午夜开始的任务(不会发生)。
猜你喜欢
  • 2012-02-29
  • 1970-01-01
  • 2010-10-30
  • 2011-07-03
  • 1970-01-01
  • 2019-10-25
  • 2011-04-30
  • 2010-11-01
  • 1970-01-01
相关资源
最近更新 更多