内联视图的ORDER BY子句中的列别名为s,这不是因为我们需要按设备id降序排列的行,而是为了避免文件排序操作,允许MySQL对具有前导列(设备id,如果需要)的索引执行“反向扫描”操作
注意:这个查询仍然会将中间结果集作为一个临时的MyISAM表进行假脱机,并且这些表上不会有任何索引。因此,这可能不会像原始查询那样执行内联视图的ORDER BY子句中的列别名为s,这不是因为我们需要按设备id降序排列的行,而是为了避免文件排序操作,允许MySQL对具有前导列(设备id,如果需要)的索引执行“反向扫描”操作,mysql,sql,join,self-join,Mysql,Sql,Join,Self Join,注意:这个查询仍然会将中间结果集作为一个临时的MyISAM表进行假脱机,并且这些表上不会有任何索引。因此,这可能不会像原始查询那样执行 另一种策略是在选择列表中使用相关子查询。您只从日志表返回一列,因此这是一个相当容易理解的查询: SELECT d.id , d.name , ( SELECT l.message FROM log l WHERE l.device_id = d.id ORDER BY l.whe
另一种策略是在选择列表中使用相关子查询。您只从日志表返回一列,因此这是一个相当容易理解的查询:
SELECT d.id
, d.name
, ( SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1
) AS message
FROM device d
WHERE d.active = 1
ORDER BY d.id ASC;
注意:由于id
是设备
表中的主键(或唯一键),并且由于您没有执行任何将生成额外行的联接,因此可以省略GROUP BY
子句
注意:此查询将使用“嵌套循环”操作。也就是说,对于从设备
表返回的每一行,(本质上)需要运行一个单独的查询来从日志中获取相关行。对于只有少数device
行(在device
表上使用更具选择性的谓词返回),并且每个设备都有大量日志条目,性能不会太差。但是,对于许多每个设备只有少量日志消息的设备,其他方法很可能会更有效。)
还请注意,使用这种方法,您可以轻松地扩展它,将第二个最新的日志消息作为单独的列返回,方法是向SELECT列表添加另一个子查询(与第一个子查询类似),只需更改LIMIT子句以跳过第一行,而改为获取第二行
, ( SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1,1
) AS message_2
为了从设备中获取基本上所有的行,使用联接操作可能会获得最佳性能。这种方法的一个缺点是,当有两(或更多)行具有与设备匹配的最新
when
值时,它有可能为设备返回多行。(基本上,当我们保证log(device\u id,when)
是唯一的时,这种方法保证返回“正确”的结果集
使用此查询作为内联视图,要获取“最新”的when值:
SELECT l.device_id
, MAX(l.when)
FROM log l
GROUP BY l.device_id
我们可以将其连接到日志表和设备表
SELECT d.id
, d.name
, m.messsage
FROM device d
LEFT
JOIN (
SELECT l.device_id
, MAX(l.when) AS `when`
FROM log l
GROUP BY l.device_id
) k
ON k.device_id = d.id
LEFT
JOIN log m
ON m.device_id = d.id
AND m.device_id = k.device_id
AND m.when = k.when
ORDER BY d.id
所有这些都是替代策略(我相信这是您提出的问题),但我不确定这两种策略中的任何一种是否能更好地满足您的特定需求。(但在工具带中使用两种不同的工具总是很好的,以便在适当的情况下使用。)您的查询和以下策略将受益于日志(设备id,when)上的索引
。该索引可以替换日志(设备id)上的索引
,因为该索引是冗余的
如果每个设备都有一大堆日志条目,那么查询中的连接将生成一个大小合适的中间结果集,每个设备将被过滤到一行。我不相信MySQL优化器有任何反连接操作的“快捷方式”(至少在5.1中没有)…但您的查询可能是最有效的 问:我能用不同的策略完成工作吗 是的,还有其他一些策略,但我不知道这些策略中有哪一个比你的查询“更好”
更新:
一种策略,你可以考虑向你的模式添加另一个表,一个保存每个设备的最新日志条目。这可以通过在“代码>日志表中定义的触发器来保持。如果只执行插入(不更新也不删除最新的日志条目,这相当简单。每当对
log
表执行插入操作时,将触发每行插入后的触发器,该触发器将设备id插入日志表时的值与当前插入时的值进行比较在log\u latest
表中插入/更新log\u latest
表中的行,以便最新的行始终存在。您还可以(冗余地)在表中存储设备名称。(或者,您可以在设备表中添加latest_when
和latest_message
列,并在其中进行维护。)
<>但是这个策略超出了你原来的问题……但是如果你需要经常运行“所有设备的最新日志信息”,那么这是一个可行的策略。查询。缺点是您有一个额外的表,并且在插入log
表时会影响性能。可以使用与原始查询类似的查询或下面的替代方法完全刷新此表
一种方法是查询,对设备
和日志
表进行简单联接,在
时按设备和降序获取行。然后使用内存变量处理行,过滤掉除“最新”日志项以外的所有行。请注意,此查询返回一个额外的列。(通过将整个查询包装为一个内联视图,可以删除这个额外的列,但如果您能够接受返回的额外列,则可能会获得更好的性能:
SELECT IF(s.id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.id AS id
, s.name
, s.message
FROM (SELECT d.id
, d.name
, l.message
FROM device d
LEFT
JOIN log l ON l.device_id = d.id
WHERE d.active = 1
ORDER BY d.id, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
SELECT列表中的第一个表达式所做的是,每当一行上的设备id值与前一行上的设备id不同时,就“标记”该行。HAVING子句过滤掉所有未标记为1的行。(可以省略HAVING子句以查看该表达式的工作原理。)
(我没有测试语法错误,如果你有错误,请告诉我,我会仔细检查。我的桌面检查说没问题……但可能我漏掉了一个括号或逗号,)
(您可以通过在另一个查询中包装该额外列来“除去”该额外列
SELECT r.id,r.name,r.message FROM (
/* query from above */
) r
(但同样,这可能会影响性能,如果您能够接受额外的列,您可能会获得更好的性能。)
当然,一个
, ( SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1,1
) AS message_2
SELECT l.device_id
, MAX(l.when)
FROM log l
GROUP BY l.device_id
SELECT d.id
, d.name
, m.messsage
FROM device d
LEFT
JOIN (
SELECT l.device_id
, MAX(l.when) AS `when`
FROM log l
GROUP BY l.device_id
) k
ON k.device_id = d.id
LEFT
JOIN log m
ON m.device_id = d.id
AND m.device_id = k.device_id
AND m.when = k.when
ORDER BY d.id
SELECT d.id, d.name, l.message
FROM device AS d
LEFT JOIN (
SELECT l1.device_id, l1.message
FROM log AS l1
WHERE l1.when = (
SELECT MAX(l2.when)
FROM log AS l2
WHERE l2.device_id = l1.device_id
) l ON l.device_id = d.id
WHERE d.active = 1
ORDER BY d.id ASC;
SELECT d.id, d.name,
SUBSTRING_INDEX(
GROUP_CONCAT(
l.message
SEPARATOR '~'
ORDER BY l.when DESC
)
, '~'
, 1
)
FROM device d
LEFT JOIN log l
ON d.id = l.device_id
WHERE d.active = 1
GROUP BY d.id
SELECT d.id
, d.name
, l.message
FROM (
SELECT d.id, d.name, MAX(l.when) lmax
FROM device d
LEFT JOIN log l
ON d.id = l.device_id
WHERE d.active = 1
GROUP BY d.id
) d
LEFT JOIN log l
ON d.id = l.device_id
AND d.lmax = l.when
ORDER BY d.id ASC;
SELECT d.id
, d.name
, l2.message
FROM device d
LEFT JOIN (
SELECT l.device_id
, MAX(l.when) lmax
FROM log l
GROUP BY l.device_id
) l1
ON d.id = l1.device_id
LEFT JOIN log l2
ON l1.device_id = l2.device_id
AND l1.lmax = l2.when
WHERE d.active = 1
ORDER BY d.id ASC;