Searching on an embedded field together with a non-embedded field in an Atlas search

I have a document with a structure like this:

{
    "_id": "",
    "title": "Todo Demo",

    "todos": [
                 {
                     "todo": "Travel to France",
                     "isCompleted": false,
                 },
                 {
                     "todo": "Send money to rome",
                     "isCompleted": false,
                 },
                 {
                     "todo": "Play football",
                     "isCompleted": false,
                 }
             ],
}

I want to use Atlas search to search from both the title field and the todos.todo field in the embedded document.

This is my index mapping definition:

{
  "mappings": {
    "dynamic": false,
    "fields": {
      "title": {
        "analyzer": "lucene.standard",
        "type": "string"
      },
      "todos": {
        "dynamic": false,
        "fields": {
          "todo": {
            "analyzer": "lucene.standard",
            "type": "string"
          }
        },
        "type": "embeddedDocuments"
      }
    }
  }
}

Here is my search aggregation pipeline:

const searchtodos = await this.todoModel.aggregate([
        {
          $search: {
            index: 'search_todos',
            embeddedDocument: {
              path: 'todos',
              operator: {
                compound: {
                  must: [
                    {
                      text: {
                        query: search,
                        path: ['todos.todo'],
                        fuzzy: {
                          maxEdits: 1,
                          maxExpansions: 10,
                        },
                      },
                    },
                  ],
                  should: [
                    {
                      phrase: {
                        query: search,
                        path: ['todos.todo'],
                        slop: 2,
                      },
                    },
                  ],
                },
              },
            },
            count: {
              type: 'total',
            },
          },
        },
        {
          $match: query,
        },
        {
          $lookup: {
            from: 'userentities',
            localField: 'userId',
            foreignField: '_id',
            as: 'userData',
          },
        },
        {
          $unwind: '$userData',
        },
        {
          $project: todoProject,
        },
        {
          $facet: {
            totalCount: [{ $count: 'count' }],
            documents: [
              {
                $skip: (page - 1) * limit,
              },
              {
                $limit: limit,
              },
            ],
          },
        },
      ]);

However, if I search with a string that matches the todos.todo field e.g “football”, the search works properly but if I search for a string that matches the title field e.g “Demo”, the search is not working. What update do I need to make to my search aggregation so that it can return result from both the title field and the todos.todo field?

Hi @Zack_Bello , it looks like your aggregation does not include any search criteria for the title field. You can add a search clause for this by using the compound operator at the top level, and nest the embeddedDocument query that you have written within it:

[
  {
    $search: {
      index: "default",
      compound: {
        should: [
          {
            embeddedDocument: {
              path: "todos",
              operator: {
                compound: {
                  must: [
                    {
                      text: {
                        path: "todos.todo",
                        query: "foo",
                        fuzzy: {
                          maxEdits: 1,
                          maxExpansions: 10,
                        },
                      },
                    },
                  ],
                  should: [
                    {
                      phrase: {
                        path: "todos.todo",
                        query: "foo",
                        slop: 2,
                      },
                    },
                  ],
                },
              },
            },
          },
          {
            text: {
              path: "name",
              query: "cake",
            },
          },
        ],
      },
    },
  },
]

BTW, can you explain what the query variable in the $match expression contains?

Hope this helps!

1 Like

Thank you, @amyjian. Your solution works perfectly. As for the query variable, this is what it contains.

const query = {

userId: new ObjectId(userId),

};

Thank you once again.

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.