目录

MongoDB如何查询出所有二维数组中的内容并返回相应的数组

MongoDB聚合查询及array相关操作

场景简介

一个在学校使用的公众号,公众号需要家长在微信中为自己的孩子进行注册然后进行一些后续的操作。这里我们要考虑到多位家长绑定同一个孩子和一个家长绑定多个孩子的场景。

首先一步要做的就是要把家长的登录信息和填写的孩子信息进行存储,这里我们使用的是MongoDB,肯定优先会把所有信息存储到一个文档中。文档大致结构如下

  • 整体信息是一个文档,可以看做一个文档为一个家庭
  • 父母可能为多个,所以使用内嵌数组存储
  • 孩子同样可能为多个,同样使用内嵌数组来存储

MongoDB文档结构大概如下

{
        _id: ObjectId("5f06ae15cf20bc8fea5cf982"),
        patients: [
            {
                p_id: "nxM81594361333",
                name: "cofl",
                sex: "男",
                tel: "12222222222",
                birth_year: "2000",
                school_id: "B0FFGAPLTR",
                school_name: "xxxxxxx",
                grade_id: "g01",
                grade_name: "一年级",
                class_id: "c01",
                class_name: "一班",
                add_time: NumberLong(1594361333)
            },
            {
                p_id: "nxM81594363774",
                name: "cofl111",
                sex: "男",
                tel: "12222222333",
                birth_year: "2000",
                school_id: "B0FFGAPLTR",
                school_name: "xxxxxxxx",
                grade_id: "g01",
                grade_name: "一年级",
                class_id: "c01",
                class_name: "一班",
                add_time: NumberLong(1594363774)
            },
            {
                p_id: "nxM81594365730",
                name: "cofl222",
                sex: "男",
                tel: "19999222233",
                birth_year: "2000",
                school_id: "xxxxxxx",
                school_name: "xxxxxxx",
                grade_id: "g01",
                grade_name: "一年级",
                class_id: "c01",
                class_name: "一班",
                add_time: NumberLong(1594365730)
            }
        ],
        parents: [
            {
            	   openid: "xxxxxxxxxxxx",
                nickname: "cofl",
                sex: 1,
                city: "朝阳",
                province: "北京",
                country: "中国"
            }
        ]
    }

具体遇到的问题

  1. 父母及孩子都是内嵌数组,所以需要对MongoDB内嵌array的增删改查
  2. 在某些场景需要将所有的学生(孩子)信息进行列表展示,所以我们需要将学生信息查询出来
    • 首先我们排除直接查询所有文档然后在进行遍历组合学生list的方案
    • 再次我们希望能通过sql直接查询到所有学生并可以进行排序,以及支持页面分页展示。

请开始你的表演

项目使用golang进行的开发,所以引用的部分代码均为golang代码

  1. MongoDB内嵌数组的的操作。 所有操作:mongodb 官方文档

    添加插入

    插入—当父母初次添加孩子信息时,我们需要将孩子信息添加到patients内嵌数组中,此时我们可以使用官方文档中的$push

    filter = bson.M{"parents.openid": openid}
    update = bson.M{"$push": bson.M{"patients": patient}}
    option := options.Update()
    result, err := db.UpdateOne(ctx, filter, update, option)
    

    `` 这样我们就可以在文档patients数组中添加一个孩子的信息。

    在MongoDB的官方文档中我们还发现$addToSet

    文档描述 :The $addToSet operator adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array.

    我个人理解就是 $addToSet会看数组中有没有要追加的内容,如果有就不再追加,如果没有就进行追加。有时间可以自己尝试一下。

    更新

    更新修改—当父母需要更新修改孩子的信息时,我们需要更新文档中patients内嵌数组中的某一条信息,此时我们可以使用$set

    filter = bson.M{"parents.openid": openid, "patients.p_id": patient.PId}
       
    //如果是更新整个文档使用bson.M{"patients.$": patient}
    update = bson.M{"$set": bson.M{"patients.$": patient}}
       
    //如果是更新文档中的某个值可以使用bson.M{"patients.$.name": "cofl888"}
    update = bson.M{"$set": bson.M{"patients.$.name": "cofl888"}}
       
    option := options.Update()
    result, err := db.UpdateOne(ctx, filter, update, option)
    

    `` 这样我们就可以实现内嵌数组中一条信息的修改更新。

    需要注意的是目前我暂时只能做到单个属性修改或者整条数据更新,没有找到可以只更新内嵌文档中一条数据的某几个字段的方法,如有知道的还请告知补充

  2. 通过使用MongoDB的聚合查询来实现学生列表的查询。 同样给出官方文档,方便查看

    MongoDB的聚合查询重点是pipeline(管道)操作,此次我们主要用到了$unwind ,首先先看下具体的代码实现

    db, ctx := mongoDb("user")
    pipeline := []bson.M{bson.M{"$unwind": "$patients"},
        bson.M{"$project": bson.M{"_id": 1, "parents": 1, "patient": "$patients"}},
        bson.M{"$sort":bson.M{"patient.add_time":-1}},
        bson.M{"$skip": (page - 1) * limit},
        bson.M{"$limit": limit}}
    
    cur, err1 := db.Aggregate(ctx, pipeline, option)
    

    ``

    $unwin的官方文档解释

    Deconstructs an array field from the input documents to output a document for each element. Each output document is the input document with the value of the array field replaced by the element.

    大概意思:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。

    重点看下上面查询的几个要点

    • $unwind 实现内嵌数组拆解
    • bson.M{“$project”: bson.M{"_id”: 1, “parents”: 1, “patient”: “$patients”}}, 实现控制查询哪些字段;“patient”: “$patients"实现了拆解内嵌数组后字段修改名字的作用 $limit和$skip是我们很常用的分页用到的,这里要特别说明一下在这里使用时,他们两个的先后顺序很重要,上面代码会先进性skip然后在进行limit,如果换成下面的顺序,则会先进行limit,然后进行skip就会导致查询不到数据
    pipeline := []bson.M{bson.M{"$unwind": "$patients"},
        bson.M{"$project": bson.M{"_id": 1, "parents": 1, "patient": "$patients"}},
        bson.M{"$sort":bson.M{"patient.add_time":-1}},
        bson.M{"$limit": limit}}
        bson.M{"$skip": (page - 1) * limit},
    

    ``

    看下实际输出结果

    原本是一个文档,查询结果变成了一个数组,仔细看对比一下原文档,我们会发现其实是把patients内嵌数组的每一项和文档其他项组合成了一个新文档,数组数量就是patients内嵌数组的item数量,这样正好就是我们想要的学生列表的形式。

    [
      {
        "id": "5f06ae15cf20bc8fea5cf982",
        "parents": [
          {
            "subscribe": 0,
            "openid": "xxxxxxx",
            "nickname": "cofl",
            "sex": 1,
            "city": "朝阳",
            "province": "北京",
            "country": "中国"
          }
        ],
        "patient": {
          "p_id": "nxM81594365730",
          "name": "cofl222",
          "sex": "男",
          "tel": "19999222233",
          "birth_year": "2000",
          "school_id": "B0FFGAPLTR",
          "school_name": "东交民巷小学马坊分校",
          "grade_id": "g01",
          "grade_name": "一年级",
          "class_id": "c01",
          "class_name": "一班",
          "add_time": 1594365730
        }
      },
      {
        "id": "5f06ae15cf20bc8fea5cf982",
        "parents": [
          {
            "subscribe": 0,
            "openid": "xxxxxxxxx",
            "nickname": "cofl",
            "sex": 1,
            "city": "朝阳",
            "province": "北京",
            "country": "中国"
          }
        ],
        "patient": {
          "p_id": "nxM81594363774",
          "name": "cofl111",
          "sex": "男",
          "tel": "12222222333",
          "birth_year": "2000",
          "school_id": "B0FFGAPLTR",
          "school_name": "xxxxxxx",
          "grade_id": "g01",
          "grade_name": "一年级",
          "class_id": "c01",
          "class_name": "一班",
          "add_time": 1594363774
        }
      },
      {
        "id": "5f06ae15cf20bc8fea5cf982",
        "parents": [
          {
            "subscribe": 0,
            "openid": "xxxxxxx",
            "nickname": "cofl",
            "sex": 1,
            "city": "朝阳",
            "province": "北京",
            "country": "中国",
          }
        ],
        "patient": {
          "p_id": "nxM81594361333",
          "name": "cofl",
          "sex": "男",
          "tel": "12222222222",
          "birth_year": "2000",
          "school_id": "B0FFGAPLTR",
          "school_name": "xxxxxx",
          "grade_id": "g01",
          "grade_name": "一年级",
          "class_id": "c01",
          "class_name": "一班",
          "add_time": 1594361333
        }
      }
    ]
    

    ``

    总结

    • MongoDB作为比较常用的非关系型数据,文档结构很灵活。在数据库结构设计的时候我们可以有更多的可能性
    • 个人觉得内嵌数组对于MongoDB来说也比较重要,能够有效的扩展文档结构,刚开始使用的时候可能会直接查处整个文档然后修改完在更新回去…(狗头),MongoDB发展到现在功能已经和完善,各个语言的driver也都比较成熟。最重要的还是要多去看看官方的文档
    • 这次主要是根据开发中遇到的情景简单介绍了MongoDB的Array和聚合查询Aggregate。

    以上内容为本人真实代码验证过的,如阐述如有差错的还请各位能纠正补充。