用于设备和模块孪生、作业和消息路由的 IoT 中心查询语言

IoT 中心提供类似于 SQL 的强大语言,用于检索有关设备孪生模块孪生作业消息路由的信息。 本文内容:

  • IoT 中心查询语言的主要功能简介。
  • 语言的详细说明。 有关消息路由的查询语言的详细信息,请参阅 IoT 中心消息路由查询语法

有关特定示例,请参阅 IoT 中心设备和模块孪生的查询IoT 中心作业的查询

注意

本文中提到的某些功能(例如云到设备消息传递、设备孪生、设备管理)仅在 IoT 中心的标准层中提供。 有关基本层和标准/免费 IoT 中心层的详细信息,请参阅 为解决方案选择正确的 IoT 中心层和大小

运行 IoT 中心查询

可以直接在 Azure 门户中针对 IoT 中心运行查询。

  1. 登录 Azure 门户,导航到 IoT 中心。
  2. 从导航菜单的设备管理部分中选择查询
  3. 在文本框中输入查询,然后选择“运行查询”。

还可以使用 Azure IoT 服务 SDK 和服务 API 在应用程序内运行查询。

有关实现 IoT 中心查询的示例代码,请参阅具有服务 SDK 的查询示例部分。

有关 SDK 参考页和示例的链接,请参阅 Azure IoT 中心 SDK

IoT 中心查询基础知识

每个 IoT 中心查询都包括 SELECT 和 FROM 子句,以及可选的 WHERE 和 GROUP BY 子句。

查询在 JSON 文档的集合(例如设备孪生)上运行。 FROM 子句指定要遍历的文档集合(即 devicesdevices.modulesdevices.jobs)。

然后,应用 WHERE 子句中的筛选器。 使用聚合时,此步骤的结果将按照 GROUP BY 子句中的指定方式进行分组。 对于每组,将按照 SELECT 子句中的指定生成一行。

SELECT <select_list>
  FROM <from_specification>
  [WHERE <filter_condition>]
  [GROUP BY <group_specification>]

SELECT子句

每个IoT 中心查询都需要 SELECT <select_list> 子句。 它用于指定要通过查询检索的值。 它指定用于生成新 JSON 对象的 JSON 值。 对于 FROM 集合中已筛选子集(且可选择性分组)的每个元素,投影阶段将生成一个新 JSON 对象。 使用 SELECT 子句中指定的值构造此对象。

例如:

  • 返回所有值

    SELECT *
    
  • 返回特定属性

    SELECT DeviceID, LastActivityTime
    
  • 聚合查询结果以返回计数

    SELECT COUNT() as TotalNumber
    

目前,与 SELECT 不同的选择子句仅在设备孪生上的聚合查询中受支持。

以下是 SELECT 子句的语法:

SELECT [TOP <max number>] <projection list>

<projection_list> ::=
    '*'
    | <projection_element> AS alias [, <projection_element> AS alias]+

<projection_element> :==
    attribute_name
    | <projection_element> '.' attribute_name
    | <aggregate>

<aggregate> :==
    count()
    | avg(<projection_element>)
    | sum(<projection_element>)
    | min(<projection_element>)
    | max(<projection_element>)

Attribute_name 引用 FROM 集合中 JSON 文档的任何属性。

FROM 子句

每个 IoT 中心查询都需要包含该 FROM <from_specification> 子句。 它必须是以下三个值之一:

  • 用于查询设备孪生的设备
  • devices.modules 用于查询模块孪生
  • devices.jobs 用于查询作业的各设备详细信息

例如:

  • 获取所有设备孪生

    SELECT * FROM devices
    

WHERE 子句

WHERE <filter_condition> 条款是可选的。 它指定要将 FROM 集合中的 JSON 文档内含在结果中时需满足的一项或多项条件。 任何 JSON 文档都必须将指定的条件计算为 true ,才能包含在结果中。

例如:

  • 检索以特定设备为目标的所有作业

    SELECT * FROM devices.jobs
      WHERE devices.jobs.deviceId = 'myDeviceId'
    

表达式和条件 ”部分中介绍了允许的条件。

GROUP BY 子句

GROUP BY <group_specification> 条款是可选的。 该子句在 WHERE 子句中指定的筛选器之后执行,并在 SELECT 子句中指定的投影之前执行。 它根据属性值来分组文档。 这些组用于生成 SELECT 子句中指定的聚合值。

例如:

  • 返回报告每个遥测配置状态的设备计数

    SELECT properties.reported.telemetryConfig.status AS status,
      COUNT() AS numberOfDevices
    FROM devices
    GROUP BY properties.reported.telemetryConfig.status
    

目前,仅在查询设备孪生时才支持使用 GROUP BY 子句。

注意

术语 当前被视为查询中的特殊关键字。 如果用作group属性名称,请考虑将它与双括号括起来以避免错误,如以下示例所示: SELECT * FROM devices WHERE tags.[[group]].name = 'some_value'

GROUP BY 的正式语法为:

GROUP BY <group_by_element>
<group_by_element> :==
    attribute_name
    | < group_by_element > '.' attribute_name

Attribute_name 引用 FROM 集合中 JSON 文档的任何属性。

查询结果分页

查询对象在实例化时,最大页大小为 小于等于 100 条记录。 若要获取多个页面,请多次在 Node.js SDK 上调用 nextAsTwin 或在 .NET SDK 方法上调用 GetNextAsTwinAsync。 一个查询对象可公开多个“下一步”值,具体取决于该查询所需的反序列化选项。 例如,查询对象可以返回设备孪生或 Job 对象,或者在使用投影时返回普通 JSON。

表达式和条件

从较高层面讲,表达式可以:

  • 计算结果为某种 JSON 类型的值(例如布尔值、数字、字符串、数组或对象)。
  • 通过使用内置运算符和函数处理来自设备 JSON 文档的数据和常量来定义。

条件是计算结果为布尔值的表达式。 将任何不同于布尔值“true”的常数视为“false” 。 此规则包括“Null”、“undefined”、任何对象或数组实例、任何字符串和布尔值“false” 。

表达式的语法为:

<expression> ::=
    <constant> |
    attribute_name |
    <function_call> |
    <expression> binary_operator <expression> |
    <create_array_expression> |
    '(' <expression> ')'

<function_call> ::=
    <function_name> '(' expression ')'

<constant> ::=
    <undefined_constant>
    | <null_constant>
    | <number_constant>
    | <string_constant>
    | <array_constant>

<undefined_constant> ::= undefined
<null_constant> ::= null
<number_constant> ::= decimal_literal | hexadecimal_literal
<string_constant> ::= string_literal
<array_constant> ::= '[' <constant> [, <constant>]+ ']'

若要了解表达式语法中的每个符号表示什么,请参阅下表:

符号 定义
属性名称 FROM 集合中 JSON 文档的任一属性。
二元运算符 运算符部分列出的任意二进制运算符。
function_name 函数部分列出的任一函数。
十进制字面量 以十进制表示法表示的浮点数。
十六进制字面量 以字符串“0x”后接十六进制数字符串表示的数字。
string_literal 由零个或多个 Unicode 字符序列或转义符序列表示的 Unicode 字符串。 字符串文本括在单引号或双引号中。 允许的转义符:\'\"\\\uXXXX(由 4 个十六进制数字定义的 Unicode 字符)。

运算符

支持以下运算符:

系列 运算符
算术 +,-,*,/,%
逻辑 AND、OR、NOT
比较 =,!=,<,>,<=,>=,<>

Functions

查询孪生体和作业时,唯一受支持的函数为:

函数 说明
IS_DEFINED(属性) 返回一个布尔值,该值指示属性是否赋值(包括 null)。

在路由情况下,支持以下数学函数:

函数 说明
ABS(x) 返回指定数值表达式的绝对(正)值。
EXP(x) 返回指定数值表达式 (e^x) 的指数值。
POWER(x,y) 返回指定表达式相对指定幂 (x^y) 的值。
SQUARE(x) 返回指定数字值的平方。
CEILING(x) 返回大于或等于指定数值表达式的最小整数值。
FLOOR(x) 返回小于或等于指定数值表达式的最大整数。
SIGN(x) 返回指定数值表达式的正数 (+1)、零 (0) 或负数 (-1)。
SQRT(x) 返回指定数值的平方根。

在路由条件中,支持以下类型检查和类型转换函数:

函数 说明
自治系统编号 将输入字符串转换为数字。 如果输入的是数字,则为 noop;如果字符串不表示数字,则为 Undefined
IS_ARRAY 返回一个布尔值,指示指定表达式类型是否为数组。
IS_BOOL 返回一个布尔值,指示指定表达式的类型是否为布尔表达式。
已定义 返回一个布尔值,该值指示属性是否为值。 仅当该值是基元类型时才支持此函数。 基元类型包括字符串、布尔值、数字或 null。 不支持 DateTime、对象类型和数组。
为空 返回一个布尔值,指示指定表达式的类型是否为 null。
IS_NUMBER (判断是否为数字) 返回一个布尔值,指示指定表达式的类型是否为数字。
是否对象 返回一个布尔值,指示指定表达式的类型是否为 JSON 对象。
是原始的 返回一个布尔值,指示指定表达式的类型是否为基元(字符串、布尔值、数字或 null)。
是字符串 返回一个布尔值,指示指定表达式的类型是否为字符串。

在路由情况下,支持以下字符串函数:

函数 说明
CONCAT(x, y, ...) 返回一个字符串,该字符串是连接两个或多个字符串值的结果。
LENGTH(x) 返回指定字符串表达式的字符数。
LOWER(x) 返回在将大写字符数据转换为小写后的字符串表达式。
UPPER(x) 返回在将小写字符数据转换为大写后的字符串表达式。
SUBSTRING(string, start [, length]) 返回字符串表达式的部分内容,该内容起于指定字符从零开始的位置,继续到指定长度或字符串结尾。
INDEX_OF(字符串,片段) 返回第一个指定的字符串表达式中第一次出现第二个字符串表达式的起始位置,如果未找到字符串,则返回 -1。
STARTS_WITH(x,y) 返回一个布尔值,指示第一个字符串表达式是否以第二个字符串表达式开头。
ENDS_WITH(x,y) 返回一个布尔值,指示第一个字符串表达式是否以第二个字符串表达式结尾。
CONTAINS(x,y) 返回一个布尔值,该值指示第一个字符串表达式是否包含第二个字符串表达式。

使用服务 SDK 的查询示例

C# 示例

查询功能由 Azure IoT 中心 服务 SDK for .NETRegistryManager 类中公开。

以下是一个简单的查询示例:

var query = registryManager.CreateQuery("SELECT * FROM devices", 100);
while (query.HasMoreResults)
{
    var page = await query.GetNextAsTwinAsync();
    foreach (var twin in page)
    {
        // do work on twin object
    }
}

查询对象使用 “查询结果分页 ”部分中提到的参数实例化。 通过多次调用 GetNextAsTwinAsync 方法检索多个页面。

Node.js 示例

Azure IoT 中心 服务 SDK for Node.js 中,通过 Registry 对象提供了查询功能。

以下是一个简单的查询示例:

var query = registry.createQuery('SELECT * FROM devices', 100);
var onResults = function(err, results) {
    if (err) {
        console.error('Failed to fetch the results: ' + err.message);
    } else {
        // Do something with the results
        results.forEach(function(twin) {
            console.log(twin.deviceId);
        });

        if (query.hasMoreResults) {
            query.nextAsTwin(onResults);
        }
    }
};
query.nextAsTwin(onResults);

查询对象使用 “查询结果分页 ”部分中提到的参数实例化。 通过多次调用 nextAsTwin 方法检索多个页面。

IoT 中心设备和模块孪生的查询

设备孪生模块孪生 可以包含任意 JSON 对象作为标记和属性。 借助 IoT 中心,可以将设备孪生和模块孪生查询为包含所有孪生信息的单个 JSON 文档。

下面是一个示例 IoT 中心设备孪生(模块孪生与 moduleId 的参数类似):

{
    "deviceId": "myDeviceId",
    "etag": "AAAAAAAAAAc=",
    "status": "enabled",
    "statusUpdateTime": "0001-01-01T00:00:00",
    "connectionState": "Disconnected",
    "lastActivityTime": "0001-01-01T00:00:00",
    "cloudToDeviceMessageCount": 0,
    "authenticationType": "sas",
    "x509Thumbprint": {
        "primaryThumbprint": null,
        "secondaryThumbprint": null
    },
    "version": 2,
    "tags": {
        "location": {
            "region": "US",
            "plant": "Redmond43"
        }
    },
    "properties": {
        "desired": {
            "telemetryConfig": {
                "configId": "db00ebf5-eeeb-42be-86a1-458cccb69e57",
                "sendFrequencyInSecs": 300
            },
            "$metadata": {
            ...
            },
            "$version": 4
        },
        "reported": {
            "connectivity": {
                "type": "cellular"
            },
            "telemetryConfig": {
                "configId": "db00ebf5-eeeb-42be-86a1-458cccb69e57",
                "sendFrequencyInSecs": 300,
                "status": "Success"
            },
            "$metadata": {
            ...
            },
            "$version": 7
        }
    }
}

设备孪生查询

IoT 中心将设备孪生公开为一个称为 设备 的文档集。 例如,最基本的查询检索整个设备孪生集:

SELECT * FROM devices

注意

Azure IoT SDK 支持对大型结果进行分页。

可以使用 SELECT 子句聚合查询的结果。 例如,以下查询获取 IoT 中心内设备总数的计数:

SELECT COUNT() as totalNumberOfDevices FROM devices

使用 WHERE 子句筛选查询结果。 例如,若要接收设备孪生,其中 location.region 标记设置为 美国 ,请使用以下查询:

SELECT * FROM devices
WHERE tags.location.region = 'CN'

使用布尔运算符和算术比较创建复杂的 WHERE 子句。 例如,以下查询可检索出位于美国的设备双胞胎,并将其配置为发送少于每分钟一次的遥测数据:

SELECT * FROM devices
  WHERE tags.location.region = 'CN'
    AND properties.reported.telemetryConfig.sendFrequencyInSecs >= 60

您还可以将数组常量与INNIN(不包括)运算符一起使用。 例如,以下查询检索报告 WiFi 或有线连接的设备孪生:

SELECT * FROM devices
  WHERE properties.reported.connectivity IN ['wired', 'wifi']

通常需要标识包含特定属性的所有设备孪生。 IoT 中心支持用于此目的的函数 is_defined() 。 例如,以下查询检索定义 connectivity 属性的设备孪生:

SELECT * FROM devices
  WHERE is_defined(properties.reported.connectivity)

有关筛选功能的完整参考,请参阅 WHERE 子句 部分。

支持分组功能。 例如,以下查询返回每个遥测配置状态中的设备计数:

SELECT properties.reported.telemetryConfig.status AS status,
    COUNT() AS numberOfDevices
  FROM devices
  GROUP BY properties.reported.telemetryConfig.status

此分组查询将返回类似于以下示例的结果:

[
    {
        "numberOfDevices": 3,
        "status": "Success"
    },
    {
        "numberOfDevices": 2,
        "status": "Pending"
    },
    {
        "numberOfDevices": 1,
        "status": "Error"
    }
]

在此示例中,三个设备报告了成功的配置,两个设备仍在应用配置,一个设备报告了错误。

投影查询允许开发人员仅返回他们关心的属性。 例如,若要检索上次活动时间以及已断开连接的所有已启用设备的设备 ID,请使用以下查询:

SELECT DeviceId, LastActivityTime FROM devices WHERE status = 'enabled' AND connectionState = 'Disconnected'

该查询的结果如以下示例所示:

[
  {
    "deviceId": "AZ3166Device",
    "lastActivityTime": "2021-05-07T00:50:38.0543092Z"
  }
]

模块孪生查询

对模块孪生的查询与设备孪生的查询类似,但使用不同的集合/命名空间;你不再从 devices 查询,而是从 devices.modules 查询。

SELECT * FROM devices.modules

不允许在设备和 devices.modules 集合之间进行联接。 如果要跨设备查询模块孪生体,可以根据标签进行查询。 以下查询将返回所有设备中具有扫描状态的所有模块孪生:

SELECT * FROM devices.modules WHERE properties.reported.status = 'scanning'

以下查询返回所有具有扫描状态的模块孪生,但仅在指定的设备子集上返回:

SELECT * FROM devices.modules
  WHERE properties.reported.status = 'scanning'
  AND deviceId IN ['device1', 'device2']

孪生查询限制

Important

查询结果是一致的最终操作,应该容忍至多 30 分钟的延迟。 在大多数情况下,双重查询通常只需几秒钟便能返回结果。 IoT 中心致力于降低所有操作的延迟。 但是,由于网络条件和其他不可预测因素,它不能保证特定的延迟。

孪生查询的替代方法是使用 获取孪生 REST API 按 ID 查询单个设备孪生。 此 API 始终返回最新值,并具有更高的速率限制。 可以直接颁发 REST API,也可以在其中一个 Azure IoT 中心服务 SDK 中使用等效的功能。

查询表达式的最大长度可以为 8,192 个字符。

目前,仅当这些属性具有基元值时,才支持基元类型(无对象) ... WHERE properties.desired.config = properties.reported.config 之间的比较。

对于任何场景,我们建议不要依赖于数字孪生查询中设备标识属性中找到的lastActivityTime。 此字段不保证对设备状态的准确性评估。 请改用 IoT 设备生命周期事件来管理设备状态和活动。 有关如何在解决方案中使用 IoT 中心 生命周期事件的信息,请参阅通过事件网格触发操作来响应 IoT 中心 事件

注意

避免对该操作的最大延迟做出任何假设。 有关如何将延迟考虑在内生成解决方案的详细信息,请参阅 延迟解决方案

针对IoT 中心作业的查询

作业 提供了在一组设备上执行操作的方法。 每个设备孪生都包含一个名为 jobs 的集合,其中包含面向它的作业信息。 IoT 中心使你能够以包含所有孪生信息的单个 JSON 文档的形式查询作业。

下面是一个示例 IoT 中心设备孪生体,它是名为 myJobId 的任务的一部分:

{
    "deviceId": "myDeviceId",
    "etag": "AAAAAAAAAAc=",
    "tags": {
        ...
    },
    "properties": {
        ...
    },
    "jobs": [
        {
            "deviceId": "myDeviceId",
            "jobId": "myJobId",
            "jobType": "scheduleUpdateTwin",
            "status": "completed",
            "startTimeUtc": "2016-09-29T18:18:52.7418462",
            "endTimeUtc": "2016-09-29T18:20:52.7418462",
            "createdDateTimeUtc": "2016-09-29T18:18:56.7787107Z",
            "lastUpdatedDateTimeUtc": "2016-09-29T18:18:56.8894408Z",
            "outcome": {
                "deviceMethodResponse": null
            }
        },
        ...
    ]
}

目前,在 IoT 中心 查询语言中,此集合可查询为devices.jobs

Important

目前,在查询设备孪生时,作业属性不会被返回。 也就是说,包含 FROM devices的查询。 作业属性只能通过使用 FROM devices.jobs 的查询直接访问。

例如,以下查询将返回所有影响单个设备的任务(包括过去的和计划的):

SELECT * FROM devices.jobs
  WHERE devices.jobs.deviceId = 'myDeviceId'

请注意,此查询提供每个返回作业的设备特定状态(以及可能的直接方法响应)。

还可以对集合中的所有 devices.jobs 对象属性使用任意布尔条件进行筛选。

例如,以下查询检索在 2016 年 9 月之后为特定设备创建的所有已完成的设备孪生更新作业:

SELECT * FROM devices.jobs
  WHERE devices.jobs.deviceId = 'myDeviceId'
    AND devices.jobs.jobType = 'scheduleUpdateTwin'
    AND devices.jobs.status = 'completed'
    AND devices.jobs.createdTimeUtc > '2016-09-01'

您还可以检索单个作业的设备逐项结果。

SELECT * FROM devices.jobs
  WHERE devices.jobs.jobId = 'myJobId'

作业查询限制

查询表达式的最大长度可以为 8,192 个字符。

当前,对 devices.jobs 的查询不支持:

  • 因此,只有 SELECT * 可以进行投影。
  • 除了作业属性外,还引用设备孪生的条件(请参阅上一部分)。
  • 聚合,例如 计数平均分组