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

Concat String par groupe

Vous pouvez le faire avec le cadre d'agrégation en tant qu'opération "en deux étapes". Qui consiste à accumuler d'abord les éléments dans un tableau via $push avec un $group pipeline, puis d'utiliser $concat avec $reduce sur le tableau produit en projection finale :

db.collection.aggregate([
  { "$group": {
    "_id": "$tag_id",
    "client_id": { "$push": "$client_id" }
  }},
  { "$addFields": {
    "client_id": {
      "$reduce": {
        "input": "$client_id",
        "initialValue": "",
        "in": {
          "$cond": {
            "if": { "$eq": [ "$$value", "" ] },
            "then": "$$this",
            "else": {
              "$concat": ["$$value", ",", "$$this"]
            }
          }
        }
      }
    }
  }}
])

Nous appliquons également $cond ici pour éviter de concaténer une chaîne vide avec une virgule dans les résultats, afin qu'elle ressemble davantage à une liste délimitée.

Pour votre information, il y a un problème JIRA SERVER-29339 qui demande $reduce à implémenter en tant qu'expression d'accumulateur pour permettre son utilisation directement dans un $group étape du pipeline. Il est peu probable que cela se produise de sitôt, mais cela remplacerait théoriquement $push dans ce qui précède et faire de l'opération une seule étape du pipeline. L'exemple de syntaxe proposé se trouve sur le problème JIRA.

Si vous n'avez pas $reduce (nécessite MongoDB 3.4) puis post-traitez simplement le curseur :

db.collection.aggregate([
  { "$group": {
    "_id": "$tag_id",
    "client_id": { "$push": "$client_id" }
  }},
]).map( doc =>
  Object.assign(
    doc,
   { "client_id": doc.client_id.join(",") }
  )
)

Ce qui conduit ensuite à l'autre alternative consistant à le faire en utilisant mapReduce si vous devez vraiment :

db.collection.mapReduce(
  function() {
    emit(this.tag_id,this.client_id);
  },
  function(key,values) {
    return [].concat.apply([],values.map(v => v.split(","))).join(",");
  },
  { "out": { "inline": 1 } }
)

Qui sort bien sûr dans le mapReduce spécifique forme de _id et value comme jeu de clés, mais il s'agit essentiellement de la sortie.

Nous utilisons [].concat.apply([],values.map(...)) car la sortie du "réducteur" peut être une "chaîne délimitée" car mapReduce fonctionne de manière incrémentielle avec de grands résultats et donc la sortie du réducteur peut devenir "entrée" lors d'une autre passe. Nous devons donc nous attendre à ce que cela puisse arriver et le traiter en conséquence.