Skip to content
Advertisement

Speed Up and Simplify MongoDB Aggregate Function

I have the following function, which is quite complicated, but i’m hoping someone might be able to understand it help me simplify/speed it up.

In this function i end up with an array that contains a list of primaryStores and the number of products coming from each one to my group of stores. However, it only counts the products, if that store has an accepted connection with the particular primary store in question. (Some might have an accepted connection to one and not the other yet)

I will try and explain how it works:

I pass into the function a list of storeIDs. I loop through each one to find out which primary stores each one has an accepted connection with. Out of those with an accepted connection, i just get the store ID. I then find out what products are in my store, and get the product IDs and the linked ID connecting them to my store. I then begin my aggregate function which starts off by finding which of my products have a connection to another store. I then look up the other stores info to get the store ID and filter to only get ones i am connected to. I then project this and group by store id/name and get the total number of products coming from each primary store and an array containing a list of all the products from each one. I push each connected primaryStore for each of my main stores into an array called ‘allFeederStores’.

As i am looping through each of my main stores one at a time to get this information. At the end I have a reduce function which combines any duplicating primaryStores that appear in my array, and concatenate the total products. This provides my with an overview of where the majority of my products are coming from for all of my main stores.

I am trying to get rid of the for loop at the beginning, so i don’t have to loop through each store at once. But i can’t figure out, how i can check a signed connection exists between the two individual stores without doing them seperate.

It’s not likely anyone will understand this, but if anyone does and can help I would much appreciate it!

Below is my code:

  async getFeederStores(storeIDs): Promise<any[]> {

    let allFeederStores = [];
    for (let storeID of storeIDs) {
      //get all the signed connections this store has with primary stores
      const linkedPrimaryStores = await this.linkedStoreModel.aggregate([
        {
          $match: {
            main_store: storeID,
            primary_store_signed_by: { $ne: null },
          },
        },
      ]);

      //get just the primary store ids from the linked primary stores
      const primaryStoreIDs = linkedPrimaryStores.map((linkedPrimaryStore) => {
        return linkedPrimaryStore.primary_store;
      });

      //get all products from this store
      const products = await this.productStoreModel
        .find({
          store: storeID,
        })
        .select("product")
        .lean();

      //get product ids from this store
      const productIDs = products.map((product) => {
        return product.product;
      });

      //get the product store ids
      const productStoreIDs = products.map((product) => {
        return product._id;
      });

      const linkedStores = await this.productStoreModel.aggregate([
        {
          //get the products connected to other stores
          $match: {
            product: {
              $in: productIDs,
            },
            _id: {
              $nin: productStoreIDs,
            },
          },
        },
        //look up the other stores info
        {
          $lookup: {
            from: "stores",
            localField: "store",
            foreignField: "_id",
            as: "store",
          },
        },
        //unwind the stores info
        {
          $unwind: "$store",
        },
        //filter to only get primary stores that are connected to this main store
        {
          $match: {
            "store._id": { $in: primaryStoreIDs },
          },
        },
        //project to only get the store id/name and the product store id
        {
          $project: {
            "store._id": 1,
            "store.name": 1,
            product_store: "$_id",
          },
        },
        //group by store - get the total number of products coming from each store and the products info from each one
        {
          $group: {
            _id: "$store._id",
            name: { $first: "$store.name" },
            total_products: { $sum: 1 },
            products: {
              $push: {
                product_store: "$product_store ",
              },
            },
          },
        },
      ]);
      for (let primaryStore of linkedPrimaryStores) {
        allFeederStores.push(primaryStore);
      }
    }

    //loop through all the feeder stores
    //any duplicate stores, combine the total number of products and the products array
    let concatenatedPrimaryStores = allFeederStores.reduce((accum, cv) => {
      const index = accum.findIndex((item) => item._id === cv._id);
      if (index === -1) {
        accum.push(cv);
      } else {
        accum[index].total_products += ", " + cv.total_products;
        accum[index].products += ", " + cv.products;
      }
      return accum;
    }, []);

    return concatenatedPrimaryStores;
  }

I have added some sample data below. In this case MainStore1 has a signed connection with PrimaryStore1 but not PrimaryStore1 (so any products coming from that primary store won’t be counted for this main store) However MainStore2 has a signed connection with both PrimaryStore1 and PrimaryStore2, (so any products coming from both those primary stores will be counted for this main store)

db={

  "storesModel": [
    {
      "_id": "Main1",
      "name": "Main Store 1",
    },
    {
      "_id": "Main2",
      "name": "Main Store 1",
    },
    {
      "_id": "Primary1",
      "name": "Primary Store 1",
    },
    {
      "_id": "Primary2",
      "name": "Primary Store 2",
    },
  ],
  "linkedStoreModel": [
    {
      "_id": "LS1",
      "main_store": "Main1",
      "primary_store_signed_by": 'Bob',
      "primary_store": "Primary1"
    },
        {
      "_id": "LS2",
      "main_store": "Main2",
      "primary_store_signed_by": 'Bill',
      "primary_store": "Primary1"
    },
    {
      "_id": "LS3",
      "main_store": "Main1",
      "primary_store_signed_by": null,
      "primary_store": "Primary2"
    },
    {
      "_id": "LS4",
      "main_store": "Main2",
      "primary_store_signed_by": 'Betty',
      "primary_store": "Primary2"
    }
  ],
  "productStoreModel": [
    {
      "_id": "PS1",
      "store": "Main1",
      "product": "Product1"
    },
    {
      "_id": "PS2",
      "store": "Main2",
      "product": "Product1"
    },
   {
      "_id": "PS3",
      "store": "Main1",
      "product": "Primary2"
    },
    {
      "_id": "PS4",
      "store": "Main2",
      "product": "Primary2"
    },
  ]
}

Expected output is something like this:

concatenatedPrimaryStores:
[
{  primaryStoreName:  “Primary1”,
   total_products: “2”,
   products: {array containing products}
}
{  primaryStoreName:  “Primary2”,
   total_products: “1”,
   products: {array containing products}
}
]

Advertisement

Answer

If I understand your logic correctly, this aggregation pipeline returns the number of, and list of, products for each "primary_store" where the products come from a “signed” "main_store".

db.linkedStoreModel.aggregate([
  { // get "signed" mains for each primary
    "$group": {
      "_id": "$primary_store",
      "signedMains": {
        "$addToSet": {
          "$cond": [
            {"$ne": ["$primary_store_signed_by", null]},
            "$main_store",
            null
          ]
        }
      }
    }
  },
  { // get all products for signedMains
    "$lookup": {
      "from": "productStoreModel",
      "localField": "signedMains",
      "foreignField": "store",
      "pipeline": [
        {
          "$group": {
            "_id": null,
            "products": {"$addToSet": "$product"}
          }
        }
      ],
      "as": "productList"
    }
  },
  { // format output
    "$project": {
      "_id": 0,
      "primaryStoreName": "$_id",
      "total_products": {
        "$size": {"$ifNull": [{"$first": "$productList.products"}, [] ]}
      },
      "products": {"$first": "$productList.products"}
    }
  }
])

Try it on mongoplayground.net.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement