Enhance Your Search Capabilities with MongoDB Atlas: Advanced Querying Techniques
Tags: mongodb, database, tutorial, nosql
Published: 2023-07-02 18:09:43

Welcome back to the second part of our blog series on MongoDB Atlas Full-Text Search. In the first part, "[From Indexing to Querying: How MongoDB's Full-Text Search Stacks Up Against Elasticsearch](https://dev.to/utsaw/from-indexing-to-querying-how-mongodbs-full-text-search-stacks-up-against-elasticsearch-51mp)," we explored the fundamentals of MongoDB's full-text search and compared it to Elasticsearch. If you haven't read it yet, we highly recommend starting there to get a comprehensive understanding of MongoDB's full-text search capabilities. In this blog post, we will dive deeper into MongoDB Atlas Full-Text Search and explore advanced querying techniques that will enable you to take full advantage of its powerful features.


## Understanding Compound Queries.

Compound queries are very important and powerful when it comes to real-world use cases.
A compound operator in MongoDB combines multiple operators to form a single query. Each part of the compound query is referred to as a clause, and each clause contains one or more sub-queries.

A compound query has the following **syntax**:

```
{
  $search: {
    "index": <index name>, // optional, defaults to "default"
    "compound": {
      <must | mustNot | should | filter>: [ { <clauses> } ],
      "score": <options>
    }
  }
}
```
**Explanation**: 

> Each must, mustNot, should, and filter clause contains an array of subclauses. Use array syntax even if the array contains only one subclause.


We required the following terms to construct the query, which we also explained above - 

**Document** for explaining all the queries - 

```
{
  "_id" : 1,
  "type" : "apple",
  "description" : "Apples come in several varieties, including Fuji, Granny Smith, and Honeycrisp.",
  "category" : "nonorganic",
  "in_stock" : false
},
{
  "_id" : 2,
  "type" : "banana",
  "description" : "Bananas are usually sold in bunches of five or six.",
  "category" : "nonorganic",
  "in_stock" : true
},
{
  "_id" : 3,
  "type" : "pear",
  "description" : "Bosc and Bartlett are the most common varieties of pears.",
  "category" : "organic",
  "in_stock" : true
}
```

**Operators** - 

- **must**
Clauses that must match for a document to be included in the results. The returned score is the sum of the scores of all the subqueries in the clause.

Maps to the AND boolean operator.

**Example:** 

```
db.col.aggregate([
  {
    $search: {
      "compound": {
        "must": [{
          "text": {
            "query": "varieties",
            "path": "description"
          }
        }]
      }
    }
  }
])
```
**Explanation** : 

> In the above query we are searching for the term `Varieties` in the field `description` which is called `path` in the query, and this query will return only those document which contains this - `Varieties` term in the field - `description`.


- **mustNot**
Clauses that must not match for a document to be included in the results. mustNot clauses don't contribute to a returned document's score.

Maps to the AND NOT boolean operator.

**Example:**

```
db.col.aggregate([
  {
    $search: {
      "compound": {
        "mustNot": [{
          "text": {
            "query": "apples",
            "path": "description"
          }
        }]
      }
    }
  }
])
```
**Explanation** : 

> In the above query we are searching for the data which do not contains apples in the field description, so this query will return only those documents which do not have apples in the field description.

- **should**
Clauses that you prefer to match in documents that are included in the results. Documents that contain a match for a should clause have higher scores than documents that don't contain a should clause. The returned score is the sum of the scores of all the subqueries in the clause.


If you use more than one should clause, you can use the minimumShouldMatch option to specify a minimum number of should clauses that must match to include a document in the results. If omitted, the minimumShouldMatch option defaults to 0.

Maps to the OR boolean operator.

**Example:**

```
db.col.aggregate([
  {
    $search: {
      "compound": {
        "Should": [{
          "text": {
            "query": "apples",
            "path": "description"
          }
        }]
      }
    }
  }
])
```
**Explanation** : 

> In the above query we are searching for the data which contains apples in the field description, so this query will return those documents which have apples in the field description.

for a better understanding of this Should operator which is the OR operator also, here is a second example - 

```
db.col.aggregate([
  {
    $search: {
      "compound": {
        "Should": [{
          "text": {
            "query": "apples",
            "path": "description"
          },
          "text": {
            "query": "Banana",
            "path": "description"
          }
        }],
          "minimumShouldMatch": 1
      }
    }
  }
])
```
**Explanation** : 

> In the above query we are searching for the data which contains `apples` OR `Banana` in the field `description`, so this `query` will return those documents which have either apples or Banana in the field `description`, also if you noticed we added `"minimumShouldMatch": 1` in this query, `minimumShouldMatch` means specify a minimum number of should clauses that must match to include a document in the results(the given term has to occur specific times whatever we pass as a parameter, `default` it is 0)

- **filter**
Clauses must all match for a document to be included in the results. filter clauses do not contribute to a returned document's score.

in other words, it is a replacement of $in operator in full-text search.

> filter behaves the same as must, except that the filter clause is not considered in a returned document's score, and therefore does not affect the order of the returned documents.

**Example:**

```
$search: {
  "compound": {
    "filter": [{
      "text": {
        "query": ["CLIENT", "PROFESSIONAL"],
        "path": "role"
      }
    }]
  }
}
```


Now, as we understand all the important operators of full-text search, here a an example of a query which combines all these operators to get the result.

**Query** - 

```
db.col.aggregate([
  {
    $search: {
      "compound": {
        "must": [{
          "text": {
             "query": "varieties",
             "path": "description"
          }
        }], 
        "mustNot": [{
          "text": {
            "query": "apples",
            "path": "description"
          }
        }],
        "should": [
          {
            "text": {
              "query": "Fuji",
              "path": "description"
            }
          },
          {
            "text": {
              "query": "Golden Delicious",
              "path": "description"
            }
          }],
          "minimumShouldMatch": 2
        }
      }
    }
])
```

**Explanation:**

> The following example uses a combination of `must`, `mustNot`, `Should `clauses to construct a `query`. The `must `clause uses the text operator to `search `for the term `varieties `in the description field. For a document to match, it must fulfil the `must `clause. The `mustNot `clause performs a `search `operation for the term `apples `in the `description `field. For a document to match, it must not fulfil the `mustNot `clause. The `Should` clause uses the text operator to `search `for the term `Fuji` or `Golden Delicious` in the description field. For a document to match either of them at least 2 times i.e. `minimumShouldMatch` we used, it will return any terms or both terms given if it occurs more than 1.

I hope you find this blog post informative and helpful as you dive deeper into MongoDB Atlas Full-Text Search. Happy querying!