MongoDB 如何对嵌套子文档分组去重计算

MongoDB的数据存储使用嵌套结构非常普遍,它通过嵌套子文档,实现一对多的关联关系。但嵌套结构分组进行子文档去重数据计算时,常需要将嵌套结构提升为扁平结构,再通过 $group 聚集运算来实现。
下面以集合 fund 为例说明,按 id 分组获取 shares、trade 子文档下的不重复 fundcode 数量。具体数据如下:

{
    "id" : 1,
    "shares" : [
        {
            "fundcode" :   "000001",
            "lastshares" : 1230.2,
            "agencyno" :   "260",
            "netno" : "260"
        },
        {
            "fundcode" :   "000002",
            "lastshares" : 213124,
            "agencyno" :   "469",
            "netno" :   "001"
        },
        {
            "fundcode" :   "000001",
            "lastshares" : 10000.8,
            "agencyno" : "469",
            "netno" :   "002"
        }
    ],
    "trade" : [
        {
            "fundcode" :   "000001",
            "c_date" : "20180412",
 
            "agencyno" :   "260",
            "netno" :   "260",
"bk_tradetype" : "122",
            "confirmbalance" :   1230.2,
            },
        {
            "fundcode" :   "000002",
            "c_date" : "20180506",
            "agencyno" :   "469",
            "netno" :   "001",
            "bk_tradetype" :   "122",
            "confirmbalance" :   213124,
        },
        {
            "fundcode" :   "000003",
            "c_date" : "20190502",
            "agencyno" :   "469",
            "netno" :   "002",
            "bk_tradetype" :   "122",
            "confirmbalance" :   10000.8,
            "netvalue" : 1,
          }
    ]
}
  …….

用 MongoDB 脚本实现思路,用 $group 按 id 分组,提取文档下的 fundcode,再用 $unwind 分别按 shares、trade 拆解成对象,将数据扁平化,再分组时用 $addToSet 去重处理,然后对 shares、trade 下的 fundcode 计数,实现过程比较麻烦。

使用集算器, 可将 trade 子文档整理成序表后,再分组去重计数,实现比较容易。
集算器安装包可去 润乾网站 下载,运行时需要一个授权,免费版本就够用。

我们将上述事例实现步骤:
1.    在集算器中编写脚本 fund.dfx

A B
1 =mongo_open("mongodb://localhost:27017/local") / 连接 MongDB 数据库
2 =mongo_shell(A1,"fund.find(,   {_id: 0})").fetch() / 条件过滤集合 fund 数据
3 =A2.trade.conj(if (#==1, t=~.fname(), t=t^~.fname())).id() / 取 trade 子文档下的字段交集
4 =A2.run(  trade=trade.new( ${A3.(A3(#)).concat@c()})) / 将共有字段数据转换成序表
5 =A2.group(id;   ~.conj(shares.(fundcode)).id().count(): shares,~.conj(trade.(fundcode)).id().count():trade) / 按 id 分组,对 shares, trade 下的 fundcode 去重计数
6 >A1.close() / 关闭连接
  2.      调试执行一下:可看到 A3 格值为:
A3 Member
agencyno
bk_tradetype
……
  3.      执行脚本返回结果:
A5 id shares  trade
1.0 4 6
2.0 3 3

由于子文档 trade 下面的字段结构不一致,需要转换成结构化的序表结构,有利于后面的数据处理。
集算器提供了 JDBC 接口,脚本 fund.dfx 很容易集成到 Java 中:

public static void doWork() {
    Connection con = null;
    java.sql.Statement st;

    try{
        Class.forName("com.esproc.jdbc.InternalDriver");
        con = DriverManager.getConnection("jdbc:esproc:local://"); 
        // 调用脚本 fund.dfx
        st=con.createStatement(); 
        ResultSet rst = st.executeQuery("call fund");
        System.out.println(rst);
    }catch(Exception e){
        System.out.println(e);
    }finally{
        // 关闭连接
        if (con!=null) {
            try {
                con.close();
            }catch(Exception e) {
                System.out.println(e);
            }
        }  
    }
}

对于嵌套子文档下的字段不一致时,可根据需要,使用并集、交集等方式动态获取想要的字段,当然也可以自定义字段 (这样更省事),对数据进行格式化处理,转换成序表结构,这样方便后面的数据处理。集算器与 JAVA 集成的进一步信息可参考:《Java 如何调用 SPL 脚本》。