Как присоединиться к mongodb к массиву ссылок на объекты - PullRequest
0 голосов
/ 06 мая 2020

У меня есть коллекция объектов (рецепт), в которой есть массив ссылок на объекты на другую коллекцию (ингредиенты). Я хочу объединить эти два на уровне базы данных, а не на уровне приложения. Я считаю, что агрегат $ - это то, что мне нужно использовать, но, к сожалению, не имел особого успеха. Далее вы можете найти мои модели, как я выполняю соединение в 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'
    }
  },
]

но я застрял в том, чтобы снова объединить его в единый документ. Кроме того, это тоже не очень эффективно.

1 Ответ

0 голосов
/ 05 июня 2020

Удалось найти ответ.

Как было предложено @hpapier, мне нужно было сгруппировать, а затем использовать замену root, чтобы объект выглядел так, как я хотел

Вот как это было в итоге:

[
{
  $unwind: {
    path: '$ingredients',
  },
},
{
  $addFields: {
    ingredientOid: {
      $toObjectId: '$ingredients.ingredientId',
    },
  },
},
{
  $lookup: {
    from: 'ingredients',
    localField: 'ingredientOid',
    foreignField: '_id',
    as: 'ingredientObj',
  },
},
{
  $unwind: {
    path: '$ingredientObj',
  },
},
{
  $group: {
    _id: {
      _id: '$_id',
      steps: '$steps',
      name: '$name',
    },
    ingredients: {
      $push: {
        amount: '$ingredients.amount',
        unit: '$ingredients.unit',
        ingredient: {
          _id: '$ingredientObj.id',
          name: '$ingredientObj.name',
        },
      },
    },
  },
},
{
  $replaceRoot: {
    newRoot: {
      _id: '$_id._id',
      steps: '$_id.steps',
      name: '$_id.name',
      ingredients: '$ingredients',
    },
  },
},

Публикация здесь в качестве примера того, как выполнить соединение и изменить объекты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...