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

Devolver documento com subdocumento máximo


Primeiro, vamos mostrar seus dados de uma maneira que as pessoas possam usá-los e produzir o resultado desejado:
{ value: 1,
  _id: ObjectId('5cb9ea0c75c61525e0176f96'),
  name: 'Test',
  category: 'Development',
  subcategory: 'Programming Languages',
  status: 'Supported',
  description: 'Test',
  change:
   [ { version: 1,
       who: 'ATL User',
       when: new Date('2019-04-19T15:30:39.912Z'),
       what: 'Item Creation' },
     { version: 2,
       who: 'ATL Other User',
       when: new Date('2019-04-19T15:31:39.912Z'),
       what: 'Name Change' } ],
}

Observe que o "quando" as datas são de fato diferentes, então haverá um $max valor e eles não são apenas os mesmos. Agora podemos percorrer os casos

Caso 1 - Buscar o "singular" $max valor


O caso básico aqui é usar o $arrayElemAt e $indexOfArray operadores para retornar o $max correspondente valor:
db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$arrayElemAt": [
        "$change",
        { "$indexOfArray": [
          "$change.when",
          { "$max": "$change.when" }
        ]}
      ]
    }
  }}
])

Devoluções:
{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : {
                "version" : 2,
                "who" : "ATL Other User",
                "when" : ISODate("2019-04-19T15:31:39.912Z"),
                "what" : "Name Change"
        }
}

Basicamente o "$max":"$change.when" retorna o valor que é o "máximo" de dentro dessa matriz de valores. Em seguida, você encontra o "índice" correspondente dessa matriz de valores por meio de $indexOfArray que retorna o primeiro índice correspondente encontrado. Essa posição de "índice" (na verdade, apenas uma matriz de "quando" valores transpostos na mesma ordem ) é então usado com $arrayElemAt para extrair o "objeto inteiro" do "change" array na posição de índice especificada.

Caso 2 - Retornar o "múltiplo" $max entradas


Praticamente a mesma coisa com $max , exceto que desta vez nós $filter para retornar os múltiplos "possíveis" valores correspondentes a $max valor:
db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$filter": {
        "input": "$change",
        "cond": {
          "$eq": [ "$$this.when", { "$max": "$change.when" } ]
        }
      }       
    }
  }}
])

Devoluções:
{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
}

Portanto, o $max é obviamente o mesmo, mas desta vez o valor singular retornado por esse operador é usado em um $eq comparação dentro de $filter . Isso inspeciona cada elemento da matriz e examina o atual "quando" valor ( "$$this.when" ). Onde "igual" então o elemento é retornado.

Basicamente o mesmo que a primeira abordagem, mas com a exceção de que $filter permite "múltiplos" elementos a serem devolvidos. Portanto, tudo com o mesmo $max valor.

Caso 3 - Pré-classifique o conteúdo do array.


Agora você pode notar que nos dados de exemplo que incluí (adaptado do seu próprio, mas com uma data "max" real) o valor "max" é de fato o último valor na matriz. Isso pode acontecer naturalmente como resultado de $push (por padrão) "anexar" até o final do conteúdo do array existente. Então "mais novo" as entradas tenderão a estar no final da matriz.

É claro que este é o padrão comportamento, mas há boas razões pelas quais você "pode" quer mudar isso. Resumindo, o melhor maneira de obter o "mais recente" a entrada do array é de fato retornar o primeiro elemento da matriz.

Tudo o que você realmente precisa fazer é garantir o "mais recente" é realmente adicionado primeiro em vez de último . Existem duas abordagens:

  1. Use $position para "pré-pender" itens da matriz: Este é um modificador simples para $push usando o 0 posição para sempre adicionar à frente :
    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [{
              "version": 3,
              "who": "ATL User",
              "when": new Date(),
              "what": "Another change"
            }],
            "$position": 0
          }
       }}
    )
    

    Isso mudaria o documento para:
    {
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                },
                {
                        "version" : 1,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-19T15:30:39.912Z"),
                        "what" : "Item Creation"
                },
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
    }
    

Observe que isso exigiria que você realmente "invertasse" todos os seus elementos de matriz de antemão para que o "mais novo" já estivesse na frente, para que a ordem fosse mantida. Felizmente, isso é um pouco coberto na segunda abordagem ...

  1. Use $sort para modificar os documentos em ordem em cada $push : E este é o outro modificador que realmente "reordena" atomicamente em cada nova adição de item. O uso normal é basicamente o mesmo com quaisquer novos itens para $each como acima, ou mesmo apenas uma matriz "vazia" para aplicar o $sort apenas para dados existentes:
    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [],
            "$sort": { "when": -1 } 
          }
       }}
    )
    

    Resulta em:
    {
            "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
            "value" : 1,
            "name" : "Test",
            "category" : "Development",
            "subcategory" : "Programming Languages",
            "status" : "Supported",
            "description" : "Test",
            "change" : [
                    {
                            "version" : 3,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-20T02:40:30.024Z"),
                            "what" : "Another change"
                    },
                    {
                            "version" : 2,
                            "who" : "ATL Other User",
                            "when" : ISODate("2019-04-19T15:31:39.912Z"),
                            "what" : "Name Change"
                    },
                    {
                            "version" : 1,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-19T15:30:39.912Z"),
                            "what" : "Item Creation"
                    }
            ]
    }
    

    Pode levar um minuto para entender por que você $push para $sort uma matriz como esta, mas a intenção geral é quando modificações podem ser feitas em uma matriz que "alteram" uma propriedade como Data valor que está sendo classificado e você usaria essa declaração para refletir essas alterações. Ou, na verdade, apenas adicione novos itens com o $sort e deixe dar certo.

Então, por que "loja" a matriz ordenada assim? Como mencionado anteriormente, você deseja o primeiro item como o "mais recente" , e, em seguida, a consulta para retornar que simplesmente se torna:
db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true }
  },
  { "change": { "$slice": 1 } }
)

Devoluções:
{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                }
        ]
}

Portanto, o $slice pode então ser usado apenas para extrair itens da matriz por índices conhecidos. Tecnicamente, você pode usar apenas -1 lá para retornar o último item da matriz de qualquer maneira, mas a reordenação onde o mais recente é o primeiro permite outras coisas, como confirmar que a última modificação foi feita por um determinado usuário e/ou outras condições, como uma restrição de intervalo de datas. ou seja:
db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true },
    "change.0.who": "ATL User",
    "change.0.when": { "$gt": new Date("2018-04-01") }
  },
  { "change": { "$slice": 1 } }
)

Observando aqui que algo como "change.-1.when" é uma declaração ilegal, e é basicamente por isso que reordenamos o array para que você possa usar o legal 0 para primeiro em vez de -1 para último .

Conclusão


Portanto, há várias coisas diferentes que você pode fazer, seja usando a abordagem de agregação para filtrar o conteúdo da matriz ou por meio de formulários de consulta padrão após fazer algumas modificações na forma como os dados são realmente armazenados. Qual usar depende de suas próprias circunstâncias, mas deve-se observar que qualquer um dos formulários de consulta padrão será executado notavelmente mais rápido do que qualquer manipulação por meio da estrutura de agregação ou de quaisquer operadores computados.