我创建了一个 mongoplayground 以使其更容易理解。
https://mongoplayground.net/p/wrTinwpfOBI
首先。我有什么?
我的类别架构包含引用过滤器架构实体的 id。
在过滤器架构中,我有一个名为“targetProperty”的属性,其中包含描述产品详细信息的属性名称。产品模式有一个属性详细信息,它是键值映射,其中前面提到的“targetProperty”的值是该详细信息映射的键。产品内的变体实体也可能包含相同的详细信息键值映射属性。
例如,如果过滤器实体的“targetProperty”是“memory”,则存在一个包含“details”属性的产品实体,该属性包含“memory”作为其键和对象{“title”:“Memory”,“value”: “256 GB”}作为其值或某些产品变体可能在其详细信息键值映射中包含“内存”属性。
const categorySchema = new Schema<ICategory>({
filters: [{ type: Schema.Types.ObjectId, ref: filterModelName }],
// some other properties unrelated to the issue...
});
const filterSchema = new Schema<IFilter>({
targetProperty: {
type: String,
required: true
}
// some other properties...
});
const productSchema = new Schema<IProduct>({
details: {
type: Map,
of: {
title: { type: String, required: true },
value: { type: Schema.Types.Mixed, required: true }
},
required: true
},
variations: [variationSchema],
categories: [Schema.Types.ObjectId],
// some other properties...
});
const variationSchema = new Schema<IProductVariation>({
details: {
type: Map,
of: {
title: { type: String, required: true },
value: { type: Schema.Types.Mixed, required: true }
}
}
// some other properties...
});
第二。我的目标是什么?
在聚合的帮助下,我想收集某个类别的相关过滤器的所有可能选项。例如,如果 iPhone 类别包含内存过滤器,我想迭代包含 iPhone 类别的所有产品并收集内存值,例如“128gb”、“256 GB”等,它们包含在详细信息映射属性的“内存”键下作为 iPhone 类别内存过滤器的可能选项。
我的聚合管道:
db.categories.aggregate([
{
"$lookup": {
"from": "filters",
"localField": "filters",
"foreignField": "_id",
"as": "filters",
"let": {
"category_id": "$_id"
},
"pipeline": [
{
"$lookup": {
"from": "products",
"let": {
"target_property": "$targetProperty"
},
"as": "options",
"pipeline": [
{
"$match": {
"$and": [
{
"$expr": {
"$in": [
"$$category_id",
"$categories"
]
}
},
{
"$expr": {
"$or": [
{
"$ne": [
{
// ######## SOURCE OF THE PROBLEM ########
"$getField": "details.$$target_property"
},
null
]
},
{
"$ne": [
{
// ######## SOURCE OF THE PROBLEM ########
"$getField": "variations.details.$$target_property"
},
null
]
}
]
}
}
]
}
},
{
"$unwind": {
"path": "$variations",
"preserveNullAndEmptyArrays": true
}
},
{
"$group": {
"_id": {
"$ifNull": [
{
// ######## SOURCE OF THE PROBLEM ########
"$getField": "details.$$target_property.value"
},
{
// ######## SOURCE OF THE PROBLEM ########
"$getField": "variations.details.$$target_property.value"
}
]
}
}
},
{
"$group": {
"_id": null,
"options": {
"$push": "$_id"
}
}
},
{
"$project": {
"_id": 0
}
}
]
}
}
]
}
}
])
第三。我偶然发现的问题。
请注意上面聚合查询的 // ######## 问题根源 ######## 注释行下的行。似乎 $lookup 聚合阶段“let”变量的值无法按照我尝试实现它的方式嵌入。我的问题是如何正确嵌入 let 变量的值,以便我能够动态访问详细信息属性的嵌套属性?如果这可能的话...
感谢您的关注!
感谢 @cmgchess 在我的问题的评论部分中给出的 $objectToArray 提示,我成功地构建了我需要的聚合管道。最终的聚合管道看起来超级麻烦且丑陋,但最重要的是它正在工作。如果您能够提出更优雅的解决方案,我会将其标记为问题。
这是管道:
db.categories.aggregate([
$lookup: {
{
from: "productfilters",
localField: "filters",
foreignField: "_id",
as: "filters",
let: {
category_id: "$_id",
},
pipeline: [
{
$lookup: {
from: "products",
let: {
target_property: "$targetProperty",
},
as: "options",
pipeline: [
{
$match: {
$expr: {
$in: [
"$$category_id",
"$categories",
],
},
},
},
{
$unwind: {
path: "$variations",
preserveNullAndEmptyArrays: true,
},
},
{
$match: {
$expr: {
$or: [
{
$gt: [
{
$size: {
$filter: {
input: {
$objectToArray:
"$details",
},
cond: {
$eq: [
"$$this.k",
"$$target_property",
],
},
},
},
},
0,
],
},
{
$gt: [
{
$size: {
$let: {
vars: {
arr: {
$filter: {
input: {
$objectToArray:
"$variations.details",
},
cond: {
$eq: [
"$$this.k",
"$$target_property",
],
},
},
},
},
in: {
$cond: {
if: {
$isArray: [
"$$arr",
],
},
then: "$$arr",
else: [],
},
},
},
},
},
0,
],
},
],
},
},
},
{
$replaceRoot: {
newRoot: {
$let: {
vars: {
detailsArr: {
$filter: {
input: {
$objectToArray:
"$details",
},
cond: {
$eq: [
"$$this.k",
"$$target_property",
],
},
},
},
variationsArr: {
$filter: {
input: {
$ifNull: [
{
$objectToArray:
"$variations.details",
},
[],
],
},
cond: {
$eq: [
"$$this.k",
"$$target_property",
],
},
},
},
},
in: {
$cond: {
if: {
$gt: [
{
$size: "$$detailsArr",
},
0,
],
},
then: {
$getField: {
field: "v",
input: {
$arrayElemAt: [
"$$detailsArr",
0,
],
},
},
},
else: {
$cond: {
if: {
$gt: [
{
$size:
"$$variationsArr",
},
0,
],
},
then: {
$getField: {
field: "v",
input: {
$arrayElemAt: [
"$$variationsArr",
0,
],
},
},
},
else: null,
},
},
},
},
},
},
},
},
{
$group: {
_id: "$value",
},
},
{
$sort: {
_id: 1,
},
},
],
},
},
{
$set: {
options: {
$map: {
input: "$options",
in: {
$getField: {
field: "_id",
input: "$$this",
},
},
},
},
},
},
],
}
}
])