在上篇笔记中,为车辆信息表、车辆耗损表以及车辆营收表插入了一些数据。之后便是查询了,重点也在查询……按照之前定好的数据结构,如果查询mongodb document的最外层比较简单,但是我们的重点应该是FormItems表单项集合中的表单项,对他们进行查询、统计等。针对插入的数据,我写了一个查询:查询车辆信息表中主键ID为1的车辆的所有耗损记录。分析一下,因为所有的表(上面的三张表都放在FormInstace表中)都在一个真实的表中,所以我们首先要确定FormId,之后是怎么确定是哪辆车,车辆耗损表中有一个字段冗余着车辆信息(不清楚的可以翻一下前面的笔记),那么查询语句就出来了:

db.getCollection('FormInstace').find({FormId:'507048044944692000','FormItems':{$elemMatch:{'key':'1572493552001','value.id':"1"}}})

  这个查询挺简单的,确定了表之后就是一个等值查询,但是现在却因为我们的数据存储结构用到了mongodb中的$elemMatch。这还是最简单的查询,之后要是有复杂的查询统计更是不敢想象(事实上也确实如此:现在的项目中对表单中的字段进行查询统计非常困难)……那么我们能不能将FormItems表单项集合中的字段拿到document的最外层,和之前的关系型数据一样,如下图:

  答案是可以,mongodb有很多聚合管道,使用不同的组合可以帮我们实现上述效果,由于mongodb的版本不同以及语句的复杂程度,写了好几版本,下面一一列出。

  第一版

//    第一版:受限于下面的几个聚合管道,可以在3.4.4及以上使用
// $addFields New in version 3.4.
// $arrayToObject New in version 3.4.4.
// $replaceRoot New in version 3.4.
db.getCollection('FormInstace').aggregate([
{
$match: {
"FormItems.key": { $ne: null }
}
},
{
$addFields: {
FormValueObj: {
$arrayToObject: {
$map: {
input: "$FormItems",
as: "field",
in: [ "$$field.key", "$$field.value" ]
}
}
}
}
},
{
$addFields: {
"FormValueObj._id": "$_id",
"FormValueObj.ExtendData":"$ExtendData",
"FormValueObj.CreateUserId": "$CreateUserId",
"FormValueObj.CreateUserName": "$CreateUserName",
"FormValueObj.CreateDate":"$CreateDate",
"FormValueObj.LastModifyDate":"$LastModifyDate",
"FormValueObj.FormId": "$FormId",
"FormValueObj.FormVersion":"$FormVersion"
}
},
{
$replaceRoot: {
newRoot: "$FormValueObj"
}
}
]);

  看一下执行结果(用的是Robo 3T 1.2.1)

  看到这样的执行结果还是很满意的,这不正是我们想要的嘛!但是当我调整上图中的数值时(修改为500),却报错了(⊙o⊙)…

  我去这是什么情况,我都怀疑我的语句写的有问题了,可是为什么第一次没问题-_-||,我在另一台电脑中测试时(操作方式相同,只是版本不同,版本是1.3),还出现过“Cannot convert to an aggregation if ntoreturn is set”这样的错误,网上查到了这篇文章https://github.com/Studio3T/robomongo/issues/1529 (这里只是记录一下,有时间再看吧)。我还有另一个GUI,便试了一下:

  至此我觉得语句应该没问题。最后的数据列是合并了所有表(逻辑上)中的所有字段和公共字段。

  第二版

//    第二版:受限于下面的几个聚合管道,可以在3.4.4及以上使用
// $addFields New in version 3.4.
// $arrayToObject New in version 3.4.4.
// $objectToArray New in version 3.4.4.
// $concatArrays New in version 3.2.
// $replaceRoot New in version 3.4.
db.getCollection('FormInstace').aggregate([
{
$match: {
"FormItems.key": { $ne: null }
}
},
{
$addFields: {
TempFormValueObj: {
$arrayToObject: {
$map: {
input: "$FormItems",
as: "field",
in: [ "$$field.key", "$$field.value" ]
}
}
}
}
},
{
$addFields: {
TempFormValues: {
$objectToArray : "$TempFormValueObj"
}
}
},
{
$addFields:{
FormValueObj:{
$arrayToObject:{
$map:{
input:{
$concatArrays: [ "$TempFormValues", {$objectToArray : "$$ROOT"} ]
},
as: "kv",
in: ["$$kv.k","$$kv.v"]
}
}
}
}
},
{
$replaceRoot: {
newRoot: "$FormValueObj"
}
},
{
$project: {
FormItems:0,
TempFormValueObj:0,
TempFormValues:0
}
}
]);

  在上一版中手动拼接了所有的公共字段,不是特别方便,这一版本中去掉了

  第三版

//    第三版:受限于下面的几个聚合管道,可以在3.6及以上使用
// $addFields New in version 3.4.
// $arrayToObject New in version 3.4.4.
// $replaceRoot New in version 3.4.
// $mergeObjects New in version 3.6.
db.getCollection('FormInstace').aggregate([
{
$match: {
"FormItems.key": { $ne: null }
}
},
{
$addFields: {
FormValueObj: {
$arrayToObject: {
$map: {
input: "$FormItems",
as: "field",
in: [ "$$field.key", "$$field.value" ]
}
}
}
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ "$FormValueObj", "$$ROOT" ] } }
},
{
$project: {
FormItems:0,
FormValueObj:0
}
}
]);

  这一版代码最精简,但是依赖的mongodb的版本比较高,要3.6+

  现在我们整理完了数据,再看一下上面的查询,对比一下变化

  就是在最后添加一个筛选管道,结果和最上面的结果是一样的都是58条。这里你可能会发现几个问题:

  1、查询表单中的表单项都是硬编码的数字(例如“1572493552001”),可读性太差了;

  2、查询时间有点长(图片中有执行时间);

  3、写一个简单的查询,这代码也太多了

  ……

  对上面的问题,解释一下

  1、在实际的界面中的筛选字段都是拖拉拽设计的,用户是感知不到的,“1572493552001”只是一个桥梁

  2、查询全部的时间是有点长,好吧!不是有点是很长……如果业务需求需要获取所有的数据时,就不要用这种方式了,麻烦你换一种方式吧!我想说的是实际的场景中都是分页加载,这时时间还是可以接受的,下面是获取30条的截图,时间0.114s

  3、代码有点多的问题,其实你可以将上面整理数据的代码作为筛选的前置条件,只要是查询都添加这段代码;还有一种处理方式就是先创建一个视图(mongodb 3.4+支持视图,https://docs.mongodb.com/manual/core/views/)

db.createView("FormInstaceView","FormInstace",[
{
$match: {
"FormItems.key": { $ne: null }
}
},
{
$addFields: {
FormValueObj: {
$arrayToObject: {
$map: {
input: "$FormItems",
as: "field",
in: [ "$$field.key", "$$field.value" ]
}
}
}
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ "$FormValueObj", "$$ROOT" ] } }
},
{
$project: {
FormItems:0,
FormValueObj:0
}
}
]);

  之后你就可以向下面一样查询了

  结果和上面的没有什么区别,但是你不要将视图当作mongodb中的collection使用,这样是查不到数据的

db.getCollection('FormInstaceView').find({FormId:"507048044944692000","1572493552001.id":1})

  这篇笔记还是够长的,一大推代码……就到这里了。

表单生成器(Form Builder)之mongodb表单数据——整理数据的更多相关文章

  1. 表单生成器(Form Builder)之mongodb表单数据查询——统计查询求和

    上一篇笔记仅是记录了一下简单的关联查询,根据笔记中的场景:将某一车辆关联的耗损记录全部放在了一个字段当中.不知道现在中有没有这种场景,我们的应用中没有类似的场景,可能我们更关注的是某车辆的总耗损金额和 ...

  2. 表单生成器(Form Builder)之mongodb表单数据查询——关联查询

    这一篇接着记录一下查询相关的操作.想象一下,如果想要在一张表格中展示某些车辆的耗损和营收情况,我们该怎么处理.车辆.耗损.营收各自存储在一张表中,耗损和营收中冗余了车辆信息……我们便想到了关联查询.m ...

  3. 表单生成器(Form Builder)之mongodb表单数据查询——返回分页数据和总条数

    上一篇笔记将开始定义的存储结构处理了一下,将FormItems数组中的表单项都拿到mongodb document的最外层,和以前的关系型数据类似,之不过好多列都是动态的,不固定,不过这并没有什么影响 ...

  4. 表单生成器(Form Builder)之伪造表单数据番外篇——随机车辆牌照

    前几天记录了一下表单生成器(Form Builder)之表单数据存储结构mongodb篇,之后便想着伪造一些数据.为什么要伪造数据呢?说来惭愧,因为拖拉拽设计表单以及表单对应的列表的PC端和移动端该显 ...

  5. 表单生成器(Form Builder)之伪造表单数据mongodb篇

    这篇文章终于回到了正轨:为mongodb伪造数据.之前的随机数.随机车牌照.随机时间还有这篇笔记中的获取指定长度的中文字符串,都是为这篇笔记做准备.看一下我们的准备(基础代码) // 1.获取指定范围 ...

  6. 表单生成器(Form Builder)之伪造表单数据番外篇——指定范围随机时间

    为了伪造一些尽量真实的假数据,也真是够费劲的.上一篇笔记记录了一下获取一个随机车辆牌照,这篇笔记记录一下怎么获取一个随机时间.这篇就不说那么多废话了,直接上代码 // 获取指定范围的随机数 var g ...

  7. 表单生成器(Form Builder)之表单数据存储结构mongodb篇

    从这篇笔记开始,记录一下表单生成器(Form Builder)相关的一些东西,网上关于他的介绍有很多,这里就不解释了. 开篇说一下如何存储Form Builder生成的数据.

  8. Ext JS4 学习笔记之发送表单(Form)时也将表单下的表格(Grid)数据一同发送的方法

    Ext JS4 学习笔记之发送表单(Form)时也将表单下的表格(Grid)数据一同发送的方法 昨天在开发的时候遇到个小问题,就是如何将Grid的内容与Form一起发送到服务器端.默认情况下,表单(F ...

  9. 表单组件 form fastadmin(生成表单元素)

    Form组件 定义文件位置: /extend/fast/Formphp 通用参数 $name 通常为我们组件的名称(name属性值),我们在后台接收时可以通过这个名称来获取到它所对应的值 $value ...

随机推荐

  1. [TimLinux] Python3 Coverity zeep/SOAP 库使用示例

    废话不多说,上代码: # 基于Coverity的示例 from zeep import Client from zeep.wsse.username import UsernameToken conf ...

  2. LightOJ 1344 Aladdin and the Game of Bracelets

    It's said that Aladdin had to solve seven mysteries before getting the Magical Lamp which summons a ...

  3. (全国多校重现赛一)D Dying light

    LsF is visiting a local amusement park with his friends, and a mirror room successfully attracts his ...

  4. JVM系列三(垃圾收集器).

    一.概述 1. 哪些内存需要回收 上篇文章 我们介绍了 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程而生,随线程而灭,在这几个区域内就不需要过多考虑回收的问题 ...

  5. 面试BAT前先搞定这18道MySQL经典面试题(含答案解析)

    一.MySQL的复制原理以及流程 (1)复制基本原理流程 1. 主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中: 2. 从:io线程——在使用start ...

  6. Ansible-上部

    Ansible概述 Ansible是一个配置管理系统configuration management systempython 语言是运维人员必须会的语言ansible 是一个基于python 开发的 ...

  7. GHOST CMS - 创建自定义主页 Creating a custom home page

    创建自定义主页 Creating a custom home page 为你的网站创建一个自定义的主页是一个让你从人群中脱颖而出的好方法,并把你自己独特的印记存放在你的网上.本教程向您展示了如何在Gh ...

  8. 《CSAPP》实验二:二进制炸弹

    二进制炸弹是第三章<程序的机器级表示>的配套实验,这章主要介绍了x64汇编,包括:操作数的表示方式,数据传送指令,算术和逻辑指令,控制流跳转指令,过程(procedure)的实现与运行时栈 ...

  9. linux之寻找男人的帮助,man和info,

    1.在linux下寻求帮助是一个很好的习惯,幸运的是系统提供了帮助的命令man和info,由于linux指令很多,记忆起来简直麻烦,比如以a开头的指令有100条,linux命令算起来得几千条,记忆却是 ...

  10. [译]C# 7系列,Part 7: ref Returns ref返回结果

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/12/12/c-7-series-part-7-ref-returns/ 背景 有两种方法可以将一个值传 ...