#mongodb #aggregation
#mongodb #агрегация
Вопрос:
Извините за плохое название, но вот проблема, которую я пытаюсь решить:
У меня есть несколько коллекций, включая ticket, fields и fieldOptions, которые выглядят примерно так:
Билет:
{
_id: 1,
subject: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
fields: [
{
key: "part-number",
value: "abc123",
},
{
key: "price",
value: "10",
},
{
key: "officer",
value: "2",
},
]
}
Поля:
[
{
_id: 1,
created: ISODate("2020-09-10T20:23:46.382Z"),
options: [],
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
displayOrder: 0,
key: "part-number",
label: "Part Number",
required: false,
type: "text",
},
{
_id: 2,
created: ISODate("2020-09-10T20:23:46.382Z"),
options: [],
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
displayOrder: 1,
key: "price",
label: "Price",
required: false,
type: "text",
},
{
_id: 3,
created: ISODate("2020-09-10T20:23:46.382Z"),
options: [1, 2, 3, 4],
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
displayOrder: 2,
key: "officer",
label: "Officer",
required: false,
type: "select",
},
// THIS WAS ADDED AFTER THE 'TICKET' DOC WAS CREATED, SO IT'S NOT LISTED UNDER 'FIELDS' IN THERE
{
_id: 4,
created: ISODate("2020-09-10T20:23:46.382Z"),
options: [],
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
displayOrder: 1,
key: "notes",
label: "Notes",
required: false,
type: "text",
},
]
Параметры поля:
[
{
_id: 1,
created: ISODate("2020-09-10T20:23:46.382Z"),
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
key: "none",
label: "None",
displayOrder: 0,
legacy: false,
},
{
_id: 2,
created: ISODate("2020-09-10T20:23:46.382Z"),
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
key: "picard",
label: "Picard",
displayOrder: 1,
legacy: false,
},
{
_id: 3,
created: ISODate("2020-09-10T20:23:46.382Z"),
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
key: "riker",
label: "Riker",
displayOrder: 2,
legacy: false,
},
{
_id: 4,
created: ISODate("2020-09-10T20:23:46.382Z"),
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
key: "data",
label: "Data",
displayOrder: 3,
legacy: false,
},
{
_id: 5,
created: ISODate("2020-09-10T20:23:46.382Z"),
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
key: "red-shirt",
label: "Red Shirt",
displayOrder: 4,
legacy: true,
},
]
In my aggregation pipeline, I have the following:
[
{ $match: {_id: 2} },
{ $unwind: { path: "$fields", "preserveNullAndEmptyArrays": true } },
{
"$lookup": {
"from": "fields",
"let": { "key": "$fields.key" },
"as": "fields.def",
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$key", "$$key"] } } },
{
"$lookup": {
"from": "fieldoptions",
"let": { "options": "$options" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$_id", "$$options"] } } },
],
"as": "options"
}
}
]
}
},
{ $unwind: { path: "$fields.def", "preserveNullAndEmptyArrays": true } },
{
$group: {
_id: "$_id",
fields: { $push: "$$ROOT.fields" },
root: { $mergeObjects: "$$ROOT" },
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: ["$root", "$$ROOT"]
}
}
},
{
$unset: "root"
},
{ $project: { fields: '$fields'}} // this is to remove other junk for testing
]
Which brings back the following (almost correct) result:
{
"_id" : 2,
"fields" : [
{
"key" : "part-number",
"value" : "abc123",
"def" : {
"_id" : 1,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"options" : [],
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"displayOrder" : 0,
"key" : "part-number",
"label" : "Part Number",
"required" : false,
"type" : "text",
"__v" : 0
}
},
{
"key" : "price",
"value" : "10",
"def" : {
"_id" : 2,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"options" : [],
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"displayOrder" : 1,
"key" : "price",
"label" : "Price",
"required" : false,
"type" : "text",
"__v" : 0
}
},
{
"key" : "officer",
"value" : "2",
"def" : {
"_id" : 3,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"options" : [
{
"_id" : 1,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"key" : "none",
"label" : "None",
"displayOrder" : 0,
"legacy" : false,
"__v" : 0
},
{
"_id" : 2,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"key" : "picard",
"label" : "Picard",
"displayOrder" : 1,
"legacy" : false,
"__v" : 0
},
{
"_id" : 3,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"key" : "riker",
"label" : "Riker",
"displayOrder" : 2,
"legacy" : false,
"__v" : 0
},
{
"_id" : 4,
"created" : ISODate("2020-09-10T20:23:46.382Z"),
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"key" : "data",
"label" : "Data",
"displayOrder" : 3,
"legacy" : false,
"__v" : 0
}
],
"updated" : ISODate("2020-09-10T20:23:46.382Z"),
"active" : true,
"displayOrder" : 2,
"key" : "officer",
"label" : "Officer",
"required" : false,
"type" : "select",
"__v" : 0
}
}
]
}
Моя проблема в том, что если я добавлю что-то в коллекцию ‘fields’ (назовем это свойством ‘foo’), существующие объекты ticket не имеют ссылки ‘foo’, поэтому они не отображаются в объекте ticket, я бы хотел как-то (в рамках агрегации)получите список текущих полей и для каждого найдите соответствующее значение, если оно существует, в противном случае возвращайте с пустым значением, например:
новый результат запроса:
{
_id: 2,
fields: [
... //existing fields
{
key: "foo", // <-- new fields / fields not listed on the ticket
value: null,
def: {
_id: 1,
created: ISODate("2020-09-10T20:23:46.382Z"),
options: [],
updated: ISODate("2020-09-10T20:23:46.382Z"),
active: true,
displayOrder: 0,
key: "foo",
label: "Foo",
required: false,
type: "text",
__v: 0,
},
},
],
}
Как это можно сделать? Кроме того, есть ли лучший способ сделать это, чем то, что у меня есть? Я очень новичок в агрегации и все еще экспериментирую.
Комментарии:
1. Есть ли что-то в предоставленном ответе, что, по вашему мнению, не отвечает на ваш вопрос? Если да, то, пожалуйста, прокомментируйте ответ, чтобы уточнить, что именно необходимо решить, чего нет.
Ответ №1:
Это очень длительный процесс объединения обоих полей, вы можете выполнить следующие действия,
$match
ваше условие$addFields
чтобы добавить массив полейkey
вkeys
массив$facet
сгенерировать 2 отдельных массива, один для совпадающих полей, а второй для несогласованного массива- создайте массив совпадающих полей
fields
, передайтеkeys
массив в let и сопоставьте$in
запрос и поиск сfieldoptions
коллекцией,$project
чтобы получить объекты объединения с текущими корневыми ключами и значением, используя$map
и$reduce
- создайте массив из несопоставимых полей
other_fields
, передайтеkeys
массив в let и сопоставьте$not
, а$in
запрос означает не включать и поиск сfieldoptions
коллекцией$project
чтобы получить объекты объединения с текущими корневыми ключами и значением, используя$map
и$mergeObjects
- создайте массив совпадающих полей
$project
для объединения обоих массивов в массив с помощьюfields
$concatArrays
$unwind
деконструироватьfields
массив$replaceWith
fields
замена объекта на корневой$unwind
деконструироватьfields
массив$group
по идентификатору билета и построению массиваfields
db.ticket.aggregate([
{ $match: { _id: 1 } },
{ $addFields: { keys: { $map: { input: "$fields", in: "$$this.key" } } } },
{
$facet: {
fields: [
{
$lookup: {
from: "fields",
let: { key: "$keys" },
as: "_fields",
pipeline: [
{ $match: { $expr: { $in: ["$key", "$$key"] } } },
{
$lookup: {
from: "fieldoptions",
localField: "options",
foreignField: "_id",
as: "options"
}
}
]
}
},
{
$project: {
fields: {
$map: {
input: "$fields",
as: "f",
in: {
$mergeObjects: [
"$$f",
{
def: {
$reduce: {
input: "$_fields",
initialValue: {},
in: {
$cond: [{ $eq: ["$$f.key", "$$this.key"] }, "$$this", "$$value"]
}
}
}
}
]
}
}
}
}
}
],
other_fields: [
{
$lookup: {
from: "fields",
let: { key: "$keys" },
as: "_fields",
pipeline: [
{ $match: { $expr: { $not: { $in: ["$key", "$$key"] } } } },
{
$lookup: {
from: "fieldoptions",
localField: "options",
foreignField: "_id",
as: "options"
}
}
]
}
},
{
$project: {
fields: {
$map: {
input: "$_fields",
as: "f",
in: {
$mergeObjects: [
{ def: "$$f" },
{ key: "$$f.key", value: null }
]
}
}
}
}
}
]
}
},
{ $project: { fields: { $concatArrays: ["$fields", "$other_fields"] } } },
{ $unwind: "$fields" },
{ $replaceWith: "$fields" },
{
$unwind: {
path: "$fields",
preserveNullAndEmptyArrays: true
}
},
{
$group: {
_id: "$_id",
fields: { $push: "$$ROOT.fields" }
}
}
])
Ваш запрос, о котором идет речь, является оптимизированной версией этой площадки запросов