MySQL间隔变量和子查询表示法

问题描述 投票:0回答:3

我继承了这个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()方法的变量。

我很难从这个查询中创建新功能,因为没有其他人理解它是如何工作的,而且我还没有找到足够的文档。我所知道的是,查询从IoT设备发送的长十六进制字符串中提取一个名为“algorithm”的值列表。

我大致了解子选择和区间变量是如何工作的,但有很多我不明白。 FROM (SELECT @prev := '') init线如何工作?查询的最后两行也让我感到困惑。当没有任何东西引用时,为什么子查询别名为xWHERE first究竟是什么意思?

如果有人能够指导我完成这段代码的工作,我将非常感激。

mysql variables subquery
3个回答
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中,10
  2. 第2列:将SQL变量@prev设置为当前匹配行的值。讨论如下,但查询的结果将始终是NULL
  3. 第3,4,5栏:我假设自我解释。
  4. 约束1,2,3:我假设自我解释。
  5. ORDER BY:值得注意的是,查询按第3列HardwareAddress排序结果,然后ID降序。

然后第一列是布尔值,表示此行的HardwareAddress列是否与前一行的HardwareAddress列相同。在上下文中,1意味着这是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:..:..:.. | ... | ... | +-----+----------------------------+-------------------+------------+-----+

因此,查询将返回如下结果:

WHERE first

将它与外部查询的约束放在一起,+-------+--------------------------+-------------------+------------+-----+ | 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

换句话说,整个查询尝试以给定的顺序获取每个HardwareAddress的第一个。神奇的@prev?这只是初始化SQL变量... ID DESC) x以便在随后的子查询中使用。尾部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; 。有人可能会利用这些别名在更复杂的查询上进行进一步的连接,但在这种情况下,它们是出于MySQL语法的原因。你可以忽略它们。

总而言之,这是一种获取与每个HardwareAddress关联的最大ID的非常低效的方法。如果查询需要每列的最大值,那么只需直接询问MAX。考虑:

SELECT HardwareAddress, CONV(...) AS 'Algorithm'
FROM (SELECT ...) x

您的输出中将有一个额外的ID;如果代码太脆弱而无法处理新列,您可以像原始查询使用子选择一样掩盖它:

AND EventType

尽管AND HardwareAddressID具有选择性,但这应该是一个更有效的查询。如果init列有一个索引,那就更好了。


1
投票

所有子查询必须是别名。所有SET @prev := '';子查询都会初始化session / @变量(它相当于在运行查询之前执行FROM ( SELECT ... ) init JOIN ( SELECT ... ) x )。


1
投票
FROM ( SELECT ... ) AS init
JOIN ( SELECT ... ) AS x

可以写

init

后一种语法有助于暗示xON是子查询的“别名”。这些别名通常在查询的其他地方需要,但在这种情况下不需要,尤其是当存在SELECT @prev := '' 子句时。 (仍然是强制性的。)实际使用的名称并不重要。

@prev

只返回一行;它并没有真正使用。它具有将“用户变量”FROM分配给空字符串的副作用。外部查询依赖于在另一个子查询之前执行的子查询。

顺便说一下,这两个子查询称为“派生”表,因为它们位于JOINSELECT 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

最后,你可以看到@prev(以及其他一些东西)。

在您的应用程序代码中格式化输出真的会更好,而不是尝试在SQL中执行WHERE first 游戏。

first

注意,0表示FALSE,其他任何表示TRUE。 1是上面讨论的专栏的别名。所以,当它是GROUP BY时,它显示;否则它会被跳过。

效果是显示每个组的第1个,按HardwareAddress分组。顺便说一下,简单的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) 条款。)我推荐以下内容来帮助提高性能,特别是如果表格很大:

qazxswpoi
© www.soinside.com 2019 - 2024. All rights reserved.