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

时间:2018-02-27 21:47:34

标签: mysql variables subquery

我继承了这个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行如何运作?查询的最后两行也让我感到困惑。当没有任何内容引用它时,为什么子查询别名为xWHERE first到底是什么意思?

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

3 个答案:

答案 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列:不知道({1}}是什么,我们看到运营商是@prev。这意味着无论其操作数是什么,第1列都将是二进制值。在MySQL中,!=1

  2. 第2列:将SQL变量0设置为当前匹配行的值。以下讨论,但查询结果将始终为@prev

  3. 第3,4,5栏:我假设自我解释。

  4. 约束1,2,3:我假设自我解释。

  5. ORDER BY:值得注意的是,查询按第3列NULL排序结果,然后HardwareAddress 降序

  6. 然后第一列是布尔值,表示此行的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

后一种语法有助于暗示initx是"别名"对于子查询。这些别名通常在查询的其他位置,但在这种情况下不需要,尤其是当存在ON子句时。 (但仍然是强制性的。)实际使用的名称并不重要。

SELECT @prev := ''

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

顺便说一句,两个子查询被称为"派生"表格,因为它们位于FROMJOIN之后。

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)