Query sui dati localizzati in MongoDB
Avere dati "localizzati" in questo modo:
{
"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
}
]
}
Quale sarebbe il modo migliore per interrogarlo tenendo conto delle impostazioni locali dell'utente (passate alla query come parametro)? Ad esempio, se è stato passato "en", la query dovrebbe restituire questo per "cID": "00001":
{
"cID": "00001",
"title": "title en1",
"desc": "desc en1",
"startDate": 20210801,
"endDate": 20210809,
"numDays": 8
}
Idealmente, la query dovrebbe essere "generica" e non filtrare in modo specifico gli oggetti "title" e "desc".
Lo so:
db.cs.find({
"cID": "00001"
},
{
"_id": 0,
"cID": 1,
"title": "$title.en", "desc": "$desc.en",
"startDate": 1,
"endDate": 1,
"numDays": 1
})
mi darà:
[
{
"cID": "00001",
"desc": "desc en1",
"endDate": 2.0210809e+07,
"numDays": 8,
"startDate": 2.0210801e+07,
"title": "title en1"
}
]
ma sarebbe difficile gestirlo con molte impostazioni locali e diversi modelli di dati in query diverse.
Parco giochi Mongo: https://mongoplayground.net/p/9erh-VYiOO4
Risposte
Puoi provare,
$reduceper iterare il ciclotitledell'array,$condper abbinarelocale restituire il risultato della corrispondenza con il valore, lo stesso processo per l'array di descrizione
{
$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"]
}
}
}
}
}
per l'opzione generica è possibile creare una funzione con due parametri il primo è il campo di input con $segno e il secondo è locale:
function languageFilter(inputField, locale) {
return {
$reduce: { input: inputField, initialValue: "", in: { $cond: [{ $eq: ["$$this.locale", locale] }, "$$this.value", "$$value"]
}
}
};
}
La tua domanda finale sarebbe:
let locale = "pl";
db.cs.aggregate([
{ $match: { cID: "00001" } }, { $addFields: {
title: languageFilter("$title", locale) description: languageFilter("$description", locale)
}
}
]);
Per creare una query generica sarebbe necessario il framework di aggregazione poiché ha alcuni operatori utili per aiutarti in questo. Per cominciare, è necessario convertire il documento incorporato in un array di coppie chiave / valore e quindi filtrare l'array sul campo chiave passando le impostazioni internazionali come parametro.
Ad esempio, converti il documento
"title": {
"en": "title en2",
"de": "title de2"
},
a un array
"title": [
{ "k": "en", '"v": "title en2" },
{ "k": "de", "v": "title de2" }
],
utilizzando l' $objectToArrayoperatore. È quindi possibile filtrare questo array nel campo chiave utilizzando l' $filteroperatore as
{
'$filter': { 'input': { '$objectToArray': '$title' }, 'cond': { '$eq': ['$$this.k', locale] }
}
}
dove la variabile locale è derivata dal parametro passato.
Una volta ottenuto l'array filtrato, ottenere il campo del valore richiede l' $arrayElemAtapplicazione dell'operatore sulla chiave del valore come
{
'$arrayElemAt': ['$title.v', 0]
}
Quindi alla fine dovrai eseguire una pipeline come questa:
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]
}
} }
]);
E con un po 'di 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')
} }
]);