2016-06-18 12 views
0

In der 'account'-Sammlung befinden sich Dokumente. Einige Felder können nicht deklariert werden. Schau dir das an.

{ _id: ObjectId(1), price: 5400, product: ObjectId(2), count: 1   } 
{ _id: ObjectId(1),    product: ObjectId(2), count: 0, sale: 0.2 } 
{ _id: ObjectId(1),    product: ObjectId(2), count: 1   } 

Mit aggregierten Rahmen möchte ich letzten Preis, letzten Verkauf und Zählung für ein Produkt haben. Derzeit verwende ich diese Abfrage

db.collection('account').aggregate([ 
{ 
    $group: { 
      _id: '$product', 
      count: { $sum: '$count' }, 
      price: { $last: '$price' }, 
      sale: { $last: '$sale' } 
     } 
    } 
], { allowDiskUse: true }).toArray() 

Aber sale und price Felder null wird. Ich denke, ich sollte $project vor $group verwenden, wie behalte ich den Wert des letzten Dokuments, wenn das aktuelle Feld null ist?

+0

Ich denke, das ist eine Aufgabe, die das Lesen der Daten in die Sprache Ihrer Wahl erfordert. Jede Daten-Frame-Bibliothek (wie 'Pandas' in Python) würde dies trivial behandeln –

Antwort

0

Meine aktuelle Lösung macht in drei Abfragen.

1. - Group by `product` and sum `count` field 
    - Remove any entries, where `count` is 0 and lower 
    - Hold `_ids` array as `product_ids` 
    - Hold result array as `firstArray` 

2. - Match any entries, where 
      - `sale` is 0 and greater 
      - `_id` is in `product_ids` 
    - Group by `product` and get last `sale` field 
    - Hold result array as `secondArray` 

3. - Match any entries, where 
      - `price` is 0 and greater 
      - `_id` is in `product_ids` 
    - Group by `product` and get last `price` field 
    - Enumerate `firstArray` and add `count` field to entries 
    - Enumerate `secondArray` and add `sale` field to entries 

Und der Code

let firstArray 
let secondArray 
let product_ids 
Promise.resolve().then(function() { 
    return db.collection(ACCOUNT).aggregate([ 
     { 
      $group: { 
       _id: '$product', 
       count: { $sum: '$count' } 
      } 
     }, { 
      $match: { 
       count: { $gt: 0 } 
      } 
     } 
    ], { allowDiskUse: true }).toArray() 
}).then(function (array) { 
    firstArray = array 
    product_ids = array.map(function (item) { 
     return item._id 
    }) 
    return db.collection(ACCOUNT).aggregate([ 
     { 
      $match: { 
       product: { $in: product_ids }, 
       sale: { $gte: 0 } 
      } 
     }, { 
      $group: { 
       _id: '$product', 
       sale: { $last: '$sale' } 
      } 
     } 
    ], { allowDiskUse: true }).toArray() 
}).then(function (array) { 
    secondArray = array 

    return db.collection(ACCOUNT).aggregate([ 
     { 
      $match: { 
       product: { $in: product_ids }, 
       price: { $gte: 0 } 
      } 
     }, { 
      $group: { 
       _id: '$product', 
       price: { $last: '$price' } 
      } 
     } 
    ], { allowDiskUse: true }).toArray() 
}).then(function (array) { 
    req.data = array.map(function (item) { 
     let count = 0 
     let sale = 0 
     firstArray.some(function (_item) { 
      if (item._id.toHexString() == _item._id.toHexString()) { 
       count = _item.count 
       return true 
      } 

      return false 
     }) 
     secondArray.some(function (_item) { 
      if (item._id.toHexString() == _item._id.toHexString()) { 
       sale = _item.sale 
       return 
      } 
      return false 
     }) 

     item.count = count 
     item.sale = sale 
     return item 
    }) 
    next() 
}) 
2

Lösung
Die folgende Pipeline sollte Ihnen das gewünschte Ergebnis

db.getCollection('account').aggregate(
[ 
    { 
     $project: { 
      _id: '$product', 
      fields: [ 
       { name: { $literal: 'price' }, value: '$price',  count: { $literal: 0 } }, 
       { name: { $literal: 'sale' }, value: '$sale',   count: { $literal: 0 } }, 
       { name: { $literal: 'count' }, value: { $literal: 0 }, count: '$count' } 
      ] 
     } 
    }, 
    { 
     $unwind: { 
      path: '$fields' 
     } 
    }, 
    { 
     $match: { 
      'fields.value': { 
       $exists: true 
      } 
     } 
    }, 
    { 
     $group: { 
      _id: { 
       product: '$_id', 
       field: '$fields.name' 
      }, 
      value: { 
       $last: '$fields.value' 
      }, 
      count: { 
       $sum: '$fields.count' 
      } 
     } 
    }, 
    { 
     $project: { 
      _id: '$_id.product', 
      price: { 
       $cond: { if: { $eq: [ '$_id.field', 'price' ] }, then: '$value', else: null } 
      }, 
      sale: { 
       $cond: { if: { $eq: [ '$_id.field', 'sale' ] }, then: '$value', else: null } 
      }, 
      count: { 
       $cond: { if: { $eq: [ '$_id.field', 'count' ] }, then: '$count', else: 0 } 
      } 
     } 
    }, 
    { 
     $group: { 
      _id: '$_id', 
      price: { 
       $max: '$price' 
      }, 
      sale: { 
       $max: '$sale' 
      }, 
      count: { 
       $sum: '$count' 
      } 
     } 
    } 
]) 

Erklärung
Es erstellt zuerst ein neues Array mit Elementen pro Feld, das Feldname, Feldwert und Zählwert enthält. Beachten Sie, dass das Feld count so behandelt wird, wie es akkumuliert werden sollte, anstatt den letzten Wert davon zu erhalten. Also nach der ersten Stufe aussehen Dokumente wie folgt aus:

/* 1 */ 
{ 
    "_id" : "2", 
    "fields" : [ 
     { 
      "name" : "price", 
      "value" : 5400, 
      "count" : 0.0 
     }, 
     { 
      "name" : "sale", 
      "count" : 0.0 
     }, 
     { 
      "name" : "count", 
      "value" : 0.0, 
      "count" : 1 
     } 
    ] 
} 

/* 2 */ 
{ 
    "_id" : "2", 
    "fields" : [ 
     { 
      "name" : "price", 
      "count" : 0.0 
     }, 
     { 
      "name" : "sale", 
      "value" : 0.2, 
      "count" : 0.0 
     }, 
     { 
      "name" : "count", 
      "value" : 0.0, 
      "count" : 0 
     } 
    ] 
} 

/* 3 */ 
{ 
    "_id" : "2", 
    "fields" : [ 
     { 
      "name" : "price", 
      "count" : 0.0 
     }, 
     { 
      "name" : "sale", 
      "count" : 0.0 
     }, 
     { 
      "name" : "count", 
      "value" : 0.0, 
      "count" : 1 
     } 
    ] 
} 

Es abwickelt dann das Array und filtert sie von Nullwerte, um loszuwerden, so dass nach der Stufe 2 & 3 Dokumente wie folgt aussehen:

/* 1 */ 
{ 
    "_id" : "2", 
    "fields" : { 
     "name" : "price", 
     "value" : 5400, 
     "count" : 0.0 
    } 
} 

/* 2 */ 
{ 
    "_id" : "2", 
    "fields" : { 
     "name" : "count", 
     "value" : 0.0, 
     "count" : 1 
    } 
} 

/* 3 */ 
{ 
    "_id" : "2", 
    "fields" : { 
     "name" : "sale", 
     "value" : 0.2, 
     "count" : 0.0 
    } 
} 

/* 4 */ 
{ 
    "_id" : "2", 
    "fields" : { 
     "name" : "count", 
     "value" : 0.0, 
     "count" : 0 
    } 
} 

/* 5 */ 
{ 
    "_id" : "2", 
    "fields" : { 
     "name" : "count", 
     "value" : 0.0, 
     "count" : 1 
    } 
} 

In der vierten Stufe werden die letzten Werte der Felder und die Summe von count gebildet. Ergebnis sieht wie folgt aus:

/* 1 */ 
{ 
    "_id" : { 
     "product" : "2", 
     "field" : "sale" 
    }, 
    "value" : 0.2, 
    "count" : 0.0 
} 

/* 2 */ 
{ 
    "_id" : { 
     "product" : "2", 
     "field" : "count" 
    }, 
    "value" : 0.0, 
    "count" : 2 
} 

/* 3 */ 
{ 
    "_id" : { 
     "product" : "2", 
     "field" : "price" 
    }, 
    "value" : 5400, 
    "count" : 0.0 
} 

Da die Werte sind nun in separaten Dokumenten mit einer anderen Form als unsere gewünschte Ergebnis sollte sein, müssen wir sie wieder in etwas projizieren wir können schließlich Gruppe. So in nach der fünften Stufe Dokumente sind wie folgt:

/* 1 */ 
{ 
    "_id" : "2", 
    "count" : 0.0, 
    "price" : null, 
    "sale" : 0.2 
} 

/* 2 */ 
{ 
    "_id" : "2", 
    "count" : 2, 
    "price" : null, 
    "sale" : null 
} 

/* 3 */ 
{ 
    "_id" : "2", 
    "count" : 0.0, 
    "price" : 5400, 
    "sale" : null 
} 

Die letzte Stufe dann aggregiert nur diese Dokumente pro Produkt.

+1

Großartig! +1 für die Arbeit und detaillierte Erklärung! – tengobash