У меня есть коллекция объектов (рецепт), в которой есть массив ссылок на объекты на другую коллекцию (ингредиенты). Я хочу объединить эти два на уровне базы данных, а не на уровне приложения. Я считаю, что агрегат $ - это то, что мне нужно использовать, но, к сожалению, не имел особого успеха. Далее вы можете найти мои модели, как я выполняю соединение в JS и как я застрял, пытаясь реализовать с использованием $ aggregate
Модель TS:
export type Ingredient = {
id?: string;
name: string;
};
export type IngredientAmount = {
ingredient: Ingredient;
amount: number;
unit: string;
};
export type Recipe {
id?: number;
name: string;
steps: string[];
ingredients: IngredientAmount[];
}
Поскольку мне нужно сделать запросы на основе ингредиентов, например «Дайте мне все рецепты с использованием молока», я решил использовать Ссылки на документы . Но теперь мне трудно придумать запрос на объединение этого массива количества ингредиентов.
Вот как выглядит запись в БД:
recipes: {
"_id": {
"$oid": "5eab14eb597fdf1af2974a55"
},
"name": "Cheese & Ham sandwich",
"steps": ["Slice bread", "Put cheese slices and Ham between bread slices"],
"ingredients": [{
"ingredientId": "5eab10d5597fdf1af2974a4f",
"amt": "2",
"unit": "slices"
}, {
"ingredientId": "5eab10e5597fdf1af2974a50",
"amt": "1",
"unit": "unit"
}, {
"ingredientId": "5eab10fc597fdf1af2974a51",
"amt": "1",
"unit": "slice"
},]}
ingredients: {
"_id": {
"$oid": "5eab10d5597fdf1af2974a4f"
},
"name": "cheese",
}
I хочу запросить эти два документа и объединить их в соответствии с моим предопределенным типом. Вот как я это делаю в JS:
// Query recipe
const rawRecipe = await db
.collection('recipes')
.findOne(new ObjectID(queryId));
// Get ids to join
const ingredientIds = await rawRecipe['ingredients'].map(
(i: { ingredientId: string }) => new ObjectID(i.ingredientId)
);
// Fetch the ingredients to be joined
const rawIngredients: Ingredient[] = await db
.collection('ingredients')
.find({ _id: { $in: ingredientIds } })
.map((r) => {
return {
id: r._id.toString(),
name: r.name,
};
})
.toArray();
// Create objects from the items to be joined
const ingredients: IngredientAmount[] = rawIngredients.map((i) => {
return {
ingredient: i,
amount: rawRecipe['ingredients'].find(
(entry: { ingredientId: string }) => entry.ingredientId == i.id
).amt,
unit: rawRecipe['ingredients'].find(
(entry: { ingredientId: string }) => entry.ingredientId == i.id
).unit,
};
});
// Create new result object
const r: Recipe = new Recipe(
rawRecipe['name'],
rawRecipe['steps'],
ingredients,
rawRecipe['_id']
);
Каким будет эквивалент при использовании агрегатного конвейера?
Я дошел до
[
{
'$unwind': {
'path': '$ingredients'
}
}, {
'$addFields': {
'ingredientOid': {
'$toObjectId': '$ingredients.ingredientId'
}
}
}, {
'$lookup': {
'from': 'ingredients',
'localField': 'ingredientOid',
'foreignField': '_id',
'as': 'ingredientObj'
}
}, {
'$unwind': {
'path': '$ingredientObj'
}
},
]
но я застрял в том, чтобы снова объединить его в единый документ. Кроме того, это тоже не очень эффективно.