MongoDB Как мне ссылаться на массив?

#python #database #mongodb #reference

Вопрос:

Я пытаюсь создать базу данных 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 в коллекции «курсы», так что это может быть одной из причин, по которой моя регистрация возвращается [].

Совокупность работает, если я создаю отдельные курсы для каждого студента, но это кажется пустой тратой памяти, поэтому я хотел бы иметь один курс со всеми идентификаторами и оценками student_id в массиве.

Ожидаемый результат:

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

Комментарии:

1. Почему оценки хранятся в виде строки, а не объекта?

2. Я не уверен. Не могли бы вы объяснить? 🙂

3. Извините, до сих пор я не мог заглянуть глубже. Я опубликовал ответ (хотя вы уже нашли рабочий ответ).

Ответ №1:

Пара моментов, о которых стоит упомянуть..

  1. Ваш пример кода в файле «courses.py» — это вставка оценок в виде строки, представляющей массив, а не фактический массив. На это указал Мэтт в комментариях, и вы попросили объяснений. Вот моя попытка объяснить — если вы вставляете строку, которая выглядит как массив, вы не можете выполнить $unwind или $lookup для подэлементов, потому что они не являются подэлементами, они являются частью строки.
  2. У вас есть массив данных на курсах, содержащих оценки учащихся, которые являются желаемыми точками данных, но вы начинаете агрегирование коллекции учащихся. Вместо этого, возможно, немного измените свою точку зрения и подойдите к ней с точки зрения курсов, а не с точки зрения студентов. Если вы это сделаете, вы можете переквалифицировать требование следующим образом: «покажите мне все курсы и оценки учащихся, где студенческий билет равен 0».
  3. Похоже, что данные вашего массива не соответствуют типу данных. Идентификатор учащегося является целым числом в вашей строковой переменной «массив», но коллекция учащихся содержит идентификатор учащегося в виде строки. Необходимо быть последовательным, чтобы поиск $работал должным образом (если вы не хотите выполнять множество кастингов).

Но, тем не менее, вот возможное решение вашей проблемы. Я пересмотрел код 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 больше не является строкой, но является ли оно допустимым объектом массива? Обратите внимание, что студенческий билет представляет собой строку, а не целое число? (На самом деле, более сильный тип данных, такой как 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'}]
 

Формат данных в конце программы может быть не таким, как хотелось бы, но его можно изменить, манипулируя агрегацией.

** РЕДАКТИРОВАТЬ **

Поэтому, если вы хотите приблизиться к этой агрегации от студента, а не от курса, вы все равно можете выполнить эту агрегацию, но поскольку массив находится в курсах, агрегирование будет немного сложнее. Поиск $должен использовать сам конвейер для подготовки внешних структур данных:

Агрегирование с точки зрения учащихся

 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'}]
 

Комментарии:

1. Привет! Это прекрасно работает. Спасибо! Я на самом деле попробовал эту точную агрегацию, но когда мой courses.py файл был сделан неправильно, он не дал мне вывода. И именно поэтому я пришел сюда, так как я не знал, была ли моя агрегация или моя коллекция(коллекции) неисправны. Еще раз спасибо за подробное объяснение! 😀

2. @MadVags -спасибо за очки! Я добавил обновление к сообщению. Я добавил агрегацию, если подходить также с точки зрения студента. Это немного сложнее, потому что массив находится во внешней таблице, но, надеюсь, это будет иметь смысл…

Ответ №2:

Наконец-то я смог взглянуть на это..

TLDR; см. Игровую площадку Mongo

Это решение требует, чтобы вы хранили 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"
    }
  }
])