在表格中,查看ID是否在一天中每分钟都有一行 - 如何优化此查询?

时间:2014-11-07 10:48:15

标签: php mysql sql

我正在尝试为每个ID显示一个表,以查看是否有可用的数据。在这种情况下,它是测量。如果没有数据,则表示记录器未正常工作。

我目前有两个表:datatimesdata包含iddatetimesensor_idvaluetimes包含idvalue

时间充满00:0000:0100:02等,一直到23:59

我有这个问题:

SELECT t.`value`, d.`sensor_id`, COUNT(t.`value`) as `numrows`
FROM `data` d
RIGHT JOIN `times` t
ON d.`datetime` LIKE CONCAT('% ', t.`value`, '%')
WHERE d.`datetime` LIKE '%$2014-11-05%'
AND d.`sensor_id` IN(1,2,3,4,5,999)
GROUP BY d.`sensor_id`, t.`value`
ORDER BY d.`sensor_id` ASC, t.`id` ASC

while($s = $select->fetch_assoc()) {
    $checkArray[$s['sensor_id']][$s['value']] = $s['numrows'];
}

foreach($checkArray as $key => $arr) {
    echo 'Sensor: ' . $key;
    for($i = 0; $i <= 23; $i++) {
        for($j = 0; $j <= 59; $j++) {
            $time = strlen($i) == 1 ? '0' . $i : '' . $i;
            $time .= ':';
            $time .= strlen($j) == 1 ? '0' .$j : '' . $j;

            if(isset($arr[$time]) && $arr[$time] >= 1) { //See if has at least one row
                echo 'YES DATA FOR ' . $time . '<br>';
            }
        }
    }
}

当然,我在表格中对此进行排序,结果如下:

Result

仅适用于传感器1,2,3,4和5,加载时间超过5.5秒。我不知道如何进一步优化这一点。我在查询的列上放了索引,但我想不出别的什么。

我的SHOW CREATE TABLE data

CREATE TABLE `data` (
 `id` int(13) NOT NULL AUTO_INCREMENT,
 `sensor_id` int(13) NOT NULL,
 `datetime` datetime NOT NULL,
 `value` float NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `sensor_id_2` (`sensor_id`,`datetime`,`value`),
 KEY `sensor_id` (`sensor_id`),
 KEY `value` (`value`),
 KEY `datetime` (`datetime`),
 KEY `sensor_id_3` (`sensor_id`),
 KEY `datetime_2` (`datetime`)
) ENGINE=InnoDB AUTO_INCREMENT=103921 DEFAULT CHARSET=utf8

2 个答案:

答案 0 :(得分:2)

您的查询会遇到困难,因为它尝试使用前导通配符对LIKE进行连接。没有索引对此有用。

进一步检查WHERE子句中datetime的值,再次使用前导通配符,这将阻止在该检查上使用索引。此外,您正在检查RIGHT JOINed表上的值,有效地将其呈现为INNER JOIN。

我会尝试这样的事情: -

SELECT sub0.aDateTime, sub1.sensor_id, COUNT(d.datetime)
FROM
(
    SELECT DATE_ADD('2014-11-05 00:00:00', INTERVAL a.Mnt + b.Mnt * 10 + c.Mnt * 100 + d.Mnt * 1000 MINUTE) AS aDateTime,
            DATE_ADD('2014-11-05 00:00:59', INTERVAL a.Mnt + b.Mnt * 10 + c.Mnt * 100 + d.Mnt * 1000 MINUTE) AS aDateTimeEnd
    FROM (SELECT 0 AS Mnt UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a
    CROSS JOIN (SELECT 0 AS Mnt UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b
    CROSS JOIN (SELECT 0 AS Mnt UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c
    CROSS JOIN (SELECT 0 AS Mnt UNION SELECT 1) d
    WHERE (a.Mnt + b.Mnt * 10 + c.Mnt * 100 + d.Mnt * 1000) < 1440
) sub0
CROSS JOIN
(SELECT 1 AS sensor_id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 999) sub1
LEFT OUTER JOIN data d
ON d.datetime BETWEEN sub0.aDateTime AND sub0.aDateTimeEnd
AND d.sensor_id = sub1.sensor_id
GROUP BY sub1.sensor_id, sub0.aDateTime
ORDER BY sub1.sensor_id ASC, sub0.aDateTime ASC

这生成一系列从0到1439的数字(即一天中的分钟数),并将其添加到您感兴趣的那一天的开始。这样就可以为当天的每一分钟生成一行(返回的2个日期/时间值是分钟的第一个和最后一个秒)。然后LEFT OUTER将数据连接到基于date_time的数据。

如果您只有一个数字1到1439的表并且将数据连接到数据以保存子查询(这仍然需要一个函数来计算日期,但仍然节省了一点时间),这可以很容易地改进)。

答案 1 :(得分:1)

您可以解决以下问题:

SELECT t.value, d.sensor_id, COUNT(t.value) as numrows
FROM times t LEFT JOIN
     data d
     ON date_format(d.datetime, '%H:%i') = t.value
WHERE d.datetime >= date('2014-11-05') and
      d.datetime < date('2014-11-06') and
      d.sensor_id IN (1, 2, 3, 4, 5, 999)
GROUP BY d.sensor_id, t.value
ORDER BY d.sensor_id ASC, t.value ASC;

请特别注意,此查询在日期/时间列上不使用like。这迫使他们转换为字符串并排除使用索引。