22_ShoppingBasket

Code-Dateien

DateinameAktion
CODECode_pizza.zipDownload

Lernmaterialien

Recommendation system

Shopping basket

A shopping basket is the place where an online store keeps the items you plan to buy.

It works like a real basket in a supermarket:

  • You add products to it while browsing

  • The basket stores them temporarily

  • You can change quantities or remove items

  • When you are ready, you go to checkout and pay

Example:

You visit an online store and choose:

  • 1 laptop

  • 2 books

  • 1 mouse

These items go into your shopping basket. The basket shows:

  • what you selected

  • how many of each item

  • the total price

In databases or e-commerce systems, a shopping basket is often stored as data linked to a customer or session, so the website remembers what the user wants to buy.

So in one sentence: a shopping basket is a temporary collection of items a customer wants to purchase.

Recommendation system

A recommendation system for a shopping basket is a system that suggests additional products based on what is already in the customer’s basket.

It answers questions like:

  • “Customers who bought this laptop also bought a mouse.”

  • “You added pasta and tomato sauce — do you also want cheese?”

  • “Buy these 3 items together and save 10%.”

So instead of recommending products from general browsing history, it uses the current basket contents to make suggestions.

Main idea

The system looks at the items already chosen and tries to predict:

  • what other item the customer may need

  • what products are often bought together

  • what combination increases convenience or sales

Example

A customer’s basket contains:

  • smartphone

  • phone case

The recommendation system may suggest:

  • screen protector

  • charger

  • wireless earbuds

because these items are commonly purchased together.

How it works

Common methods include:

  • Association rules / market basket analysis Finds patterns like: {bread, butter} → jam

  • Collaborative filtering

    Recommends items based on what similar customers bought

  • Content-based methods

    Suggests related items based on product attributes

  • Machine learning / deep learning

    Predicts the next best item from basket context, user behavior, and past purchases

Why stores use it

It helps to:

  • increase sales

  • improve customer experience

  • make shopping easier

  • support cross-selling and upselling

A shopping-basket recommendation system is a system that recommends extra or related items based on the products currently in a customer’s cart.

If you want, I can also explain the difference between market basket analysis and a shopping basket recommendation system.

Implementation

Data

Here is my datastructure. Please prepare it for a recommendation system for a shopping basket. Please give me for each collection one file. Fille it with data.

I prepared one file per collection from your MongoDB structure and filled them with linked sample data for basket recommendations, based on your uploaded schema

Files:

  • product.json

  • customer.json

product.json

[
  {
    "_id": "000000000000000000000005",
    "prduct_name": "Margherita Pizza",
    "product_decription": "Classic pizza with tomato sauce and mozzarella",
    "product_type": "pizza",
    "alcohol": "no",
    "restaurant": [
      {
        "restaurant_id": "000000000000000000000001",
        "restaurant_name": "Pizza Palace",
        "restaurant_adress": {
          "street_name": "Main Street 10",
          "zip": "10115",
          "city_name": "Berlin",
          "country": "Germany"
        },
        "current_price": 8.9
      }
    ]
  },
  ...

customer.json

[
  {
    "_id": "000000000000000000000016",
    "first_name": "Anna",
    "last_name": "Schmidt",
    "phone_number": "491511000001",
    "eMail": "anna.schmidt@example.com",
    "adress": {
      "street_name": "Birkenweg 4",
      "zip": "10117",
      "city_name": "Berlin",
      "country": "Germany"
    },
    "order": [
      {
        "order_date": "2025-01-05T18:30:00Z",
        "delivery_date": "2025-01-06T19:15:00Z",
        "order_state": "delivered",
        "delivery_costs": 1.99,
        "driver": {
          "driver_name": "Nina Courier",
          "vehicle_name": "Toyota Yaris"
        },
        "product": [
          {
            "product_id": "000000000000000000000005",
            "product_name": "Margherita Pizza",
            "selling_price_piece": 8.9,
            "number_of_pieces": 1,
            "restaurant_id": "000000000000000000000001",
            "restaurant_name": "Pizza Palace"
          },
          {
            "product_id": "000000000000000000000007",
            "product_name": "Garlic Bread",
            "selling_price_piece": 4.5,
            "number_of_pieces": 1,
            "restaurant_id": "000000000000000000000001",
            "restaurant_name": "Pizza Palace"
          },
          ...
        ]
      },
      ...

Import

A MongoDB collection is a container that holds multiple documents in a database. You can think of it as similar to a table in a relational database, but it is more flexible. Instead of rows with a fixed structure, a collection stores documents in BSON, which is a JSON-like format. Documents in the same collection usually belong to the same type of data, such as students, orders, or products, but they do not all need to have exactly the same fields.

001.png
002.png
003.png
004.png
005.png

Query

Below is a MongoDB aggregation query for a shopping-basket recommendation system using your structure. It uses the customer collection, because your basket and purchase history are stored inside customer.order.product

Goal of the query

Given a current basket like:

  • Margherita Pizza

  • Coca Cola 1L

the query finds other products that were often bought together in past orders and recommends them.

Please generate a query for a recommendation system. Use my structure. Explain the query in detail.
// 0) Current in the basket
const basketProductIds = [
  "000000000000000000000005", // Margherita Pizza
  "000000000000000000000009"  // Coca Cola 1L
];

db.customer.aggregate([
  // 1) Split customer -> orders
  {
    $unwind: "$order"
  },

  // 2) Keep only orders that contain all basket items
  {
    $match: {
      $expr: {
        $setIsSubset: [
          basketProductIds,
          {
            $map: {
              input: "$order.product",
              as: "p",
              in: "$$p.product_id"
            }
          }
        ]
      }
    }
  },

  // 3) Split the products of the matching orders
  {
    $unwind: "$order.product"
  },

  // 4) Do not recommend items already in the basket
  {
    $match: {
      "order.product.product_id": { $nin: basketProductIds }
    }
  },

  // 5) Count how often each candidate product appears together
  {
    $group: {
      _id: {
        product_id: "$order.product.product_id",
        product_name: "$order.product.product_name"
      },
      times_bought_together: { $sum: 1 },
      avg_selling_price: { $avg: "$order.product.selling_price_piece" },
      restaurants: {
        $addToSet: {
          restaurant_id: "$order.product.restaurant_id",
          restaurant_name: "$order.product.restaurant_name"
        }
      }
    }
  },

  // 6) Optional: enrich with data from your product collection
  {
    $lookup: {
      from: "product",
      localField: "_id.product_id",
      foreignField: "_id",
      as: "product_master"
    }
  },

  {
    $unwind: {
      path: "$product_master",
      preserveNullAndEmptyArrays: true
    }
  },

  // 7) Shape the recommendation result
  {
    $project: {
      _id: 0,
      product_id: "$_id.product_id",
      product_name: "$_id.product_name",
      times_bought_together: 1,
      avg_selling_price: { $round: ["$avg_selling_price", 2] },
      product_type: "$product_master.product_type",
      product_description: "$product_master.product_decription",
      alcohol: "$product_master.alcohol",
      available_from_restaurants: "$restaurants"
    }
  },

  // 8) Best recommendations first
  {
    $sort: {
      times_bought_together: -1,
      product_name: 1
    }
  },

  // 9) Limit the output
  {
    $limit: 5
  }
]);
006.png

Why this fits your structure

Your schema has:

  • customer

    • order

      • product

        • product_id

        • product_name

        • selling_price_piece

        • restaurant_id

        • restaurant_name

and also a separate product collection with:

  • _id

  • prduct_name

  • product_decription

  • product_type

  • alcohol

  • restaurant

So the best source for basket recommendation logic is the customer collection, because that is where the real order combinations live. The product collection is useful to enrich the result with product details

unwind

{ $unwind: "$order" }

Each customer has an array of orders. This step turns:

  • one customer with many orders

into

  • one document per order

That is necessary because recommendations should be based on single baskets/orders, not on all products the customer ever bought across time.

[
  { _id: "16",
    first_name: "Anna",
    last_name: "Schmidt",
    order: {
      order_date: "2025-01-05",
      product: [
        { product_id: "5", product_name: "Margherita Pizza", ... },
        { product_id: "7", product_name: "Garlic Bread", ... },
        ... ] } } ,

  { _id: "16",
    first_name: "Anna",
    last_name: "Schmidt",
    order: {
      order_date: "2025-01-22",
      product: [
        { product_id: "6", product_name: "Pepperoni Pizza", ... },
        { product_id: "7", product_name: "Garlic Bread", ... },
        { product_id: "9", product_name: "Cola 0.5L", ... }
        ... ] } },

Match only orders containing the current basket

const basketProductIds = [
  ObjectId("5"), // Margherita Pizza
  ObjectId("9")  // Coca Cola 1L
];
{
  $match: {
    $expr: {
      $setIsSubset: [
        basketProductIds,
        {
          $map: {
            input: "$order.product",
            as: "p",
            in: "$$p.product_id"
          }
        }
      ]
    }
  }
}

This is the core of the recommendation logic.

It asks:

“Is the current basket a subset of the products in this historical order?”

Example:

Current basket:

  • 5 … Pizza

  • 9 … Cola

Historical order:

  • 5 … Pizza

  • 7 … Garlic Bread

  • 9 … Cola

This order matches, because the historical order contains all basket items.

How it works:

  • $map extracts all product_id values from order.product

  • $setIsSubset checks whether basketProductIds is fully contained in that order’s product list

So only relevant historical baskets remain.

This $match returns only those unwound order documents whose order.product contains every ID in basketProductIds.

So the result is not a transformed document. It is a filtered subset of the pipeline input.

Inner part

{
  $map: {
    input: "$order.product",
    as: "p",
    in: "$$p.product_id"
  }
}

This converts one order like this:

order.product: [
  { product_id: "5", product_name: "Pizza" },
  { product_id: "9", product_name: "Cola" },
  { product_id: "7", product_name: "Garlic Bread" }
]

into:

[ "5", "9", "7" ]

Then $setIsSubset checks:

basketProductIds = ["5", "9"]
Is basketProductIds a subset of [ "5", "9", "7" ]?
YES!!!

Exact meaning of the result

After this stage, the pipeline output contains only documents like:

[
  { _id: "23",
    first_name: "Susi",
    last_name: "Müller",
    order: {
      order_date: "2025-01-05",
      product: [
        { product_id: "5", product_name: "Margherita Pizza", ... },
        { product_id: "9", product_name: "Cola 0.5L", ... }
        { product_id: "7", product_name: "Garlic Bread", ... },
        ... ] } } ,

  { _id: "23",
    first_name: "Susi",
    last_name: "Müller",
    order: {
      order_date: "2025-01-22",
      product: [
        { product_id: "5", product_name: "Margherita Pizza", ... },
        { product_id: "7", product_name: "Garlic Bread", ... },
        { product_id: "9", product_name: "Cola 0.5L", ... }
        ... ] } },

but only for matching orders.

So the result is:

  • same structure as after $unwind: "$order"

  • fewer documents

  • only orders containing all basket items

unwind

Now each matching order is split into individual products.

If a matching order contains:

  • Pizza

  • Cola

  • Garlic Bread

it becomes three documents.

This allows MongoDB to count which extra products appear with the basket most often.

Exclude products already in the basket

{
  $match: {
    "order.product.product_id": { $nin: basketProductIds }
  }
}

You do not want to recommend products the user already has.

If the basket already contains:

  • Pizza

  • Cola

then those two should not appear as recommendations.

Only possible add-on items remain, such as:

  • Garlic Bread

  • Tiramisu

  • Water

Group and count co-occurrences

{
  $group: {
    _id: {
      product_id: "$order.product.product_id",
      product_name: "$order.product.product_name"
    },
    times_bought_together: { $sum: 1 },
    avg_selling_price: { $avg: "$order.product.selling_price_piece" },
    restaurants: {
      $addToSet: {
        restaurant_id: "$order.product.restaurant_id",
        restaurant_name: "$order.product.restaurant_name"
      }
    }
  }
}

This stage creates the recommendation score.

It groups by product and calculates:

  • times_bought_together: how often this product was bought with the basket

  • avg_selling_price: average selling price in matching orders

  • restaurants: which restaurants sold it in those orders

Example result before sorting:

  • Garlic Bread → 18

  • Tiramisu → 9

  • Water → 6

That means Garlic Bread is the strongest recommendation.

$lookup into product

{
  $lookup: {
    from: "product",
    localField: "_id.product_id",
    foreignField: "_id",
    as: "product_master"
  }
}

This step enriches the recommendation with extra fields from your product collection, such as:

  • product_type

  • product_decription

  • alcohol

This is optional, but useful if you want the frontend to show more than just the product name.

Format the output

{
  $project: {
    _id: 0,
    product_id: "$_id.product_id",
    product_name: "$_id.product_name",
    times_bought_together: 1,
    avg_selling_price: { $round: ["$avg_selling_price", 2] },
    product_type: "$product_master.product_type",
    product_description: "$product_master.product_decription",
    alcohol: "$product_master.alcohol",
    available_from_restaurants: "$restaurants"
  }
}

This makes the result easier to use in an application.

Example output:

[
  {
    product_id: ObjectId("7"),
    product_name: "Garlic Bread",
    times_bought_together: 18,
    avg_selling_price: 4.49,
    product_type: "side",
    product_description: "Toasted bread with garlic butter",
    alcohol: "no",
    available_from_restaurants: [
      {
        restaurant_id: ObjectId("64f200000000000000000001"),
        restaurant_name: "Bella Napoli"
      }
    ]
  }
]

Sort by strongest recommendation

{
  $sort: {
    times_bought_together: -1,
    product_name: 1
  }
}

Products most frequently bought together appear first.

Limit results

{ $limit: 5 }

Returns only the top 5 recommendations.

Limitations

A product bought together often is recommended, but it does not consider:

  • recency

  • customer preferences

  • seasonality

  • product popularity bias

Lift

Lift is a measure used in market basket analysis and recommendation systems to show how strongly two products are associated.

It answers this question:

How much more often do two items occur together than we would expect by pure chance?

For a rule:

"Pizza" → "Cola"

the lift is:

Lift("Pizza" → "Cola") = 
Support("Pizza" ∪ "Cola") / (Support("Pizza") × Support("Cola"))

Meaning of the parts

  • Support(“Pizza”) = how often item “Pizza” appears

  • Support(“Cola”) = how often item “Cola” appears

  • Support(“Pizza” ∪ “Cola”) = how often A and B appear together

Interpretation

  • Lift = 1 … “Pizza” and “Fries” are independent. They occur together exactly as often as expected by chance.

  • Lift > 1 … “Pizza” and “Cola” occur together more often than expected. This means there is a useful positive association.

  • Lift < 1 … “Pizza” and “Noodles” occur together less often than expected. This suggests a negative association.

A lift of 1.67 means:

Pizza and Cola are bought together 1.67 times more often than expected by chance.

So Cola is a meaningful recommendation for Pizza.

In recommendation systems

You can use lift to rank or filter recommendations.

Example rules:

  • Pizza → Garlic Bread, lift = 2.4

  • Pizza → Water, lift = 1.1

  • Pizza → Cola, lift = 1.7

Then Garlic Bread is the strongest associated recommendation, even if Water appears often.

const productA = "000000000000000000000005"; // Pizza
const productB = "000000000000000000000009"; // Cola

db.customer.aggregate([
  {
    $unwind: "$order"
  },
  {
    $project: {
      productIds: {
        $map: {
          input: "$order.product",
          as: "p",
          in: "$$p.product_id"
        }
      }
    }
  },
  {
    $group: {
      _id: null,
      totalOrders: { $sum: 1 },
      countA: {
        $sum: {
          $cond: [
            { $in: [productA, "$productIds"] },
            1,
            0
          ]
        }
      },
      countB: {
        $sum: {
          $cond: [
            { $in: [productB, "$productIds"] },
            1,
            0
          ]
        }
      },
      countAB: {
        $sum: {
          $cond: [
            {
              $and: [
                { $in: [productA, "$productIds"] },
                { $in: [productB, "$productIds"] }
              ]
            },
            1,
            0
          ]
        }
      }
    }
  },
  {
    $project: {
      _id: 0,
      totalOrders: 1,
      countA: 1,
      countB: 1,
      countAB: 1,
      supportA: { $divide: ["$countA", "$totalOrders"] },
      supportB: { $divide: ["$countB", "$totalOrders"] },
      supportAB: { $divide: ["$countAB", "$totalOrders"] },
      lift: {
        $divide: [
          { $divide: ["$countAB", "$totalOrders"] },
          {
            $multiply: [
              { $divide: ["$countA", "$totalOrders"] },
              { $divide: ["$countB", "$totalOrders"] }
            ]
          }
        ]
      }
    }
  }
]);