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

Concatenar valores de string em array em um único campo no MongoDB


Nas versões modernas do MongoDB, você pode. Você ainda não pode aplicar "diretamente" uma matriz a $concat , mas você pode usar $reduce para trabalhar com os elementos da matriz e produzir isso:
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" ] }
          }    
        }
      }        
    }
  }}
])

Combinando, é claro, com $indexOfArray para não "concatenar" com o "_" sublinhado quando é o "primeiro" índice da matriz.

Além disso, meu "desejo" adicional foi respondido com $sum :
db.collection.aggregate([
  { "$addFields": {
    "total": { "$sum": "$items.value" }
  }}
])

Esse tipo de coisa aumenta um pouco em geral com operadores de agregação que pegam uma matriz de itens. A distinção aqui é que significa um "array" de "agumentos" fornecidos na representação codificada em oposição a um "elemento de array" presente no documento atual.

A única maneira que você pode realmente fazer o tipo de concatenação de itens dentro de um array presente no documento é fazer algum tipo de opção JavaScript, como neste exemplo em mapReduce:
db.collection.mapReduce(
    function() {
        emit( this._id, { "values": this.values.join("_") } );
    },
    function() {},
    { "out": { "inline": 1 } }
)

É claro que se você não estiver realmente agregando nada, possivelmente a melhor abordagem é simplesmente fazer essa operação de "junção" no código do cliente no pós-processamento dos resultados da consulta. Mas se ele precisar ser usado para algum propósito em documentos, o mapReduce será o único lugar em que você poderá usá-lo.

Eu poderia acrescentar que "por exemplo" eu adoraria que algo assim funcionasse:
{
    "items": [
        { "product": "A", "value": 1 },
        { "product": "B", "value": 2 },
        { "product": "C", "value": 3 }
    ]
}

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

Mas não funciona assim porque $add espera argumentos em oposição a uma matriz do documento. Suspirar! :(. Parte do raciocínio "por design" para isso pode ser argumentado que "só porque" é uma matriz ou "lista" de valores singulares sendo passados ​​do resultado da transformação, não é "garantido" que esses sejam valores de tipo numérico singular "válidos" que o operador espera, pelo menos não nos métodos implementados atualmente de "verificação de tipo".

Isso significa que, por enquanto, ainda temos que fazer isso:
db.collection.aggregate([
   { "$unwind": "$items" },
   { "$group": {
       "_id": "$_id",
        "total": { "$sum": "$items.value" }
   }}
])

E também, infelizmente, não haveria como aplicar esse operador de agrupamento para concatenar strings.

Então você pode esperar por algum tipo de mudança nisso, ou esperar por alguma mudança que permita que uma variável de escopo externo seja alterada dentro do escopo de um $map operação de alguma forma. Melhor ainda, um novo $join operação também seria bem-vinda. Mas estes não existem no momento da escrita, e provavelmente não existirão por algum tempo.