Search code examples
mongodbmongooseunionmongoose-schemamongoose-populate

Union 2 tables like it is a single entity, then sort, skip, and limit (Mongoose)


I have 2 schemas, schema A and B like the following:

const A = new Schema({
  paymentId: Number,
  date: Date,
  ...data
})

const B = new Schema({
  paidId: Number,
  date: Date,
  ...data
})

I want to return records from both A and B like it is a single table, where I can get records from both A and B that can be used with .sort(), .skip(), and .limit() functions ideally.

I could just do a .find() on both tables, concatenate them, and manually sort / skip / limit, but I find that highly inefficient.

EDIT: To clarify. It shouldn't matter if both collections are related or not. All I want is to query from both collections like both are in one collection.

For example, if I have the following documents

// Documents in A
{ date: '2020-01-01', A_id: 1 },
{ date: '2020-01-03', A_id: 2 },
{ date: '2020-01-05', A_id: 3 },

// Documents in B
{ date: '2020-01-02', B_id: 1 },
{ date: '2020-01-04', B_id: 2 },
{ date: '2020-01-06', B_id: 3 },

Doing a query with the options .sort('date').skip(0).limit(5) should result in the following:

// Documents in A and B
{ date: '2020-01-01', A_id: 1 },
{ date: '2020-01-02', B_id: 1 },
{ date: '2020-01-03', A_id: 2 },
{ date: '2020-01-04', B_id: 2 },
{ date: '2020-01-05', A_id: 3 },

Solution

  • From https://stackoverflow.com/a/55289023/3793648:

    Doing unions in MongoDB in a 'SQL UNION' fashion is possible using aggregations along with lookups, in a single query.

    Something like this:

        db.getCollection("AnyCollectionThatContainsAtLeastOneDocument").aggregate(
        [
          { $limit: 1 }, // Reduce the result set to a single document.
          { $project: { _id: 1 } }, // Strip all fields except the Id.
          { $project: { _id: 0 } }, // Strip the id. The document is now empty.
    
          // Lookup all collections to union together.
          { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
          { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
          { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } },
    
          // Merge the collections together.
          {
            $project:
            {
              Union: { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
            }
          },
    
          { $unwind: "$Union" }, // Unwind the union collection into a result set.
          { $replaceRoot: { newRoot: "$Union" } } // Replace the root to cleanup the resulting documents.
        ]);
    

    More details are in the post above. Adding $sort, $skip, and $limit is just a matter of adding them to the aggregate pipeline. Many thanks to @sboisse!