修改密码

请输入密码
请输入密码 请输入8-64长度密码 和 email 地址不相同 至少包括数字、大写字母、小写字母、半角符号中的 3 个
请输入密码
提交

修改昵称

当前昵称:
提交

申请证书

证书详情

Please complete this required field.

  • Ultipa Graph V4

Standalone

Please complete this required field.

Please complete this required field.

服务器的MAC地址

Please complete this required field.

Please complete this required field.

取消
申请
ID
产品
状态
核数
申请天数
审批时间
过期时间
MAC地址
申请理由
审核信息
关闭
基础信息
  • 用户昵称:
  • 手机号:
  • 公司名称:
  • 公司邮箱:
  • 地区:
  • 语言:
修改密码
申请证书

当前未申请证书.

申请证书
Certificate Issued at Valid until Serial No. File
Serial No. Valid until File

Not having one? Apply now! >>>

ProductName CreateTime ID Price File
ProductName CreateTime ID Price File

No Invoice

搜索
    中文

      UQL 查询举例

      本文将介绍几种常用的图查询命令,展示如何用编写其 UQL 语句以及它们在 Ultipa Manager 中的查询结果样式。

      文本使用的图集:

      对于没有 Ultipa 服务器环境的用户:

      • 点击代码框上的 Run 按钮,在弹窗中运行并查看当前 UQL 命令的查询结果
      • 点击 Copy 按钮复制代码,并在 Ultipa Playground 中运行(注意选择图集 Quick Start

      基本查询

      查找点

      点的查找类似于在关系型数据库中对某张表进行查询。尝试理解下边的 UQL 语句:

      find().nodes() as myFirstQuery
      return myFirstQuery{*} limit 10
      

      该语句的字面含义为:查找点,将它们作为 myFirstQuery,返回 myFirstQuery 并限制 10 条记录。

      以上的描述已经相当接近了,以下是更多的解释:

      • 链式语句 find().nodes() 发起了点查询
      • 这些点的别名 myFirstQuery 被后面的 return 子句调用
      • 点别名后紧跟着的 {*} 携带了这些点的全部属性

      所以这句 UQL 的目的是 “找到 10 个不同的点,返回它们的全部属性”。在 Ultipa Manager 中的执行结果为:

      返回的 10 个点均属于 schema @customer,这种巧合取决于数据插入的先后顺序,同时也会受到并发计算的影响。

      如果要查找某个特定 schema 的点,比如 merchant,可以在 nodes() 中添加描述:

      find().nodes({@merchant}) as mySecondQuery
      return mySecondQuery{*} limit 10
      
      • 参数 nodes() 中的花括号 {} 及其内容被称作 过滤器图数据 中介绍的其他用来描述点的参数也如是)

      上面的 UQL 语句的执行结果如下:

      查找边

      在当前的图模型中,@customer节点通过@transfer边向@merchant节点进行转账:

      边的查找与点极为类似,只需替换使用 edges() 传入边的过滤条件:

      find().edges({_from == "60017791850"}) as payment
      return payment{*} limit 10
      

      返回的 10 个 payment 全部由顾客 Chen**(ID 为 60017791850)发起,payment_id 表示接收转账的商家的 ID。现在,用一个点查询语句调用这些商家的 ID:

      find().edges({_from == "60017791850"}) as payment
      find().nodes({_id == payment._to}) as merchant
      return merchant{*} limit 10
      
      • 点别名后面紧跟着 ._id 表示调用这些点的属性 _id

      续写后的 UQL 语句返回 “10 个接收了顾客 Chen**(ID 为 60017791850)的转账的商家的所有属性”:

      返回的 10 个商家中有两个商家重复出现了,即 _uuid 为 117 和 119 的商家各出现了两次,原因是这两个商家各收到两笔来自 Chen** 的付款。

      这向我们展示了一个典型的复杂图(多边图)的例子,即两个节点之间有多条边存在。想要更好的观察这一现象,可以更换查询命令并用 Ultipa Manager 的2D 视图进行观测。

      展开

      spread().src({_id == "60017791850"}).depth(1) as transaction
      return transaction{*} limit 10
      

      该语句的字面意思:展开自 ID 为 60017791850 的源头,深度为 1,返回 10 条记录的所有属性。

      • 命令 spread() 发起了以节点 src() 为源头的边查询,并以 BFS 方式进行搜索
      • 参数 depth() 限制了该 BFS 搜索的最大深度
      • 别名 transaction 为找到的边的一步路径形式,即 “起点-边-终点”
      • 路径别名后面紧跟着的 {*} 携带了路径中点、边的全部属性

      所以这句 UQL 的目的是 “查找 10 笔由顾客 Chen** 发起的转账,返回顾客 Chen**、所有转账边、所有收款商家的全部属性”:

      Ultipa Manager 将返回的路径自动显示在 2D 视图中,从图中能清楚的看到商家 117 和 119 与顾客 Chen** 之间的多笔转账。

      如果要指定 spread() 命令所返回的路径终点的不重复的数量,可以适当引入去重操作再进行返回。另一种思路是改用另一种查询命令,同样使用 BFS 的方式进行搜索,但搜索的目标是点,而非边。

      K邻

      khop().src({_id == "60017791850"}).depth(1) as merchant
      return merchant{*} limit 10
      

      该语句的字面意思:跳跃 K 步自 ID 为 60017791850 的源头,深度为 1,返回 10 条记录的所有属性。

      • 命令 khop() 发起了以 src() 为源头的点查询,并以 BFS 方式进行搜索
      • 别名 merchant 为找到的点

      所以这句 UQL 的目的是 “查找 10 个接收顾客 Chen** 的转账的商家,返回商家的全部属性”:

      khop() 命令找到了 10 个不同的收款商家,对比之前使用 spread() 命令找到的 10 笔付款中的 8 个收款商家,新发现了 110 和 118 两个商家。

      模板的思维方式

      模板查询是一种高级的图查询方法,通过使用 n()e()nf() 对路径中的每一个点、边进行精确描述。(前文 图数据 中有提到。)

      链路

      n({_id == "60017791850"}).e().n() as transaction
      return transaction{*} limit 10
      

      该语句的字面意思:查找从 ID 为 60017791850 的点开始的一步路径,返回 10 条路径的所有属性:

      返回的结果和之前 spread() 示例的结果完全一致,因为它们全都是在查找从顾客 Chen** 出发的一步转账路径。

      现在考虑这样一种路径:从 Chen** 出发,经过三条边,最终到达某个商家:

      n({_id == "60017791850"}).e({tran_amount > 70000})[3].n() as transChain
      return transChain{*} limit 10
      
      • e() 后面紧跟着的 [3] 表示 3 条连续的边(作用类似于之前提到的 depth()
      (绿色圆圈:路径起点;蓝色圆圈:路径终点)

      10 条路径从 Chen** 出发,到达第二个节点(商家 111)后兵分三路,再从 Zheng**、Qian**、Chu** 分散为 10 条路径、到达 8 个不同的商家。

      图中所示的 Chen** 和 Zheng**、Qian**、Chu** 均购买过商家 111 的商品,在特定场景下可能意味着这 4 个顾客特征相似,因此便有理由将 Zheng**、Qian**、Chu** 光顾过的其他 8 个商家(路径终点)推荐给 Chen**。这便是一种典型的商品、商家推荐模型。

      当返回结果中有点、边重复出现时,Manager 的 2D 视图不会对其进行重复渲染。例如上面查到的 10 条路径,每一条都以 Chen** 为起点,以 “转账 60” 为第一条边,以 “商家 111” 为第二个点,而 2D 视图中只渲染一个 Chen**、一条 “转账 60” 边、一个 “商家 111”。如需了解每一条路径的具体信息,可切换至列表视图:

      环路

      路径中不允许有边重复出现,但允许有重复的点,于是便有了环路。

      n({@customer} as start).e({tran_date > "2020-1-1 0:0:0"})[4].n(start) as transRing
      return transRing{*} limit 10
      
      • 最后一个 n() 调用了的第一个 n() 的别名 start,表示路径的首尾节点相同(即形成环路)

      所以这句 UQL 的目的是 “查找 10 条从某个顾客出发的四步路径,并最终回到该顾客,要求每一步转账都晚于 2020-1-1 0:0:0,返回这些路径中的所有点、边的全部属性”:

      切换到列表视图为:

      顾客 Chu**、Ou** 均从商家 110、108 处购买商品,这种结构在特定的场景下意味着 Chu** 和 Ou** 具有相似性。

      最短路

      n({_id == "60017791850"}).e()[:5].n(115) as transRange
      return transRange{*} limit 1
      
      • 中括号中的 :5 也可写作 1:5,表示边数为 1~5,而非固定值
      • 最后一个 n() 中的数字 115 是过滤器 {_uuid == 115} 的简写形式

      所以这句 UQL 的目的是 “查找 1 条从 Chen** 出发的、到达 UUID 为 115 的节点的路径,要求边数不超过 5,返回这条路径中的所有点、边的全部属性”:

      碰巧,返回的这条路径刚好有 5 条边。

      对路径中的边数稍加修改,则可能完全改变查询的目标:

      n({_id == "60017791850"}).e()[*:5].n(115) as transShortest
      return transShortest{*} limit 1
      
      • 在边数中添加星号 * 后得到 *:5,模板变成了边数不超过 5 的最短路径

      所以这句 UQL 的目的是 “查找 1 条从 Chen** 出发的、到达 UUID 为 115 的节点的最短路径,要求边数不超过 5,返回这条路径中的所有点、边的全部属性”:

      最短路径体现的是两点之间最直接的联系。一般情况下,路径越短,路径中数据的关联性就越大,路径就越值得被研究。但也正因如此,真实世界中的某些实体会故意将自身隐藏在很长(20~30步)的数据链路末端。为了能挖掘出这类可疑实体,要求数据库拥有超深遍历的能力和快速响应的高性能。

      常用运算

      UQL 查到点、边、路径以后,可以进行很多运算。这些运算是通过函数、子句进行的。运算的种类非常多,具体可参看 UQL 文档。在本节中仅挑选一些较常用的运算进行介绍。

      去重

      修改前面链路一节中的第一个例子,将 10 条路径的终点去重后再返回(8 个商家):

      n({_id == "60017791850"}).e().n(as payee) limit 10
      with distinct payee as payeeDedup
      return payeeDedup{*}
      
      • 操作符 distinctpayee 进行去重
      • 去重操作写在 with 子句中
      • UQL 语句按顺序执行,limit 10 位于 with 之前则先查找 10 条结果,再进行去重

      计数

      修改上面的示例,计算去重后的终点个数:

      n({_id == "60017791850"}).e().n(as payee) limit 10
      with count(distinct payee) as cardinality
      return cardinality
      
      • 函数 count()distinct payee 进行数量统计,count()distinct 经常这样套用在一起

      排序

      修改前面查找边一节中的第一个例子,将找到的边按转账金额降序排列后再返回:

      find().edges({_from == "60017791850"}) as payment limit 10
      order by payment.tran_amount desc
      return payment{*}
      
      • 子句关键词 order bypayment 按照其属性 tran_amount 进行排序
      • 位于子句 order by 末尾的关键词 desc 表示 “降序”

      分组

      修改前面链路一节中的第二个例子,将找到的三步路径按照路径中的第三个点进行分组,并统计每组中的路径数量:

      n({_id == "60017791850"}).e({tran_amount > 70000})[2].n(as third).e({tran_amount > 70000}).n() limit 10
      group by third
      return table(third.cust_name, count(third))
      
      • 为了在模板中体现第三个点,将原先的结构 n().e()[3].n() 改写为 n().e()[2].n().e().n()
      • 子句关键词 group bythird 进行分组
      • 函数 count() 对每组中的 third 进行数量统计
      • 函数 table()third.cust_namecount(third) 合并到一个表格中,使结果更加一目了然
      请完成以下信息后可下载此书
      *
      公司名称不能为空
      *
      公司邮箱必须填写
      *
      你的名字必须填写
      *
      你的电话必须填写
      *
      你的电话必须填写