Kusto 查询语言 (KQL) 图形语义最佳做法

本文介绍如何对不同的用例和方案高效地使用 KQL 中的图形语义功能。 它演示如何使用语法和运算符创建和查询图形,以及如何将它与其他 KQL 功能和函数集成。 它还可帮助用户避免常见的陷阱或错误,例如创建超出内存或性能上限的图形,或者应用不适合或不兼容的筛选器、投影或聚合。

图形的大小

make-graph 运算符会创建图形的内存中表示形式。 它由图形结构本身及其属性组成。 创建图形时,请使用适当的筛选器、投影和聚合来仅选择相关的节点和边缘及其属性。

以下示例演示如何减少节点和边缘及其属性的数量。 在此方案中,Bob 将经理从 Alice 更改为 Eve,用户只想查看其组织图形的最新状态。 为了缩减图形的大小,首先按组织属性筛选节点,然后使用 project-away 运算符从图形中移除该属性。 边缘也是如此。 然后,summarize 运算符arg_max 一起用于获取图形的最后已知状态。

let allEmployees = datatable(organization: string, name:string, age:long)
[
  "R&D", "Alice", 32,
  "R&D","Bob", 31,
  "R&D","Eve", 27,
  "R&D","Mallory", 29,
  "Marketing", "Alex", 35
];
let allReports = datatable(employee:string, manager:string, modificationDate: datetime)
[
  "Bob", "Alice", datetime(2022-05-23),
  "Bob", "Eve", datetime(2023-01-01),
  "Eve", "Mallory", datetime(2022-05-23),
  "Alice", "Dave", datetime(2022-05-23)
];
let filteredEmployees =
    allEmployees
    | where organization == "R&D"
    | project-away age, organization;
let filteredReports =
    allReports
    | summarize arg_max(modificationDate, *) by employee
    | project-away modificationDate;
filteredReports
| make-graph employee --> manager with filteredEmployees on name
| graph-match (employee)-[hasManager*2..5]-(manager)
  where employee.name == "Bob"
  project employee = employee.name, topManager = manager.name

输出

employee topManager
Bob Mallory

图形的最后已知状态

图形大小示例演示了如何使用 summarize 运算符和 arg_max 聚合函数获取图形边缘的最后已知状态。 获取最后已知状态是计算密集型操作。

请考虑创建具体化视图以提高查询性能,如下所示:

  1. 创建在模型中具有一些版本概念的表。 建议使用 datetime 列,它以后可用于创建图形时序。

    .create table employees (organization: string, name:string, stateOfEmployment:string, properties:dynamic, modificationDate:datetime)
    
    .create table reportsTo (employee:string, manager:string, modificationDate: datetime)
    
  2. 为每个表创建一个具体化视图,并使用 arg_max 聚合函数来确定员工的最后已知状态和 reportsTo 关系。

    .create materialized-view employees_MV on table employees
    {
        employees
        | summarize arg_max(modificationDate, *) by name
    }
    
    .create materialized-view reportsTo_MV on table reportsTo
    {
        reportsTo
        | summarize arg_max(modificationDate, *) by employee
    }
    
  3. 创建两个函数,确保仅使用具体化视图的具体化组件,并应用其他筛选器和投影。

    .create function currentEmployees () {
        materialized_view('employees_MV')
        | where stateOfEmployment == "employed"
    }
    
    .create function reportsTo_lastKnownState () {
        materialized_view('reportsTo_MV')
        | project-away modificationDate
    }
    

使用具体化生成的查询使查询更快、对较大的图形更高效。 它还具备更高的并发性和较低的查询延迟,便于获取图形的最新状态。 如果需要,用户仍可以根据员工和 reportsTo 表查询图形历史记录

let filteredEmployees =
    currentEmployees
    | where organization == "R&D"
    | project-away organization;
reportsTo_lastKnownState
| make-graph employee --> manager with filteredEmployees on name
| graph-match (employee)-[hasManager*2..5]-(manager)
  where employee.name == "Bob"
  project employee = employee.name, reportingPath = hasManager.manager

图形按时间顺序查看

某些方案要求根据图形在特定时间点的状态分析数据。 图形按时间顺序查看使用时间筛选器的组合,并使用 arg_max 聚合函数进行汇总。

以下 KQL 语句会创建一个函数,其中包含一个参数,用于定义图形值得关注的时间点。 它会返回一个现成的图形。

.create function graph_time_travel (interestingPointInTime:datetime ) {
    let filteredEmployees =
        employees
        | where modificationDate < interestingPointInTime
        | summarize arg_max(modificationDate, *) by name;
    let filteredReports =
        reportsTo
        | where modificationDate < interestingPointInTime
        | summarize arg_max(modificationDate, *) by employee
        | project-away modificationDate;
    filteredReports
    | make-graph employee --> manager with filteredEmployees on name
}

有了这个函数,用户就可以创建一个查询,基于 2022 年 6 月的图形获取 Bob 的上级经理。

graph_time_travel(datetime(2022-06-01))
| graph-match (employee)-[hasManager*2..5]-(manager)
  where employee.name == "Bob"
  project employee = employee.name, reportingPath = hasManager.manager

输出

employee topManager
Bob Dave

处理多个节点和边缘类型

有时需要使用包含多个节点类型的图形来上下文化时序数据。 处理此方案的一种方法是创建由规范模型表示的常规用途属性图。

有时,可能需要使用具有多个节点类型的图形来上下文化时序数据。 可以通过创建基于规范模型(如下例)的常规用途属性图来处理该问题。

  • nodes
    • nodeId (string)
    • label (string)
    • properties (dynamic)
  • edges
    • source (string)
    • destination (string)
    • label (string)
    • properties (dynamic)

以下示例演示如何将数据转换为规范模型以及如何对其进行查询。 图形节点和边缘的基表具有不同的架构。

此方案涉及一名工厂经理,他/她想要了解设备运行不佳的原因,以及谁负责修复设备。 该经理决定使用一个图形,将生产车间的资产图与每天更改的维护人员层次结构组合在一起。

下图显示了资产与其时序之间的关系,例如速度、温度和压力。 操作员和资产(如)通过操作边缘连接。 操作员自己向管理层上报。

属性图方案信息图。

这些实体的数据可以直接存储在群集中,也可以使用查询联合获取到其他服务,例如 Azure Cosmos DB、Azure SQL 或 Azure 数字孪生。 为了说明该示例,以下表格数据作为查询的一部分创建:

let sensors = datatable(sensorId:string, tagName:string, unitOfMeasuree:string)
[
  "1", "temperature", "°C",
  "2", "pressure", "Pa",
  "3", "speed", "m/s"
];
let timeseriesData = datatable(sensorId:string, timestamp:string, value:double, anomaly: bool )
[
    "1", datetime(2023-01-23 10:00:00), 32, false,
    "1", datetime(2023-01-24 10:00:00), 400, true,
    "3", datetime(2023-01-24 09:00:00), 9, false
];
let employees = datatable(name:string, age:long)
[
  "Alice", 32,
  "Bob", 31,
  "Eve", 27,
  "Mallory", 29,
  "Alex", 35,
  "Dave", 45
];
let allReports = datatable(employee:string, manager:string)
[
  "Bob", "Alice",
  "Alice", "Dave",
  "Eve", "Mallory",
  "Alex", "Dave"
];
let operates = datatable(employee:string, machine:string, timestamp:datetime)
[
  "Bob", "Pump", datetime(2023-01-23),
  "Eve", "Pump", datetime(2023-01-24),
  "Mallory", "Press", datetime(2023-01-24),
  "Alex", "Conveyor belt", datetime(2023-01-24),
];
let assetHierarchy = datatable(source:string, destination:string)
[
  "1", "Pump",
  "2", "Pump",
  "Pump", "Press",
  "3", "Conveyor belt"
];

员工传感器以及其他实体和关系不共享规范数据模型。 可以使用联合运算符合并和规范数据。

以下查询将传感器数据与时序数据联接在一起,以查找具有异常读数的传感器。 然后,它使用投影为图形节点创建通用模型。

let nodes =
    union
        (
            sensors
            | join kind=leftouter
            (
                timeseriesData
                | summarize hasAnomaly=max(anomaly) by sensorId
            ) on sensorId
            | project nodeId = sensorId, label = "tag", properties = pack_all(true)
        ),
        ( employees | project nodeId = name, label = "employee", properties = pack_all(true));

边缘以类似的方式转换。

let edges =
    union
        ( assetHierarchy | extend label = "hasParent" ),
        ( allReports | project source = employee, destination = manager, label = "reportsTo" ),
        ( operates | project source = employee, destination = machine, properties = pack_all(true), label = "operates" );

通过规范化的节点和边缘数据,可以使用 make-graph 运算符创建图形,如下所示:

let graph = edges
| make-graph source --> destination with nodes on nodeId;

创建后,定义路径模式并投影所需的信息。 模式从标记节点开始,后跟资产的可变长度边缘。 该资产由一个操作员操作,该操作员通过可变长度边缘(名为 reportsTo)向上级经理汇报。 graph-match 运算符的约束部分(在此例中为 where)将标记减少到具有异常且在特定日期操作的那些。

graph
| graph-match (tag)-[hasParent*1..5]->(asset)<-[operates]-(operator)-[reportsTo*1..5]->(topManager)
    where tag.label=="tag" and tobool(tag.properties.hasAnomaly) and
        startofday(todatetime(operates.properties.timestamp)) == datetime(2023-01-24)
        and topManager.label=="employee"
    project
        tagWithAnomaly = tostring(tag.properties.tagName),
        impactedAsset = asset.nodeId,
        operatorName = operator.nodeId,
        responsibleManager = tostring(topManager.nodeId)

输出

tagWithAnomaly impactedAsset 1operatorName responsibleManager
温度 Eve Mallory

graph-match 中的投影输出了温度传感器在指定日期显示了异常这一信息。 它是由 Eve 操作的,Eve 最终向 Mallory 汇报。 利用此信息,工厂经理可以联系 Eve,并可能联系 Mallory,以便更好地了解异常情况。