Query sui dati localizzati in MongoDB

Nov 09 2020

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

1 turivishal Nov 21 2020 at 21:44

Puoi provare,

  • $reduceper iterare il ciclo titledell'array, $condper abbinare locale 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)
    }
  }
]);
2 chridam Nov 09 2020 at 04:02

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')
    } }
]);