Estoy tratando de generar una agregación jerárquica similar a la siguiente:

 ┌ Product Category 1
 │ ├ Category Aggregate Info
 │ └ Products
 │   ├ Product 1
 │   │ ├ Aggregate Prices
 │   │ ├ Aggregate Quantities Sold
 │   │ └ etc.
 │   └ Product 2
 │     ├ Aggregate Prices
 │     ├ Aggregate Quantities Sold
 │     └ etc.
 │
 └ Product Category 2
   ├ Category Aggregate Info
   └ Products   
     ├ Product 4
     │ ├ Aggregate Prices
     │ ├ Aggregate Quantities Sold
     │ └ etc.
     └ Product 7
       ├ Aggregate Prices
       ├ Aggregate Quantities Sold
       └ etc.

No controlo las categorías de productos (por ejemplo, frutas frente a bebidas) o los productos (por ejemplo, ciruelas frente a agua). Esto me impide codificar estos valores tal como los definen los usuarios.

Solo puedo controlar la jerarquía de los valores usando el orden cellguid.

Hasta ahora, he podido generar una lista que tiene la estructura que se muestra a continuación.

[
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Fruits"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Mango"
      }
    ]
  },
  ... //truncated for brevity
]

Mi desafío actual es convertir esto en agregados anidados que tengan la siguiente información:

{
    "records": [{
            "cellguid": "N118M2J4",
            "value": "Fruits",
            "count": 5,
            "records": [
                {
                    "cellguid": "V671H8W7",
                    "value": "Mango",
                    "count": 2
                },
                {
                    "cellguid": "V671H8W7",
                    "value": "Orange",
                    "count": 1
                },
                {
                    "cellguid": "V671H8W7",
                    "value": "Plum",
                    "count": 1
                },
                {
                    "cellguid": "V671H8W7",
                    "value": "Apple",
                    "count": 1
                }
            ]
        },
        {
            "cellguid": "N118M2J4",
            "value": "Drinks",
            "count": 9,
            "records": [
                {
                    "cellguid": "V671H8W7",
                    "value": "Coca Cola",
                    "count": 3
                },
                {
                    "cellguid": "V671H8W7",
                    "value": "Pepsi",
                    "count": 3
                },
                {
                    "cellguid": "V671H8W7",
                    "value": "Water",
                    "count": 3
                }
            ]
        }
    ]
}

Estoy usando MongoDB 4.4.0 sin mucha suerte para obtener los grupos anidados. Cualquier ayuda será muy apreciada.

La lista completa está aquí:

[
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Coca Cola"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Fruits"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Mango"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Coca Cola"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Coca Cola"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Fruits"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Orange"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Pepsi"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Pepsi"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Pepsi"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Fruits"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Plum"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Fruits"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Apple"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Water"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Water"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Drinks"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Water"
      }
    ]
  },
  {
    "record": [
      {
        "cellguid": "N118M2J4",
        "value": "Fruits"
      },
      {
        "cellguid": "V671H8W7",
        "value": "Mango"
      }
    ]
  }
]
1
haroldcampbell 21 feb. 2021 a las 14:50

2 respuestas

La mejor respuesta

Un enfoque puede ser agrupar por categ / itemId, luego por categId

const groupByItemCateg = {
  $group: {
    _id: '$record',
    categ: {
      $first: {
        $first: '$record'
      }
    },
    item: {
      $first: {
        $last: '$record'
      }
    },
    n: { $sum: 1 }
  }
}

const groupByCateg = {
  $group: {
    _id: '$categ',
    cellguid: {
      $first: '$categ.cellguid'
    },
    value: {
      $first: '$categ.value'
    },
    n: { $sum: '$n' },
    records: {
      $push: {
        cellguid: '$item.cellguid',
        value: '$item.value',
        n: '$n'
      }
    }
  }
}

const project = {
  $project: { _id: 0 }
}
printjson(db.products.aggregate([
  groupByItemCateg,
  groupByCateg,
  project
]).toArray())

Patio


Realmente necesario para la generalización de la jerarquía anidada k:

  1. Podemos considerar el dato como {cellguid, value, records, n}.

Luego, podemos reenviar el documento inicial (doc:$$ROOT) a lo largo de las diferentes etapas para elegir el dato correspondiente en profundidad k.

  1. Estoy serializando el campo _id como una cadena, pero todo es bueno siempre que no sea un matriz

El nombre de campo _id está reservado para su uso como clave primaria; su valor debe ser único en la colección, es inmutable y puede ser de cualquier tipo que no sea una matriz.

(De hecho, probé con _id: $slice: { ['$doc.record' 0, depth+1] } pero esto da resultados inconsistentes en local / mongoplayground aunque 4.4.3 en ambas partes)


db.products.remove({})
data=[{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Coca Cola"},{"cellguid":"savour","value":"light"}]},{"record":[{"cellguid":"N118M2J4","value":"Fruits"},{"cellguid":"V671H8W7","value":"Mango"},{"cellguid":"color","value":"yellow"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Coca Cola"},{"cellguid":"savour","value":"lemon"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Coca Cola"},{"cellguid":"savour","value":"light"}]},{"record":[{"cellguid":"N118M2J4","value":"Fruits"},{"cellguid":"V671H8W7","value":"Orange"},{"cellguid":"color","value":"orange"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Pepsi"},{"cellguid":"savour","value":"light"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Pepsi"},{"cellguid":"savour","value":"MAX"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Pepsi"},{"cellguid":"savour","value":"crystal"}]},{"record":[{"cellguid":"N118M2J4","value":"Fruits"},{"cellguid":"V671H8W7","value":"Plum"},{"cellguid":"color","value":"yellow"}]},{"record":[{"cellguid":"N118M2J4","value":"Fruits"},{"cellguid":"V671H8W7","value":"Apple"},{"cellguid":"color","value":"golden"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Water"},{"cellguid":"savour","value":"beer"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Water"},{"cellguid":"savour","value":"ale"}]},{"record":[{"cellguid":"N118M2J4","value":"Drinks"},{"cellguid":"V671H8W7","value":"Water"},{"cellguid":"savour","value":"ale"}]},{"record":[{"cellguid":"N118M2J4","value":"Fruits"},{"cellguid":"V671H8W7","value":"Mango"},{"cellguid":"color","value":"yellow"}]}]
db.products.insert(data)

const id = (depth, arrField='$doc.record') => ({
  $reduce: {
    input: { $slice: [arrField, 0, depth+1] },
    initialValue: '',
    in: {
      $concat: ['$$value', '$$this.cellguid', '$$this.value']
    }
  }
})
const initialize = depth => ({
  $group: {
    _id: id(depth, '$record'),
    datum: {
      $first: {
        $last: '$record'
      }
    },
    n: { $sum: 1 },
    doc: {
      $first: '$$ROOT'
    }
  }
})

const groupByItemCateg = depth => ({
  $group: {
    _id: id(depth),
    datum: {
      $first: {
        $arrayElemAt: ['$doc.record', depth]
      }
    },
    n: { $sum: '$n' },
    records: {
      $push: {
        cellguid: '$datum.cellguid',
        value: '$datum.value',
        records: '$records',
        n: '$n'
      }
    },

    doc: { $first: '$doc' }
  }
})

const project = {
  $project: {
    cellguid:'$datum.cellguid',
    value: '$datum.value',
    records: 1,
    n: 1
  }
}
const stages = [
  initialize(2),
  // here we are 3-level nested
  // max array idx is 2, so we have group by on the 0th idx and 1st idx
  // should we be 4-level nested, we would need to prepend/unshift groupByItemCateg(2)
  groupByItemCateg(1),
  groupByItemCateg(0), // the top level group by
  project
]
printjson(db.products.aggregate(stages).toArray())

patio de juegos para 3 profundidades

2
grodzi 21 feb. 2021 a las 19:05

Prueba esta consulta:

db.products.aggregate([
    {
        $addFields: {
            "product_category": { $arrayElemAt: ["$record", 0] }
        }
    },
    {
        $addFields: {
            "product": { $slice: ["$record", 1, { $size: "$record" }] }
        }
    },
    { $unwind: "$product" },
    {
        $group: {
            _id: "$product",
            count: { $sum: 1 },
            product_category: { $first: "$product_category" }
        }
    },
    {
        $group: {
            _id: "$product_category",
            count: { $sum: "$count" },
            records: {
                $push: {
                    "cellguid": "$_id.cellguid",
                    "value": "$_id.value",
                    "count": "$count"
                }
            }
        }
    },
    {
        $project: {
            "_id": 0,
            "cellguid": "$_id.cellguid",
            "value": "$_id.value",
            "count": "$count",
            "records": "$records"
        }
    }
]);

Salida:

/* 1 */
{
    "_id" : {
        "cellguid" : "N118M2J4",
        "value" : "Fruits"
    },
    "count" : 5,
    "records" : [
        {
            "cellguid" : "V671H8W7",
            "value" : "Mango",
            "count" : 2
        },
        {
            "cellguid" : "V671H8W7",
            "value" : "Plum",
            "count" : 1
        },
        {
            "cellguid" : "V671H8W7",
            "value" : "Apple",
            "count" : 1
        },
        {
            "cellguid" : "V671H8W7",
            "value" : "Orange",
            "count" : 1
        }
    ]
},

/* 2 */
{
    "_id" : {
        "cellguid" : "N118M2J4",
        "value" : "Drinks"
    },
    "count" : 9,
    "records" : [
        {
            "cellguid" : "V671H8W7",
            "value" : "Pepsi",
            "count" : 3
        },
        {
            "cellguid" : "V671H8W7",
            "value" : "Water",
            "count" : 3
        },
        {
            "cellguid" : "V671H8W7",
            "value" : "Coca Cola",
            "count" : 3
        }
    ]
}
0
Dheemanth Bhat 21 feb. 2021 a las 16:15