Querying localized data in MongoDB

一笑奈何 提交于 2020-12-12 04:03:54

问题


Having data 'localized' like this:

{
  "cs": [
    {
      "cID": "00001",
      "title": {
        "en": "title en1",
        "de": "title de1"
      },
      "desc": {
        "en": "desc en1",
        "de": "desc de1"
      },
      "startDate": 20210801,
      "endDate": 20210809,
      "numDays": 8
    },
    {
      "cID": "00002",
      "title": {
        "en": "title en2",
        "de": "title de2"
      },
      "desc": {
        "en": "desc en2",
        "de": "desc de2"
      },
      "startDate": 20210701,
      "endDate": 20210715,
      "numDays": 14
    }
  ]
}

What would be the best way to query this taking user locale (passed to query as param) into account? For example, if "en" was passed, the query should return this for "cID": "00001" :

    {
      "cID": "00001",
      "title": "title en1",
      "desc": "desc en1",
      "startDate": 20210801,
      "endDate": 20210809,
      "numDays": 8
    }

Ideally, the query should be 'generic' and not filter specifically the 'title' and 'desc' objects.

I know that:

db.cs.find({
  "cID": "00001"
},
{
  "_id": 0,
  "cID": 1,
  "title": "$title.en",
  "desc": "$desc.en",
  "startDate": 1,
  "endDate": 1,
  "numDays": 1
})

will give me:

[
  {
    "cID": "00001",
    "desc": "desc en1",
    "endDate": 2.0210809e+07,
    "numDays": 8,
    "startDate": 2.0210801e+07,
    "title": "title en1"
  }
]

but it would be tricky to handle it with many locales and different data models in different queries.

Mongo playground: https://mongoplayground.net/p/9erh-VYiOO4


回答1:


You can try,

  • $reduce to iterate loop of title array, $cond to match local and return match result to value, same process for description array
 {
    $addFields: {
      title: {
        $reduce: {
          input: "$title",
          initialValue: "",
          in: {
            $cond: [{ $eq: ["$$this.locale", "pl"] }, "$$this.value", "$$value"]
          }
        }
      },
      description: {
        $reduce: {
          input: "$description",
          initialValue: "",
          in: {
            $cond: [{ $eq: ["$$this.locale", "pl"] }, "$$this.value", "$$value"]
          }
        }
      }
    }
  }

for the generic option you can make a function with two parameters first one is input field with $ sign and second one is locale:

function languageFilter(inputField, locale) {
  return {
    $reduce: {
      input: inputField,
      initialValue: "",
      in: {
        $cond: [{ $eq: ["$$this.locale", locale] }, "$$this.value", "$$value"]
      }
    }
  };
}

Your final query would be:

let locale = "pl";
db.cs.aggregate([
  { $match: { cID: "00001" } },
  {
    $addFields: {
      title: languageFilter("$title", locale)
      description: languageFilter("$description", locale)
    }
  }
]);



回答2:


To create a generic query one would need the aggregation framework as it has some handy operators to help you with this. To start with, you would need to convert the embedded document into array of key/value pairs and then filter the array on the key field passing in the locale as the parameter.

For example, convert the document

  "title": {
    "en": "title en2",
    "de": "title de2"
  },

to an array

  "title": [
    { "k": "en", '"v": "title en2" },
    { "k": "de", "v": "title de2" }
  ],

using $objectToArray operator. You can then filter this array on the key field using the $filter operator as

{
    '$filter': {
        'input': { '$objectToArray': '$title' },
        'cond': { '$eq': ['$$this.k', locale] }
    }
}

where the variable locale is derived from the passed parameter.

Once you have the filtered array, getting the value field requires the $arrayElemAt operator applied on the value key as

{ 
    '$arrayElemAt': ['$title.v', 0]
}

So in the end you will have to run a pipeline like this:

var locale = 'en';

db.cs.aggregate([
    { '$match': { "cID" : "00001" } },
    { '$addFields': {
        'title': {
            '$filter': {
                'input': { '$objectToArray': '$title' },
                'cond': { '$eq': ['$$this.k', locale] }
            }
        },
        'desc': {
            '$filter': {
                'input': { '$objectToArray': '$desc' },
                'cond': { '$eq': ['$$this.k', locale] }
            }
        }
    } },

    { '$addFields': {
        'title': { 
            '$arrayElemAt': ['$title.v', 0]
        },
        'desc': { 
            '$arrayElemAt': ['$desc.v', 0]
        }
    } }
]);

And with some refactoring:

var locale = 'en';
var getFilterOperatorExpression = function (field) {
    return {
        '$filter': {
            'input': { '$objectToArray': '$'+ field },
            'cond': { '$eq': ['$$this.k', locale] }
        }
    }
};
var getValueOperatorExpression = function (field) { 
    return { 
        '$arrayElemAt': ['$'+ field +'.v', 0]
    }
};

db.cs.aggregate([
    { '$match': { "cID" : "00001" } },
    { '$addFields': {
        'title': getFilterOperatorExpression('title'),
        'desc': getFilterOperatorExpression('desc'),
    } },

    { '$addFields': {
        'title': getValueOperatorExpression('title'),
        'desc': getValueOperatorExpression('desc')
    } }
]);


来源:https://stackoverflow.com/questions/64742175/querying-localized-data-in-mongodb

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!