[SERVER-4633] Retrieve only the first matched object Created: 06/Jan/12  Updated: 06/Dec/22  Resolved: 04/Dec/17

Status: Closed
Project: Core Server
Component/s: Querying
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Minor - P4
Reporter: Rodrigo Coelho Assignee: Backlog - Query Team (Inactive)
Resolution: Done Votes: 0
Labels: conditional_operator, query
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Assigned Teams:
Query
Backwards Compatibility: Fully Compatible
Participants:

 Description   

This is a proposal for a new feature that makes it possible to limit a result to only one object.
The limit is not applied via an standard sorting, but using a conditional operator with a list of parameters in decreasing priority.
Conditional operators like $in and $or will return only the first matched object.
The object can be a document or an array item.


The use case: multilanguage documents with support for fallback to a default language.

For this document:

{
    "_id": 1,
    "author": "John",
    "languages": [
        {
            "code": "en",
            "data": {
                "title": "Title",
                "contents": "My text."
            }
        },
        {
            "code": "fr",
            "data": {
                "title": "Le titre",
                "contents": "Mon texte."
            }
        },
        {
            "code": "it",
            "data": {
                "title": "Titolo",
                "contents": "Il mio testo."
            }
        }
    ]
}

I'd like to query conditionally for the user (website visitor) language, with a fallback to the default (app) language, but returning only one of them.
For that, I'd propose the new conditional operators $inFirst and $orFirst. They would return only the first match, evaluated from the left to the right.
I'll use $inFirst in the example below.

Suppose user's language is 'fr' and app default language is 'it'.

db.col.find( { _id:1, 'languages.code': { $inFirst: [ 'fr', 'it' ] } }, { author: 1, 'languages.$': 1 } )

Please note that the query features a (hopefully) future feature specified in SERVER-828.

Expected result:

{
    "_id": 1,
    "author": "John",
    "languages": [
        {
            "code": "fr",
            "data": {
                "title": "Le titre",
                "contents": "Mon texte."
            }
        }
    ]
}

I hope that this can be implemented without issues with indexes, parallel processing and sharded setups.



 Comments   
Comment by Asya Kamsky [ 04/Dec/17 ]

Here's the code to do it given sample document schema:

db.lang2.aggregate({$addFields:{lang:
   {$let:{
     vars:{prefLang:{$arrayElemAt:[
         {$filter:{input:"$languages",cond:{$eq:["$$this.code","fr"]}}},
         0
     ]}}, 
     in:{$ifNull:[ 
         "$$prefLang", 
         {$arrayElemAt:[{$filter:{input:"$languages",cond:{$eq:["$$this.code","en"]}}},0]}
     ]}
   }}
}})

Replace "fr" string with string representing preferred language.

Comment by Asya Kamsky [ 04/Dec/17 ]

This can already be achieved with Aggregation framework $project or $addFields using expressions like $cond or $filter.

Also related to this is SERVER-17038 which is trying to use $redact, but with objects stored in flat array $redact is not necessary and it can be done with $filter.

Comment by Rodrigo Coelho [ 06/Jan/12 ]

For more flexibility for use cases that require more than one result, $inFirst and $orFirst could have an optional parameter specifying how many objects to match.
Not sure how the positional operator would behave in this case, though.

Generated at Thu Feb 08 03:06:32 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.