#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:
Пара моментов, о которых стоит упомянуть..
- Ваш пример кода в файле «courses.py» — это вставка оценок в виде строки, представляющей массив, а не фактический массив. На это указал Мэтт в комментариях, и вы попросили объяснений. Вот моя попытка объяснить — если вы вставляете строку, которая выглядит как массив, вы не можете выполнить $unwind или $lookup для подэлементов, потому что они не являются подэлементами, они являются частью строки.
- У вас есть массив данных на курсах, содержащих оценки учащихся, которые являются желаемыми точками данных, но вы начинаете агрегирование коллекции учащихся. Вместо этого, возможно, немного измените свою точку зрения и подойдите к ней с точки зрения курсов, а не с точки зрения студентов. Если вы это сделаете, вы можете переквалифицировать требование следующим образом: «покажите мне все курсы и оценки учащихся, где студенческий билет равен 0».
- Похоже, что данные вашего массива не соответствуют типу данных. Идентификатор учащегося является целым числом в вашей строковой переменной «массив», но коллекция учащихся содержит идентификатор учащегося в виде строки. Необходимо быть последовательным, чтобы поиск $работал должным образом (если вы не хотите выполнять множество кастингов).
Но, тем не менее, вот возможное решение вашей проблемы. Я пересмотрел код 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"
}
}
])