sql >> Base de Datos >  >> NoSQL >> MongoDB

Documento de devolución con subdocumento máximo

En primer lugar, mostremos sus datos de manera que las personas puedan usarlos y producir el resultado deseado:

{ 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' } ],
}

Tenga en cuenta que el "when" de hecho, las fechas son diferentes, por lo que habrá un $max valor y no son lo mismo. Ahora podemos repasar los casos

Caso 1:obtenga el "singular" $max valor

El caso básico aquí es usar $arrayElemAt y $indexOfArray operadores para devolver la coincidencia $max valor:

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$arrayElemAt": [
        "$change",
        { "$indexOfArray": [
          "$change.when",
          { "$max": "$change.when" }
        ]}
      ]
    }
  }}
])

Devoluciones:

{
        "_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"
        }
}

Básicamente el "$max": "$change.when" devuelve el valor que es el "máximo" dentro de esa matriz de valores. Luego, encuentra el "índice" coincidente de esa matriz de valores a través de $indexOfArray que devuelve el primero índice coincidente encontrado. Esa posición de "índice" (en realidad solo de una matriz de "when" valores transpuestos en el mismo orden) se usa con $arrayElemAt para extraer el "objeto completo" del "change" matriz en la posición de índice especificada.

Caso 2:devolver el "múltiple" $max entradas

Más o menos lo mismo con $max , excepto que esta vez $filter para devolver el múltiplo "posible" valores que coinciden con $max valor:

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$filter": {
        "input": "$change",
        "cond": {
          "$eq": [ "$$this.when", { "$max": "$change.when" } ]
        }
      }       
    }
  }}
])

Devoluciones:

{
        "_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"
                }
        ]
}

Entonces el $max es, por supuesto, lo mismo, pero esta vez el valor singular devuelto por ese operador se usa en un $eq comparación dentro de $filter . Esto inspecciona cada elemento de la matriz y mira el actual "when" valor ( "$$this.when" ). Donde "igual" luego se devuelve el elemento.

Básicamente lo mismo que el primer enfoque pero con la excepción de que $filter permite "múltiples" elementos a devolver. Por lo tanto todo con el mismo $max valor.

Caso 3 - Ordenar previamente el contenido de la matriz.

Ahora puede notar que en los datos de ejemplo que incluí (adaptados de los suyos pero con una fecha "máxima" real), el valor "máximo" es de hecho el último valor en la matriz. Esto puede suceder naturalmente como resultado de que $push (por defecto) "añade" hasta el final del contenido de la matriz existente. Así que "más nuevo" las entradas tenderán a estar al final de la matriz.

Esto, por supuesto, es el predeterminado comportamiento, pero hay buenas razones por las que "puede" quiero cambiar eso. En resumen lo mejor manera de obtener el "más reciente" la entrada de la matriz es, de hecho, devolver el primer elemento de la matriz.

Todo lo que necesita hacer es asegurarse de que el "más reciente" en realidad se agrega primero en lugar de último . Hay dos enfoques:

  1. Utilice $position para "preestablecer" los elementos de la matriz: Este es un modificador simple para $push utilizando el 0 posición para agregar siempre al frente :

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [{
              "version": 3,
              "who": "ATL User",
              "when": new Date(),
              "what": "Another change"
            }],
            "$position": 0
          }
       }}
    )
    

    Esto cambiaría el documento a:

    {
        "_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"
                }
        ]
    }
    

Tenga en cuenta que esto requeriría que realmente vaya e "invierta" todos los elementos de su matriz de antemano para que el "más nuevo" ya esté al frente para que se mantenga el orden. Afortunadamente, esto está algo cubierto en el segundo enfoque...

  1. Utilice $sort para modificar los documentos en orden en cada $push : Y este es el otro modificador que en realidad "reordena" atómicamente en cada adición de un nuevo elemento. El uso normal es básicamente el mismo con cualquier elemento nuevo para $each como arriba, o incluso simplemente una matriz "vacía" para aplicar el $sort solo a datos existentes:

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [],
            "$sort": { "when": -1 } 
          }
       }}
    )
    

    Resultados en:

    {
            "_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"
                    }
            ]
    }
    

    Puede tomar un minuto comprender por qué $push para $sort una matriz como esta, pero la intención general es cuando se pueden realizar modificaciones en una matriz que "alteran" una propiedad como Date valor que se está clasificando y usaría tal declaración para reflejar esos cambios. O simplemente agregue nuevos elementos con $sort y deja que funcione.

Entonces, ¿por qué "almacenar" la matriz ordenada así? Como se mencionó anteriormente, desea el primero elemento como el "más reciente" , y luego la consulta a devolver que simplemente se convierte en:

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true }
  },
  { "change": { "$slice": 1 } }
)

Devoluciones:

{
        "_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"
                }
        ]
}

Así que $slice luego se puede usar solo para extraer elementos de la matriz mediante índices conocidos. Técnicamente puedes usar -1 allí para devolver el último elemento de la matriz de todos modos, pero el reordenamiento donde el más reciente es primero permite otras cosas como confirmar que la última modificación fue realizada por un determinado usuario y/u otras condiciones como una restricción de rango de fechas. es decir:

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

Notando aquí que algo como "change.-1.when" es una declaración ilegal, que es básicamente la razón por la que reordenamos la matriz para que pueda usar la legal 0 para primero en lugar de -1 para último .

Conclusión

Por lo tanto, hay varias cosas diferentes que puede hacer, ya sea utilizando el enfoque de agregación para filtrar el contenido de la matriz o mediante formularios de consulta estándar después de realizar alguna modificación en la forma en que se almacenan los datos. Cuál usar depende de sus propias circunstancias, pero debe tener en cuenta que cualquiera de los formularios de consulta estándar se ejecutará notablemente más rápido que cualquier manipulación a través del marco de agregación o cualquier operador calculado.