MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Concaténer des valeurs de chaîne dans un tableau dans un seul champ dans MongoDB

Dans les versions modernes de MongoDB, vous le pouvez. Vous ne pouvez toujours pas appliquer "directement" un tableau à $concat , mais vous pouvez utiliser $reduce pour travailler avec les éléments du tableau et produire ceci :

db.collection.aggregate([
  { "$addFields": {
    "values": { 
      "$reduce": {
        "input": "$values",
        "initialValue": "",
        "in": {
          "$cond": {
            "if": { "$eq": [ { "$indexOfArray": [ "$values", "$$this" ] }, 0 ] },
            "then": { "$concat": [ "$$value", "$$this" ] },
            "else": { "$concat": [ "$$value", "_", "$$this" ] }
          }    
        }
      }        
    }
  }}
])

En combinant bien sûr avec $indexOfArray pour ne pas "concaténer" avec le "_" soulignement lorsqu'il s'agit du "premier" index du tableau.

De plus, mon "souhait" supplémentaire a été répondu avec $sum :

db.collection.aggregate([
  { "$addFields": {
    "total": { "$sum": "$items.value" }
  }}
])

Ce genre de problème est un peu soulevé en général avec les opérateurs d'agrégation qui prennent un tableau d'éléments. La distinction ici est qu'il s'agit d'un "tableau" d'"arguments" fournis dans la représentation codée par opposition à un "élément de tableau" présent dans le document courant.

La seule façon de vraiment faire le type de concaténation d'éléments dans un tableau présent dans le document est de faire une sorte d'option JavaScript, comme avec cet exemple dans mapReduce :

db.collection.mapReduce(
    function() {
        emit( this._id, { "values": this.values.join("_") } );
    },
    function() {},
    { "out": { "inline": 1 } }
)

Bien sûr, si vous n'agrégez rien, la meilleure approche consiste peut-être simplement à effectuer cette opération de "joindre" dans votre code client lors du post-traitement des résultats de votre requête. Mais s'il doit être utilisé à certaines fins dans tous les documents, mapReduce sera le seul endroit où vous pourrez l'utiliser.

Je pourrais ajouter que "par exemple" j'aimerais que quelque chose comme ça fonctionne :

{
    "items": [
        { "product": "A", "value": 1 },
        { "product": "B", "value": 2 },
        { "product": "C", "value": 3 }
    ]
}

Et dans l'ensemble :

db.collection.aggregate([
    { "$project": {
        "total": { "$add": [
            { "$map": {
                "input": "$items",
                "as": "i",
                "in": "$$i.value"
            }}
        ]}
    }}
])

Mais cela ne fonctionne pas de cette façon car $add attend des arguments par opposition à un tableau du document. Soupir! :(. Une partie du raisonnement "par conception" pour cela pourrait être soutenu que "juste parce que" c'est un tableau ou une "liste" de valeurs singulières transmises à partir du résultat de la transformation, il n'est pas "garanti" que celles-ci soient en fait "valides" les valeurs de type numérique singulier que l'opérateur attend. Du moins pas avec les méthodes actuellement implémentées de "vérification de type".

Cela signifie que pour l'instant nous devons encore faire ceci :

db.collection.aggregate([
   { "$unwind": "$items" },
   { "$group": {
       "_id": "$_id",
        "total": { "$sum": "$items.value" }
   }}
])

Et aussi, malheureusement, il n'y aurait aucun moyen d'appliquer un tel opérateur de regroupement pour concaténer des chaînes non plus.

Vous pouvez donc espérer une sorte de changement à ce sujet, ou espérer un changement qui permette de modifier une variable à portée externe dans le cadre d'un $map opération en quelque sorte. Mieux encore un nouveau $join opération serait également la bienvenue. Mais ceux-ci n'existent pas au moment de la rédaction et n'existeront probablement pas avant un certain temps.