mysql加入子查询

Posted

技术标签:

【中文标题】mysql加入子查询【英文标题】:mysql join subqueries 【发布时间】:2011-07-20 22:59:26 【问题描述】:

我有以下表格:

CREATE TABLE `data` (
  `date_time` decimal(26,6) NOT NULL,
  `channel_id` mediumint(8) unsigned NOT NULL,
  `value` varchar(40) DEFAULT NULL,
  `status` tinyint(3) unsigned DEFAULT NULL,
  `connected` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`channel_id`,`date_time`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

CREATE TABLE `channels` (
  `channel_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `channel_name` varchar(40) NOT NULL,
  PRIMARY KEY (`channel_id`),
  UNIQUE KEY `channel_name` (`channel_name`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

我想知道是否有人可以就如何优化或重写以下查询给我一些建议:

SELECT channel_name, t0.date_time, t0.value, t0.status, t0.connected, t1.date_time, t1.value, t1.status, t1.connected FROM channels,
    (SELECT MAX(date_time) AS date_time, channel_id, value, status, connected FROM data
        WHERE date_time <= 1300818330
        GROUP BY channel_id) AS t0
    RIGHT JOIN
    (SELECT MAX(date_time) AS date_time, channel_id, value, status, connected FROM data
        WHERE date_time <= 1300818334
        GROUP BY channel_id) AS t1
ON t0.channel_id = t1.channel_id
WHERE channels.channel_id = t1.channel_id

基本上,我在两个不同时间获取每个 channel_name 的值、状态和连接字段。由于 t0 始终

问题好像是因为我加入了子查询,所以没有索引可以用?我尝试重写它以首先对数据表的 channel_id 进行自连接,但那是数百万行。

如果能够在 t0.value = t1.value & t0.status = t1.status & t0.connected = t1.connected 时为每个最终行添加一个布尔字段,这将是一件好事。

非常感谢您的宝贵时间。

【问题讨论】:

为什么要使用特定的日期时间过滤器?您还会获得不一定与 MAX(datetime) 相关的随机数据,因为 group by 不会按您期望的方式工作。 您可能拥有多少个渠道与多少数据记录? 我输入的具体date_times是随机的,会是php代码中的变量。通道的数量将远小于数据记录的数量。可能有大约 100,000 个或更多通道,但可能有数百万条数据记录。 group by 是否不返回对应于该最大 date_time 的行? 【参考方案1】:

您可以将两个子查询减少为一个

SELECT channel_id,
   MAX(date_time) AS t1_date_time,
   MAX(case when date_time <= $p1 then date_time end) AS t0_date_time
FROM data
WHERE date_time <= $p2
GROUP BY channel_id

GROUP BY 在 mysql 中是出了名的误导。想象一下,如果您在同一个选择中有 MIN() 和 MAX(),那么未分组的列应该来自哪一行?一旦你理解了这一点,你就会明白为什么它不是确定性的。

获取完整的 t0 和 t1 行

SELECT x.channel_id,
       t0.date_time, t0.value, t0.status, t0.connected,
       t1.date_time, t1.value, t1.status, t1.connected
FROM (
    SELECT channel_id,
       MAX(date_time) AS t1_date_time,
       MAX(case when date_time <= $p1 then date_time end) AS t0_date_time
    FROM data
    WHERE date_time <= $p2
    GROUP BY channel_id
) x
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time

最后是加入以获得频道名称

SELECT c.channel_name,
       t0.date_time, t0.value, t0.status, t0.connected,
       t1.date_time, t1.value, t1.status, t1.connected,
       t0.value=t1.value AND t1.status=t0.status
                         AND t0.connected=t1.connected name_me
FROM (
    SELECT channel_id,
       MAX(date_time) AS t1_date_time,
       MAX(case when date_time <= $p1 then date_time end) AS t0_date_time
    FROM data
    WHERE date_time <= $p2
    GROUP BY channel_id
) x
INNER JOIN channels c on c.channel_id = x.channel_id
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time


编辑

要对频道名称执行 RLIKE,在c.channel_name 的查询末尾添加 WHERE 子句看起来很简单。然而,在子查询中过滤它可能会更好地执行,利用 MySQL 从左到右处理逗号连接的特性。

SELECT x.channel_name,
       t0.date_time, t0.value, t0.status, t0.connected,
       t1.date_time, t1.value, t1.status, t1.connected,
       t0.value=t1.value AND t1.status=t0.status
                         AND t0.connected=t1.connected name_me
(
    SELECT c.channel_id, c.channel_name,
       MAX(d.date_time) AS t1_date_time,
       MAX(case when d.date_time <= $p1 then d.date_time end) AS t0_date_time
    FROM channels c, data d
    WHERE c.channel_name RLIKE $expr
      AND c.channel_id = d.channel_id
      AND d.date_time <= $p2
    GROUP BY c.channel_id
) x
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time

【讨论】:

谢谢!当 date_time > $p1 时,MAX(date_time CASE 的默认值为ELSE NULL。因此,对于 date_time &gt; $p1 的所有情况,它们都被转换为 NULL,因此不考虑该范围内的 MAX。使用带大小写的 MAX 允许查询一次完成,而不是两次。 非常好!有没有一种简单的方法可以向每一行添加一个布尔字段,只有当 t0.value = t1.value、t0.status = t1.status 和 t0.connected = t1.connected 时才为真? 再次感谢您!我唯一需要改变的是第一个内部连接上的频道到频道:) 如果我想通过 channel_name 上的 RLIKE 来缩小频道数量,最好放在哪里?是否可以在 group by 中这样做,以便比较尽可能少的通道?

以上是关于mysql加入子查询的主要内容,如果未能解决你的问题,请参考以下文章

Laravel中的MySQL子查询,如何加入查询并将第二个查询的结果作为新列添加到第一个查询?

将子查询(不在)重写为加入

优化类似的 MySQL 子查询

MySQL 中的 While 循环使用连接和子查询选择数据

试图从 2 个正在加入的子查询中获取一列并且分组不起作用

CakePHP 2.1 中的 Facebook 点赞对话列表(与分组最大值、子查询、加入相关)