掘金 后端 ( ) • 2024-04-25 22:53

DQL(Dimentinal Query Language)语言是润乾公司提出的一种面向OLAP的多维数据查询语言,可以将复杂的主子表很简单的整合为一个大宽表来查询。具体介绍可以参考乾学院的文章

告别宽表,用 DQL 成就新一代 BI - 乾学院

详细语法分析参见dql.md

在Nop平台中,NopORM也提供了类似的查询方式,可以通过QueryBean来查询多个表的数据,而不需要关心表之间的关联关系。

QueryBean表达多表查询

比如对于下面的表结构

  NopAuthGroup <--- NopAuthGroupDept ---> NopAuthDept

如果要查询NopAuthGroup表并同时返回每个分组关联的部门的总数,类似于下面的SQL语句

 select o.groupId, o.name, (select count(g.deptId) from NopAuthUserDept g where g.groupId= o.groupId) as deptCount
 from NopAuthGroup o

可以通过QueryBean来实现

QueryBean query = new QueryBean();
query.setSourceName(NopAuthGroup.class.getName());
query.fields(mainField("groupId"), mainField("name"), subField("deptMappings", "deptId").count().alias("deptCount"));
query.addOrderField("name", true);

List<Map<String, Object>> list = ormTemplate.findListByQuery(query);

上面的代码中,mainField表示主表字段,subField 表示子表字段,subField的第一个参数是关联的字段名,第二个参数是要查询的子表上的字段名,count表示统计子表的总数,alias 表示别名。

实际的实现原理不是生成一个复杂SQL,而是在内存中分成多个查询,然后在内存中通过HashJoin来把数据整合为一个大宽表。

select o.groupId, o.name from NopAuthGroup o;

select g.groupId, count(g.deptId) as deptCount from NopAuthUserDept g group by g.groupId;

这样的查询方式可以大大简化复杂的多表查询,提高开发效率。

分页和复合关联属性

QueryBean支持设置offset和limit属性,从而基于主表进行分页查询。此外,因为底层的运行引擎是NopORM,而不是普通的JDBC查询引擎,因此它会自动识别复合属性,并自动展开为表关联关系。

QueryBean query = new QueryBean();
query.fields(mainField("refField.name"), subField("deptMappings","otherRefField.user.name").count().alias("count"));
query.addFilter(FilterBeans.eq("refField.status",1));
query.setOffset(100);
query.setLimit(10);

这里假定主实体上存在名为refFieldto-one关联,而deptMappings是一个to-many关联,otherRefFielddeptMappings 关联的另一个to-one关联。

在类似MyBatis的sql-lib中管理动态构建的QueryBean


<sql-lib>
    <sqls>
        <query name="queryGroupWithDeptCount" sqlMethod="findList">
            <source>
                <sourceName>NopAuthUser</sourceName>
                <fields>
                    <field name="groupId"/>
                    <field name="name"/>
                    <field owner="deptMappings" name="deptId" aggFunc="count" alias="deptCount"/>
                </fields>
                <filter>
                    <c:if test="${someCondition}">
                        <eq name="status" value="${status}"/>
                    </c:if>
                </filter>
                <orderBy>
                    <field name="name" asc="true"/>
                </orderBy>
            </source>
        </query>
    </sqls>
</sql-lib>

<query>的source段就是一段xpl模板语言,它生成QueryBean的一个XML表示。在生成过程中,可以使用Xpl标签来实现进一步的抽象。

通过sqlMethod我们可以选择使用findListfindFirst或者exists等方法,分别用于返回列表数据、第一条数据或者判断是否存在数据。

通过Underscore帮助类对数据进行加工

Underscore.java工具类提供了一些针对集合对象的帮助函数,比如leftjoinMerge可以实现两个列表的join合并。

 Underscore.leftjoinMerge(listA,listB, leftPropName, rightPropName, Arrays.asList(fldB1,fldB2));

以上函数相当于

select listA.*, listB.fldB1,listB.fldB2
from listA, listB
where listA.leftProp =  listB.rightProp

集成tablesaw

nop-tablesaw模块集成了tablesaw计算包,它的功能类似于python中的pandas库,可以完成一系列针对列表数据的统计计算。

Table table = DataSetHelper.dataSetToTable(dsName, dataSet);
table.numberColumn("count").sum();

NopORM查询得到的IDataSet数据集可以直接被转换为tablesawTable接口,然后就可以调用select/pivot/summarize/count等一系列操作函数。

NopORM内部将所有的数据集合对象都统一封装为IDataSet接口,因此并不对外暴露ResultSet等泄露实现细节的接口。NopORM底层可以不运行在JDBC之上。