DMN的初学者通常会为模型中的每个简单值定义一个单独的变量,例如字符串或数字。但是,将相关值分组到单个变量中通常要好得多,DMN中的FEEL语言有多种方法可以做到这一点:
结构(有时称为上下文)使用名称标识每个组件值。例如,结构Customer可以包含组件Name、BirthDate、TaxID等。变量Customer引用其所有组件;要仅选择一个,请使用点表示法,例如Customer.Name。
列表变量包含一系列值,通常为相同类型。列表项不限于简单类型,也可以是结构甚至嵌套列表。每个列表项不是按名称标识的,而是按其在列表中的位置标识的。例如,myList[1]表示myList中的第一项;myList[-1]表示最后一项。在FEEL中,列表可能包含重复值,并且项目的顺序很重要,因此列表[1,2,3]不等于列表[3,2,1]或列表[1,1,2,3]。
表是结构列表。表的每一行都是相同类型的结构。结构组件命名表的列。因此,例如,CustomerList.Name[3]返回CustomerList中第三个Customer的Name。
将DMN变量定义为结构、列表和表,而不是完全简单的类型,通常可以使您的模型更简单、更强大且更易于维护。
数据类型
Together建模器可以轻松定义结构化数据类型。您只需为每个组件提供名称和类型,如下所示:

创建列表的方法有很多种。最简单的是列表运算符,方括号括起来以逗号分隔的表达式列表,例如[1,2,3]。列表变量的数据类型称为集合,最佳做法是将类型命名为Collection[项类型]。例如,具有IncomeType和MonthlyAmount列的IncomeItems表的数据类型将按如下方式指定:

列表函数
FEEL提供了多种内置函数来操作列表和表格。有些特定于数字和日期/时间列表,但大多数适用于任何列表项类型。示例如下表所示:

过滤器
要从列表或表中选择项目,FEEL提供了筛选运算符,该运算符建模为紧跟在列表或表名称后面的方括号,其中包含筛选表达式(整数或布尔值)。如果为整数,则筛选器返回该位置的项。例如,myList[1]返回myList中的第一项。布尔表达式返回表达式为true的所有项。例如,LoanTable[ratePct<4.5]返回组件ratePct小于4.5的所有LoanTable项目的集合。
布尔筛选器始终返回集合,即使您知道它最多只能返回一个项目。如果每个Customer都有一个唯一的ID,则Customer[id=123456]将返回一个包含一个Customer和Customer[id=123456]的列表。Name返回一个包含一个Name的列表。包含一个项目的列表与项目本身不同。因此,最佳做法是附加筛选器[1]以提取项目值,使返回值键入Text而不是CollectionofText:Customer[id=123456].name[1]。
过滤后的列表或表是列表函数的参数是很常见的,例如count(LoanTable[ratePct<4.5])返回ratePct小于4.5的LoanTable的行数。
如果布尔筛选表达式未找到表达式为true的项目,则返回的值为空列表[],如果您尝试从中提取值,则返回值为null。例如,如果表Customer没有id=123456的条目,则Customer[id=123456]为[]且Customer[id=123456].Name[1]为null。由于此类情况,DMN最近引入了B-FEEL,这是FEEL的一种方言,可以更好地处理null值,您应该在模型中使用B-FEEL。如果没有B-FEEL,将null字符串值与其他文本连接起来将返回null;使用B-FEEL时,空值将替换为空字符串“”。
迭代
FEEL的一个强大功能是能够对列表或表中的所有项目迭代某个表达式。语法为
for [rangevariable] in [listname] return [outputexpression]
此处rangevariable是您选择用于代表列表中的单个item值的名称。例如,如果LoanTable是由lenderName、rate、pointsPct、fees和term指定的抵押贷款产品列表,我们可以迭代每个贷款产品的摊销公式,以获得每个贷款产品的月供:
for x in LoanTable return payment(RequestedAmt*(1+x.pointsPct/100),x.rate/100,x.term)
这里x是范围变量,表示LoanTable的单行,payment是一个BKM,其中包含基于贷款金额、贷款利率和期限的摊销公式,以获得每月抵押贷款付款。
除了对item值的迭代之外,FEEL还提供对item位置的迭代,其语法为
for [rangevariable] in [integerrange] return [outputexpression]
例如
for I in 1..10 return i*i
返回从1到10的整数平方的列表。
测试列表中的成员资格
FEEL提供了多种方法来检查某个值是否包含在某个列表中。
somexin[list expression]satisfies[Boolean expression]
如果任何列表项满足布尔测试,则返回true,并且
everyxin[list expression]satisfies[Boolean expression]
仅当所有列表项都满足测试时,才返回true。
in运算符是测试列表中成员身份的另一种方法:
[itemvalue]in[list expression]
如果值在列表中,则返回true。
listcontains()函数做同样的事情:
listcontains([list expression],[value])
你也可以在过滤器上使用count()函数,如下所示:
count([filtered list expression])>0
集合操作
上面描述的成员身份测试是检查列表中是否包含单个值的方法,但有时我们有两个列表,我们想知道listA中的部分或全部项目是否包含在listB中。有时我们想考虑将setA与setB进行比较,其中这些集是列表的重复数据删除和无序版本。如有必要,我们可以使用函数distinctvalues()从列表中删除重复项。
Intersection
如果ListA和listB之间有任何共同的值,则它们相交。要测试交集,我们可以使用表达式
some x in listA satisfies list contains(listB,x)
Containment
要测试listA中的所有项目是否都包含在listB中,我们可以使用every运算符:
every x in listA satisfies list contains(listB,x)
Identity
仅当列表在每个位置值处都相同时,简单函数listA=listB才返回true。测试去重的无序集是否相同有点棘手。我们可以使用FEELunion()函数来连接和删除重复列表,并结合every运算符:
everyxinunion(listA,listB)satisfies(listcontains(listA,x)andlistcontains(listB,x))
对列表进行排序
我们可以使用FEEL sort()函数对列表进行排序,这很不寻常,因为函数的第二个参数本身就是一个函数。通常,它被内联定义为匿名函数,使用关键字函数,其中两个范围变量代表列表中的任意两个项目,以及这些范围变量的布尔表达式。布尔表达式确定排序列表中哪个范围变量值位于另一个范围变量值之前。例如
sort(LoanTable,function(x,y)x.rate<y.rate)
按贷款利率的递增值对列表LoanTable进行排序。此处,范围变量x和y代表LoanTable的两行,如果rate值较低,则行x在排序列表中位于行y之前。
替换列表项
最后,我们可以使用Together的list replace()函数将列表项替换为另一个列表项,另一个函数将函数作为参数。语法为
List replace([list],[matchfunction],[newItem])
match函数是一个布尔测试,涉及代表原始列表中某个项目的范围变量。例如,EmployeeTable列出每个员工的可用休假天数。当员工休假时,将从之前的AvailableDays中扣除天数,以生成更新的行NewRecord。我们使用match函数选择与另一个输入VacationRequest中的EmployeeId匹配的员工。

现在,我们想将该表中的employee记录替换为NewRecord:
list replace(EmployeeTable,function(x,VacationRequest)x.employeeid=VacationRequest.employeeId,NewRecord)
这里,范围变量x代表EmployeeTable的某一行,我们将替换与Vacation Request中的employeeid匹配的行。所以在这种情况下,我们将用另一个表格行替换一个表格行,但我们也可以使用list replace()来替换简单列表中的一个值。
结论
在实际决策逻辑中,您的源数据通常使用列表和表格来指定。与提取输入数据中的单个项目并一次处理一个项目相比,使用列表和表格一次处理所有项目通常更容易、更快捷。FEEL具有多种功能和运算符,使其变得简单,而Together规则引擎的自动化功能使它们易于执行。
想要更深入地了解列表和表格的用法吗?查看我们的DMN方法和样式培训。