MongoDB 如何引用数组?

MongoDB How do I reference an array?

我正在尝试创建一个 MongoDB 数据库,其中包含两个集合:学生和课程。

第一个集合“学生”包含:

from pymongo import MongoClient
import pprint

client = MongoClient("mongodb://127.0.0.1:27017")
db = client.Database

student = [{"_id":"0",
          "firstname":"Bert",
           "lastname":"Holden"},
           {"_id":"1",
            "firstname":"Sam",
            "lastname":"Olsen"},
           {"_id":"2",
            "firstname":"James",
            "lastname":"Swan"}]


students = db.students
students.insert_many(student)
pprint.pprint(students.find_one())

第二个合集“课程”包含:

from pymongo import MongoClient
import pprint

client = MongoClient("mongodb://127.0.0.1:27017")
db = client.Database

course = [{"_id":"10",
           "coursename":"Databases",
           "grades":"[{student_id:0, grade:83.442}, {student_id:1, grade:45.323}, {student_id:2, grade:87.435}]"}]




courses = db.courses
courses.insert_many(course)
pprint.pprint(courses.find_one())

然后我想使用聚合来查找学生和相应课程的成绩。

from pymongo import MongoClient
import pprint

client = MongoClient("mongodb://127.0.0.1:27017")
db = client["Database"]

pipeline = [
    {
        "$lookup": {
            "from": "courses",
            "localField": "_id",
            "foreignField": "student_id",
            "as": "student_course"
            }
    },
    {
        "$match": {
            "_id": "0"
        }
    }
]

pprint.pprint(list(db.students.aggregate(pipeline)))

我不确定 student_id/grade 是否在“课程”集合中正确实施,所以这可能是我的理由之一 returns [].

如果我为每个学生创建单独的课程,聚合就可以工作,但这似乎是在浪费内存,所以我想要一个包含所有 student_ids 和成绩的课程。

预期输出:

 [{'_id': '0',
  'firstname': 'Bert',
  'lastname': 'Holden',
  'student_course': [{'_id': '10',
                      'coursename': 'Databases',
                      'grade': '83.442',
                      'student_id': '0'}]}]

有几点值得一提..

  1. 文件“courses.py”中的示例代码正在插入成绩作为 表示数组的字符串,而不是实际数组。这是 Matt 在评论中指出,您要求 解释。这是我试图解释的 - 如果你插入一个字符串 它看起来像一个你不能对其执行 $unwind 或 $lookup 的数组 sub-elements 因为它们不是 sub-elements,它们是 字符串.
  2. 您在保存学生成绩的课程中有数组数据,这些数据是 所需的数据点,但您开始聚合 学生 collection。相反,也许改变你的观点 位并从课程 collections 而不是 学生视角。如果这样做,您可能 re-qualify 要求为 - “显示所有课程和学生成绩 学生编号为 0"。
  3. 您的数组数据似乎有不匹配的数据类型。学号 是字符串变量“array”中的一个整数,但是学生 collection 将学生 ID 作为字符串。需要保持一致 为 $lookup 正常工作(如果不想执行一堆 铸造)。

但是,尽管如此,这里还是有可能解决您的问题。我修改了 python 代码,包括重新定义聚合...

我的测试数据库的名称是 pythontest,如本代码示例所示。 此数据库 必须 在 运行 代码之前存在,否则会出错。

文件students.py

from pymongo import MongoClient
import pprint

client = MongoClient("mongodb://127.0.0.1:27017")
db = client.pythontest

student = [{"_id":"0",
          "firstname":"Bert",
           "lastname":"Holden"},
           {"_id":"1",
            "firstname":"Sam",
            "lastname":"Olsen"},
           {"_id":"2",
            "firstname":"James",
            "lastname":"Swan"}]


students = db.students
students.insert_many(student)
pprint.pprint(students.find_one())

然后是课程文件。注意字段 grades 不再是一个字符串,而是一个有效的数组 object?请注意学生 ID 是一个字符串,而不是一个整数? (实际上,UUID 或 int 等更强大的数据类型可能更可取)。

文件courses.py

from pymongo import MongoClient
import pprint

client = MongoClient("mongodb://127.0.0.1:27017")
db = client.pythontest

course = [{"_id":"10",
           "coursename":"Databases",
           "grades": [{ "student_id": "0", "grade": 83.442}, {"student_id": "1", "grade": 45.323}, {"student_id": "2", "grade": 87.435}]}]


courses = db.courses
courses.insert_many(course)
pprint.pprint(courses.find_one())

...最后,具有更改的聚合管道的聚合文件...

文件aggregation.py

from pymongo import MongoClient
import pprint

client = MongoClient("mongodb://127.0.0.1:27017")
db = client.pythontest

pipeline = [
    { "$match": { "grades.student_id": "0" } },
    { "$unwind": "$grades" },
    { "$project": { "coursename": 1, "student_id": "$grades.student_id", "grade": "$grades.grade" } },
    {
        "$lookup":
        {
            "from": "students",
            "localField": "student_id",
            "foreignField": "_id",
            "as": "student"
        }
    },
    {
        "$unwind": "$student"
    },
    { "$project": { "student._id": 0 } },
    { "$match": { "student_id": "0" } }
]

pprint.pprint(list(db.courses.aggregate(pipeline)))

运行 程序的输出

> python3 aggregation.py
[{'_id': '10',
  'coursename': 'Databases',
  'grade': 83.442,
  'student': {'firstname': 'Bert', 'lastname': 'Holden'},
  'student_id': '0'}]

程序末尾的数据格式可能不尽如人意,但可以通过操作聚合进行调整。

** 编辑 **

因此,如果您想从学生而不是从课程中接近它,您仍然可以执行该聚合,但由于数组在课程中,因此聚合会稍微复杂一些。 $lookup 必须利用管道本身来准备外部数据结构:

学生视角的汇总

db.students.aggregate([
{ $match: { _id: "0" } },
{ $addFields: { "colStudents._id": "$_id" } },
{
    $lookup:
    {
        from: "courses",
        let: { varStudentId: "$colStudents._id"},
        pipeline:
        [
            { $unwind: "$grades" },
            { $match: { $expr: { $eq: ["$grades.student_id", "$$varStudentId" ] } } },
            { $project: { course_id: "$_id", coursename: 1, grade: "$grades.grade", _id: 0} }
        ],
        as: "student_course"
    }
},
{ $project: { _id: 0, student_id: "$_id", firstname: 1, lastname: 1, student_course: 1 } }
])

输出

> python3 aggregation.py
[{'firstname': 'Bert',
  'lastname': 'Holden',
  'student_course': [{'course_id': '10',
                      'coursename': 'Databases',
                      'grade': 83.442}],
  'student_id': '0'}]

终于可以看一看了..

TLDR; see Mongo Playground

此解决方案要求您将 grades 存储为实际对象而不是字符串。

考虑以下数据库结构:

db={
  // Collection
  "students": [
    {
      "_id": "0",
      "firstname": "Bert",
      "lastname": "Holden"
    },
    {
      "_id": "1",
      "firstname": "Sam",
      "lastname": "Olsen"
    },
    {
      "_id": "2",
      "firstname": "James",
      "lastname": "Swan"
    }
  ],
  // Collection
  "courses": [
    {
      "_id": "10",
      "coursename": "Databases",
      "grades": [
        {
          student_id: "0",
          grade: 83.442
        },
        {
          student_id: "1",
          grade: 45.325
        },
        {
          student_id: "2",
          grade: 87.435
        }
      ]
    }
  ],
}

您可以使用以下查询实现您想要的结果:

db.students.aggregate([
  {
    $match: {
      _id: "0"
    }
  },
  {
    $lookup: {
      from: "courses",
      pipeline: [
        {
          $unwind: "$grades"
        },
        {
          $match: {
            "grades.student_id": "0"
          }
        },
        {
          $group: {
            "_id": "$_id",
            "coursename": {
              $first: "$coursename"
            },
            "grade": {
              $first: "$grades.grade"
            },
            "student_id": {
              $first: "$grades.student_id"
            }
          }
        }
      ],
      as: "student_course"
    }
  }
])