MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Concat stringa per gruppo

Puoi farlo con il framework di aggregazione come un'operazione "in due passaggi". Che consiste nell'accumulare prima gli elementi in un array tramite $push con un $group pipeline e quindi per utilizzare $concat con $reduce sull'array prodotto nella proiezione 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"]
            }
          }
        }
      }
    }
  }}
])

Applichiamo anche $cond qui per evitare di concatenare una stringa vuota con una virgola nei risultati, in modo che assomigli più a un elenco delimitato.

Cordiali saluti, c'è un problema JIRA SERVER-29339 che richiede $reduce da implementare come espressione accumulatore per consentirne l'utilizzo direttamente in un $group fase del gasdotto. È improbabile che accada presto, ma in teoria sostituirà $push in quanto sopra e rendere l'operazione un'unica fase della pipeline. La sintassi di esempio proposta riguarda il problema JIRA.

Se non hai $reduce (richiede MongoDB 3.4) quindi è sufficiente pubblicare il cursore:

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

Il che poi porta all'altra alternativa di farlo usando mapReduce se proprio devi:

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 } }
)

Che ovviamente esce nello specifico mapReduce forma di _id e value come il set di chiavi, ma è fondamentalmente l'output.

Usiamo [].concat.apply([],values.map(...)) perché l'output del "riduttore" può essere una "stringa delimitata" perché mapReduce lavora in modo incrementale con grandi risultati e quindi l'uscita del riduttore può diventare "input" in un altro passaggio. Quindi dobbiamo aspettarci che ciò possa accadere e trattarlo di conseguenza.