MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Concat String por Grupo


Você pode fazer isso com a estrutura de agregação como uma operação de "duas etapas". Que é primeiro acumular os itens em um array via $push dentro de um $group pipeline e, em seguida, usar $concat com $reduce no array produzido na projeção final:
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"]
            }
          }
        }
      }
    }
  }}
])

Também aplicamos $cond aqui para evitar a concatenação de uma string vazia com uma vírgula nos resultados, para que pareça mais uma lista delimitada.

Para sua informação, há um problema no JIRA SERVER-29339 que pede $reduce a ser implementado como uma expressão de acumulador para permitir seu uso diretamente em um $group estágio de tubulação. Não é provável que aconteça tão cedo, mas teoricamente substituiria $push acima e tornar a operação um único estágio de pipeline. A sintaxe proposta de amostra está no problema do JIRA.

Se você não tiver $reduce (requer MongoDB 3.4) então apenas post processe o cursor:
db.collection.aggregate([
  { "$group": {
    "_id": "$tag_id",
    "client_id": { "$push": "$client_id" }
  }},
]).map( doc =>
  Object.assign(
    doc,
   { "client_id": doc.client_id.join(",") }
  )
)

O que leva à outra alternativa de fazer isso usando mapReduce se você realmente deve:
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 } }
)

O que, obviamente, gera no mapReduce específico forma de _id e value como o conjunto de chaves, mas é basicamente a saída.

Usamos [].concat.apply([],values.map(...)) porque a saída do "redutor" pode ser uma "string delimitada" porque mapReduce funciona incrementalmente com grandes resultados e, portanto, a saída do redutor pode se tornar "entrada" em outra passagem. Portanto, precisamos esperar que isso possa acontecer e tratá-lo de acordo.