1. 衡石 HQL
衡石系统中自定义的计算过程描述语言为 Hengshi SENSE Query Language (HQL) ,用于衡石系统的前后端交互、API 数据请求中。
1.1. 语法规定
使用统一的 JSON 来描述各个计算过程. HQL 由多层调用的 HE (Hengshi Expression) 组成。下面是单个 HE 的语法结构。
| HE 节点名 | 类型 | 是否必须 | 描述 | 
|---|---|---|---|
| uid | STRING | 否 | 本节点的全局唯一标识符 | 
| kind | STRING | 是 | 节点类型,见HE状态说明 | 
| op | OBJECT | 是 | 不同的 HE 类型,对应不同的节点定义,见HE状态说明 | 
| args | HE 数组 | 否 | 当 kind 为 functions的时候,此项为必须的 | 
| type | STRING | 否 | 本节点输出的数据类型,可以是 string, number, integer, date, time, json, bool 中的一种 | 
| dataset | OBJECT | 否 | 本节点所在的数据集 id, 一般在 kind 为 field 的时候会指定字段所属的数据集 | 
| direction | STRING | 否 | 可选值为 asc 或者 desc,本节点用户排序的时候可以指定该字段,不指定的话,默认为 asc | 
| window | OBJECT | 否 | 指定本节点计算的窗口,只有 kind 为 function 时,该字段的值才有效,见HEWindow窗口说明 | 
| filter | HE 数组 | 否 | 计算的过滤条件,一般用户聚合计算的过滤 | 
1.1.1. HE 节点类型说明
| 节点类型 | 节点描述 | 类型对应的 op | 
|---|---|---|
| field | 描述字段 | 字段名 | 
| function | 描述函数 | 函数名 | 
| reference | 描述引用字段,一般用户图表的定义中 | 引用的名字 | 
| constant | 描述常量 | 常量的值 | 
| formula | 描述自定义的 HE 表达式 | 表达式的定义 | 
| array | 描述数组 | 数组 | 
| attr | 描述用户属性 | 用户属性的名字 | 
| param | 描述应用参数 | 应用参数的名字 | 
| dataset | 描述数据集 | 数据集的 id | 
1.1.2. HE Window 窗口说明
| 窗口关键字 | 类型 | 描述 | 
|---|---|---|
| partition | HE 数组 | 计算过程的划分依据,对应 sql 中的 partition by 关键字 | 
| order | HE 数组 | 计算过程的排序依据,对应 sql 中的 order by 关键字 | 
1.1.3. 示例1:描述字段
kind 的值是 field, op 的值是字段名, dataset 的值是字段所在的数据集 id。下面的例子描述的是来自数据集 1 的 "test" 字段
{
   "kind": "field",
   "op": "test",
   "dataset": 1
}
1.1.4. 示例2:描述常量
kind 的值是 constant, op 的值是常量值, type 的值是常量的类型。type 为非必需的。 下面的例子描述的是字符串类型常量值 'test'
{
   "kind": "constant",
   "op": "'test'"
}
下面的例子描述的是时间类型的常量值 '2000-01-01'
{
   "kind": "constant",
   "op": "'2000-01-01'",
   "type": "date"
}
1.1.5. 示例3:描述函数
kind 的值是 function, op 的值是函数名或者操作符, args 是函数参数数组。 下面的例子是描述一个加法操作,数据集 2 的字段 salesNum + 2000。
{
   "kind": "function",
   "op": "+",
   "args": [
        {
             "kind": "field",
             "op": "salesNum",
             "dataset": 2
        },
        {
             "kind": "constant",
             "op": 2000
        }
   ]
}
下面的例子是描述聚合函数 sum 的使用,计算 数据集 2 的 salesNum 字段列的求和。
{
   "kind": "function",
   "op": "sum",
   "args": [
        {
             "kind": "field",
             "op": "salesNum",
             "dataset": 2
        }
   ]
}
下面的例子是描述窗口函数 avg 的使用,以 数据集 2 的 location 字段为分组,计算 数据集 2 的 salesNum 字段列的平均值。
{
   "kind": "function",
   "op": "avg",
   "args": [
        {
             "kind": "field",
             "op": "salesNum",
             "dataset": 2
        }
   ],
   "window": {
        "partition": [
             {
                 "kind": "field",
                 "op": "location",
                 "dataset": 2
             }
        ]
   }
}
下面的例子是描述窗口函数 rank 的使用,根据数据集 2 的 saleNum 字段求和的降序对数据集 2 的 location 字段排名。
{
   "kind": "function",
   "op": "avg",
   "args": [
        {
             "kind": "field",
             "op": "location",
             "dataset": 2
        }
   ],
   "window": {
        "order": [
             {
                 "kind": "function",
                 "op": "sum",
                 "args": [{
                     "kind": "field",
                     "op": "saleNum",
                     "dataset": 2
                  }],
                 "direction": "desc"
             }
        ]
   }
}
下面的例子是描述带 filter 条件的聚合函数 sum 的使用,计算所有 location 为"北京"或者"上海"的条件下, 数据集 2 的 salesNum 字段列的求和。
{
   "kind": "function",
   "op": "sum",
   "args": [
        {
             "kind": "field",
             "op": "salesNum",
             "dataset": 2
        }
   ],
   "filter": [
        {
             "kind": "function",
             "op": "in",
             "args":[
                {
                     "kind": "field",
                     "op": "location",
                     "dataset": 2
                },
                {
                     "kind": "constant",
                     "op": ["北京", "上海"]
                }
             ]
        }
   ]
}
1.1.6. 示例4:描述表达式
kind 的值是 formula, op 的值是表达式。函数表达式的使用请参照函数文档。 HE 函数的描述都可以转成HE 表达式,例子中也对应了上面函数描述的例子。下面是HE 以及对应 表达式表述的定义。
| 用途 | HE | 表达式 | 
|---|---|---|
| 字段 | {“kind”: “field”, “op”:”字段名”} | {字段名} | 
| 数据集的字段 | {“kind”: “field”, “op”:”字段名”, "dataset":"数据集id"} | .{字段名} | 
| 用户属性 | {“kind”: “attr”, “op”:”用户属性名”} | {{$用户属性}} | 
| 应用参数 | {“kind”: “param”, “op”:”应用参数名”} | {{%应用参数}} | 
| 函数 | {“kind”: “function”, “op”:”函数名”, "args":["arg1", "arg2"]} | 函数名(arg1, arg2) | 
| 窗口函数 | {“kind”: “function”, “op”:”函数名”, "args":["arg1", "arg2"],"window":{"partition":["分组"],"order":["排序"]}} | 函数名(arg1, arg2) over(partition by "分组" order by "排序") | 
| 带过滤函数 | {“kind”: “function”, “op”:”函数名”, "args":["arg1", "arg2"],"filter":["filter1","filter2"]} | 函数名(arg1, arg2) filter(where "filter1" and "filter2") | 
下面的例子描述的是连接数据集 1 的 firstname 列 和 数据集 1 的 lastname 列。
{
   "kind": "formula",
   "op": "concat({{1}}.{firtname}, {{1}}.{lastname})"
}
下面的例子描述的是比较 数据集 1 的 salesNum 字段 和 应用参数的基准值。
{
   "kind": "formula",
   "op": "{{1}}.{salesNum} > {{%基准值}}"
}
下面的例子是描述窗口函数 avg 的使用,以 数据集 2 的 location 字段为分组,计算 数据集 2 的 salesNum 字段列的平均值。
{
   "kind": "formula",
   "op": "avg({{2}}.{salesNum}) over(partition by {{2}}.{location})"
}
下面的例子是描述窗口函数 rank 的使用,根据数据集 2 的 saleNum 字段求和的降序对数据集 2 的 location 字段排名。
{
   "kind": "formula",
   "op": "rank({{2}}.{location}) over(order by sum({{2}}.{salesNum}) desc)"
}
下面的例子是描述带 filter 条件的聚合函数 sum 的使用,计算所有 location 为"北京"或者"上海"的条件下, 数据集 2 的 salesNum 字段列的求和。
{
   "kind": "formula",
   "op": "sum({{2}}.{salesNum}) filter(where in({{2}}.{location}, ['北京','上海']))"
}
1.2. 使用场景1: 图表的定义
在衡石系统中,定义一个图表由两部分组成,一部分是 HE 的定义,用于获取数据;另一部分是格式的定义,用于展示。这里我们只关注用于获取数据的 HE 部分。 下面是一个简单的图表示例,HE 的计算组合在一起,描述了业务:以 region_name 分组,计算组内 region_id 的求和,结果按照 region_name 的升序展示。
{
    "options": {
        "axes": [
            {
                "args": [
                    {
                        "op": "region_name",
                        "kind": "field",
                        "dataset": 7
                    }
                ],
                "op": "group",
                "uid": "u_edbee8adba68e26a_2",
                "kind": "function"
            },
            {
                "op": "sum",
                "kind": "function",
                "args": [
                    {
                        "op": "region_id",
                        "kind": "field",
                        "dataset": 7
                    }
                ],
                "uid": "u_9f886f9cb7bdf1d2_2"
            }
        ],
        "sort": [
            {
                "op": "u_edbee8adba68e26a_2",
                "kind": "reference",
                "direction": "asc"
            }
        ],
        "limit": 1000
    }
}
1.3. 使用场景2: url 过滤
访问衡石系统的共享链接时,可以在共享链接后面添加 url 过滤条件,用于数据的权限控制。这个过滤条件也是由 HE 组成的。 下面这个过滤表示过滤 location 为北京的数据,它会作用于所有的图表上。
where=[{"kind":"formula","op":"{location}='北京"}]
下面这个过滤表示过滤数据集 1 的 location 字段为北京的数据,它会作用于所有与数据集1 由关联的图表上。
where=[{"kind":"formula","op":"{1}.{location}='北京"}]
1.4. 使用场景3: 数据集的新增字段和指标
在衡石系统的数据集里新建字段、新建指标都是通过写 HE 表达式实现的。