MySQL присоединиться к подзапросам
У меня есть следующие таблицы:
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
В основном я получаю значения, статус и подключенные поля для каждого имени канала в два разных времени. Поскольку t0 всегда <= t1, поля могут существовать для t1, но не для t0, и я хочу, чтобы это было показано. Вот почему я использую ПРАВИЛЬНОЕ СОЕДИНЕНИЕ. Если он не существует для t1, то он не будет существовать для t0, поэтому строка не должна возвращаться.
Кажется, проблема в том, что, поскольку я присоединяюсь к подзапросам, индекс не может быть использован? Я попытался переписать его, чтобы сначала выполнить самостоятельное соединение для channel_id таблицы данных, но это миллионы строк.
Также было бы хорошо иметь возможность добавить логическое поле в каждую из последних строк, что верно, когда t0.value = t1.value & t0.status = t1.status & t0.connected = t1.connected.
Большое спасибо за уделенное время.
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() в одном и том же select, из какой строки должны исходить не сгруппированные столбцы? Как только вы поймете это, вы поймете, почему это не является детерминированным.
Чтобы получить полные строки 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 для имени канала, достаточно просто добавить предложение WHERE в конце запроса на c.channel_name
, Однако может быть лучше выполнить фильтрацию в подзапросе, используя функцию 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