我继承了这个MySQL查询作为一些遗留代码:
SELECT
HardwareAddress, CONV(SUBSTRING(EventValue,3,2), 16, 10) AS 'Algorithm'
FROM ( SELECT @prev := '') init
JOIN
( SELECT HardwareAddress != @prev AS first,
@prev := HardwareAddress,
HardwareAddress, EventValue, ID
FROM Events
WHERE Time > {unixtime}
AND EventType = 104
AND HardwareAddress IN ({disps})
ORDER BY
HardwareAddress,
ID DESC
) x
WHERE first;
{unixtime}
和{disps}
是使用Python String.format()方法填充的变量。
我很难从这个查询中创建新功能,因为没有其他人理解它是如何工作的,而且我还没有找到足够的文档。我所知道的是,查询会提取一个名为' algorithm'来自物联网设备发送的长十六进制字符串。
我粗略地理解子选择和区间变量是如何工作的,但有很多我不明白。 FROM (SELECT @prev := '') init
行如何运作?查询的最后两行也让我感到困惑。当没有任何内容引用它时,为什么子查询别名为x
,WHERE first
到底是什么意思?
如果有人能够指导我完成这段代码的工作,我将非常感激。
答案 0 :(得分:3)
让我们将SQL分解成几部分。整体的胆量是JOINed子查询:
SELECT
HardwareAddress != @prev AS first,
@prev := HardwareAddress,
HardwareAddress,
EventValue,
ID
FROM Events
WHERE
Time > {unixtime}
AND EventType = 104
AND HardwareAddress IN ({disps})
ORDER BY HardwareAddress, ID DESC
第1列:不知道({1}}是什么,我们看到运营商是@prev
。这意味着无论其操作数是什么,第1列都将是二进制值。在MySQL中,!=
或1
。
第2列:将SQL变量0
设置为当前匹配行的值。以下讨论,但查询结果将始终为@prev
。
第3,4,5栏:我假设自我解释。
约束1,2,3:我假设自我解释。
ORDER BY:值得注意的是,查询按第3列NULL
排序结果,然后HardwareAddress
降序。
然后第一列是布尔值,表示此行的ID
列是否与上一行的列相同。在上下文中,1表示这是HardwareAddress
的{{1}}的第一行。
因此,查询将返回如下结果:
HardwareAddress
将外部查询ORDER BY
的约束与最终结果放在一起将是:
+-------+--------------------------+-------------------+------------+-----+
| first | @prev := HardwareAddress | HardwareAddress | EventValue | ID |
+-------+--------------------------+-------------------+------------+-----+
| 1 | NULL | ff:ff:9d:5f:f5:01 | ... | 10 |
| 0 | NULL | ff:ff:9d:5f:f5:01 | ... | 9 |
| 0 | NULL | ff:ff:9d:5f:f5:01 | ... | 8 |
| 0 | NULL | ff:ff:9d:5f:f5:01 | ... | 7 |
| 1 | NULL | ff:ff:9d:5f:f5:02 | ... | 200 |
| 0 | NULL | ff:ff:9d:5f:f5:02 | ... | 37 |
| 0 | NULL | ff:ff:9d:5f:f5:02 | ... | 24 |
| 0 | NULL | ff:ff:9d:5f:f5:02 | ... | 23 |
| 0 | NULL | ff:ff:9d:5f:f5:02 | ... | 22 |
| 1 | NULL | ff:ff:9d:5f:f5:03 | ... | 152 |
| ... | NULL | ff:ff:9d:..:..:.. | ... | ... |
| ... | NULL | ff:ff:9d:..:..:.. | ... | ... |
| ... | NULL | ff:ff:9d:..:..:.. | ... | ... |
+-----+----------------------------+-------------------+------------+-----+
换句话说,整个查询尝试以给定的顺序获取每个HardwareAddress的第一个。神奇的WHERE first
?这只是初始化SQL变量+-------+--------------------------+-------------------+------------+-----+
| first | @prev := HardwareAddress | HardwareAddress | EventValue | ID |
+-------+--------------------------+-------------------+------------+-----+
| 1 | NULL | ff:ff:9d:5f:f5:01 | ... | 10 |
| 1 | NULL | ff:ff:9d:5f:f5:02 | ... | 200 |
| 1 | NULL | ff:ff:9d:5f:f5:03 | ... | 152 |
+-----+----------------------------+-------------------+------------+-----+
以便在随后的子查询中使用。尾部FROM (SELECT @prev := '') init
部分将内部查询别名为@prev
。有人可能会利用这些别名在更复杂的查询上进行进一步的连接,但在这种情况下,出于MySQL语法的原因,它们会在那里进行。你可以忽略它们。
总而言之,这是一种获取与每个HardwareAddress关联的最大ID的非常低效的方法。如果查询需要每列的最大值,那么只需直接询问MAX。考虑:
... ID DESC) x
您的输出中会有额外的ID;如果代码太脆弱而无法处理新列,您可以像原始查询一样使用子选择来掩盖它:
x
尽管有SELECT
HardwareAddress,
CONV(SUBSTRING(EventValue,3,2), 16, 10) AS 'Algorithm',
MAX(ID) AS ID
FROM Events
WHERE
Time > {unixtime}
AND EventType = 104
AND HardwareAddress IN ({disps})
GROUP BY 1, 2;
和SELECT HardwareAddress, CONV(...) AS 'Algorithm'
FROM (SELECT ...) x
的选择性,但这应该是一个更有效的查询。如果AND EventType
列有索引,那就更好了。
答案 1 :(得分:1)
所有子查询必须是别名。所有init
子查询都会初始化session / @变量(它等同于在运行查询之前只执行SET @prev := '';
)。
答案 2 :(得分:1)
FROM ( SELECT ... ) init
JOIN ( SELECT ... ) x
可以写
FROM ( SELECT ... ) AS init
JOIN ( SELECT ... ) AS x
后一种语法有助于暗示init
和x
是"别名"对于子查询。这些别名通常在查询的其他位置,但在这种情况下不需要,尤其是当存在ON
子句时。 (但仍然是强制性的。)实际使用的名称并不重要。
SELECT @prev := ''
只返回一行;它并没有真正使用。它具有分配"用户变量"的副作用。 @prev
到空字符串。外部查询依赖于在另一个子查询之前执行的子查询。
FROM
或JOIN
之后。
SELECT HardwareAddress != @prev AS first, -- Outputs 1 or 0
@prev := HardwareAddress, -- Outputs HA, and sets @prev
...
ORDER BY
HardwareAddress, -- to go thru in order
ID DESC
每当有"更改时,会将1
显示为first
"在HardwareAddress
中意图是1
位于第一行,然后是一堆行0
,然后返回1
以获取不同的地址。这两个表达式构成了一种“模式”。实现这一目标。
HardwareAddress, EventValue, ID
最后,您可以看到HardwareAddress
(以及其他一些事情)。
在应用程序代码中格式化输出真的会更好,而不是尝试在SQL 中执行@prev
游戏。
WHERE first
注意0表示FALSE,其他任何表示TRUE。 first
是上面讨论的列的别名。因此,当它为1
时,它会显示;否则它被跳过。
效果是显示每个组的第1个,按HardwareAddress分组。顺便说一句,简单的GROUP BY
是不可能的;需要某种形式的诡计。 (参见我对groupwise max的讨论。)
SELECT
HardwareAddress,
第二个派生表必须有一些额外的列。拥有此外部查询可让您丢弃这些额外内容并专注于所需的输出 - 即HardwareAddress
,而不是first
,而不是HardwareAddress
的额外副本。
SELECT ...
CONV(SUBSTRING(EventValue,3,2), 16, 10) AS 'Algorithm'
这会将EventValue
的一部分从十六进制转换为十进制。表达式可以在派生表中完成;它并没有多大区别。
, ID
这似乎是虚假的信息,后来被扔掉了。
WHERE Time > {unixtime}
AND EventType = 104
AND HardwareAddress IN ({disps})
(我假设您理解WHERE
子句。)我建议使用以下内容来帮助提高性能,尤其是在表格很大的情况下:
INDEX(EventType, Time),
INDEX(EventType, HardwareAddress)