如何通过Mongoose中的字段对记录进行分组?

时间:2015-08-23 10:11:01

标签: javascript node.js mongodb mongoose nosql

我有一个MongoDB文档,其记录如下:

[
  { _id: id, category_name: 'category1', parent_category: null },
  { _id: id, category_name: 'category2', parent_category: null },
  { _id: id, category_name: 'subcategory1', parent_category: id_parent_1 },
  { _id: id, category_name: 'subcategory2', parent_category: id_parent_1 },
  { _id: id, category_name: 'subcategory3', parent_category: id_parent_2 },
  { _id: id, category_name: 'subcategory4', parent_category: id_parent_2 }
]

如您所见,我正在存储parent_category null的类别,子类别包含父类别的ID。我正在寻找的是将这些分组为这样的格式:

[
  { category_name: 'category1', categories: [
       { category_name: 'subcategory1', _id: id },
       { category_name: 'subcategory2', _id: id }
    ]
  },
  { category_name: 'category2', categories: [
       { category_name: 'subcategory3', _id: id },
       { category_name: 'subcategory4', _id: id }
    ]
  }
]

因此,基本上将父类别与具有子类别的数组进行分组。我正在使用猫鼬。我尝试使用MongoDB提供的聚合框架,但我无法获得所需的结果。 :(

我有权以任何可能需要的方式修改架构!

提前致谢!

4 个答案:

答案 0 :(得分:2)

您似乎将Mongo视为关系数据库(将所有这些字段分开并将它们与查询结合在一起)。你应该做的是重建你的架构。例如:

var CategorySchema = new Schema({
   category_name: String,
   subCategories:[subCategorySchema]
}

var subCategorySchema = new Schema({
   category_name: String
})

这种方式当你需要查询集合时,这是一个简单的

db.find({category_name: "name of the category"}, function(){})

获得您需要的一切。

以防万一:您可以使用简单的更新将子类别添加到数组中。请阅读this了解详情。

答案 1 :(得分:1)

如果您的架构未更改,请尝试此操作:

var MongoClient = require('mongodb').MongoClient

//connect away
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
  if (err) throw err;
  console.log("Connected to Database");

  //simple json record
    var document = [];
    //insert record
    //db.data.find({"parent_category":null }).forEach(function(data) {print("user: " + db.data.findOne({"parent_category":data._id })) })
    db.collection('data').find({"parent_category":null }, function(err, parentrecords) {
        if (err) throw err;
        var cat ={};
        parentrecords.forEach(function(data){
            cat["category_name"] = data["category_name"];
            db.collection('data').find({"parent_category":data._id },function(err, childrecords) {
                var doc = [];
                childrecords.forEach(function(childdata){
                    doc.push(childdata);
                        },function(){
                        cat["categories"] = doc;
                        document.push(cat);
                        console.log(document);
                    });
            });
        });
});
});

答案 2 :(得分:1)

如果您想在不更改架构的情况下找到预期结果,那么您基本上会遵循一些复杂的mongo aggregation查询。要查找输出,请按以下步骤操作:

  1. 首先在$project检查parent_category等于null如果为真,则添加_id否则添加parent_category
  2. 现在,文档结构与新key name as parent_id礼物和parent_id分组相似,并推送剩余数据,如category_name and parent_category
  3. 之后,使用 $setDifference $setIntersection 来区分父数据和子数据。
  4. 最后unwind只有单个数组对象,所以这个单个数组对象和用于显示那些要显示的字段的项目。
  5. 检查工作聚合查询,如下所示:

    db.collectionName.aggregate({
        "$project": {
            "parent_id": {
                "$cond": {
                    "if": {
                        "$eq": ["$parent_category", null]
                    },
                    "then": "$_id",
                    "else": "$parent_category"
                }
            },
            "category_name": 1,
            "parent_category": 1
        }
    }, {
        "$group": {
            "_id": "$parent_id",
            "categories": {
                "$push": {
                    "category_name": "$category_name",
                    "parent_category": "$parent_category"
                }
            },
            "parentData": {
                "$push": {
                    "$cond": {
                        "if": {
                            "$eq": ["$parent_category", null]
                        },
                        "then": {
                            "category_name": "$category_name",
                            "parent_category": "$parent_category"
                        },
                        "else": null
                    }
                }
            }
        }
    }, {
        "$project": {
            "findParent": {
                "$setIntersection": ["$categories", "$parentData"]
            },
            "categories": {
                "$setDifference": ["$categories", "$parentData"]
            }
        }
    }, {
        "$unwind": "$findParent"
    }, {
        "$project": {
            "category_name": "$findParent.category_name",
            "categories": 1
        }
    }).pretty() 
    

答案 3 :(得分:-1)

要按字段对记录进行分组,请尝试在聚合中使用 $ group 。这对我有用。

示例

SYNOPSIS
  globex PATTERNLIST[, \%options]

DESCRIPTION
  Extends the standard glob() function with support for recursive globbing.
  Prepend '**/' to the part of the pattern that should match anywhere in the
  subtree or end the pattern with '**' to match all files and dirs. in the
  subtree, similar to Bash's `globstar` option.

  A pattern that doesn't contain '**' is passed to the regular glob()
  function.
  While you can use brace expressions such as {a,b}, using '**' INSIDE
  such an expression is NOT supported, and will be treated as just '*'.
  Unlike with glob(), whitespace in a pattern is considered part of that
  pattern; use separate pattern arguments or a brace expression to specify
  multiple patterns.

  To also follow directory symlinks, set 'follow' to 1 in the options hash
  passed as the optional last argument.
  Note that this changes the sort order - see below.

  Traversal:
  For recursive patterns, any given directory examined will have its matches
  listed first, before descending depth-first into the subdirectories.

  Hidden directories:
  These are skipped by default, onless you set 'hiddendirs' to 1 in the
  options hash passed as the optional last argument.

  Sorting:
  A given directory's matching items will always be sorted
  case-insensitively, as with glob(), but sorting across directories
  is only ensured, if the option to follow symlinks is NOT specified.

  Duplicates:
  Following symlinks only prevents cycles, so if a symlink and its target
  they will both be reported.
  (Under the hood, following symlinks activates the following 
   File::Find:find() options: `follow_fast`, with `follow_skip` set to 2.)

  Since the default glob() function is at the heart of this function, its
  rules - and quirks - apply here too:
  - If literal components of your patterns contain pattern metacharacters,
    - * ? { } [ ] - you must make sure that they're \-escaped to be treated
    as literals; here's an expression that works on both Unix and Windows
    systems: s/[][{}\-~*?]/\\$&/gr
  - Unlike with glob(), however, whitespace in a pattern is considered part
    of the pattern; to specify multiple patterns, use either a brace
    expression (e.g., '{*.txt,*.md}'), or pass each pattern as a separate
    argument.
  - A pattern ending in '/' restricts matches to directories and symlinks
    to directories, but, strangely, also includes symlinks to *files*.
  - Hidden files and directories are NOT matched by default; use a separate
    pattern starting with '.' to include them; e.g., globex '**/{.*,*}'
    matches all files and directories, including hidden ones, in the 
    current dir.'s subtree.
    Note: As with glob(), .* also matches '.' and '..'
  - Tilde expansion is supported; escape as '\~' to treat a tilde as the
    first char. as a literal.
 -  A literal path (with no pattern chars. at all) is echoed as-is, 
    even if it doesn't refer to an existing filesystem item.

COMPATIBILITY NOTES
  Requires Perl v5.6.0+
  '/' must be used as the path separator on all platforms, even on Windows.

EXAMPLES
  # Find all *.txt files in the subtree of a dir stored in $mydir, including
  # in hidden subdirs.
  globex "$mydir/*.txt", { hiddendirs => 1 };

  # Find all *.txt and *.bak files in the current subtree.
  globex '**/*.txt', '**/*.bak'; 

  # Ditto, though with different output ordering:
  # Unlike above, where you get all *.txt files across all subdirs. first,
  # then all *.bak files, here you'll get *.txt files, then *.bak files
  # per subdirectory encountered.
  globex '**/{*.txt,*.bak}';

  # Find all *.pm files anywhere in the subtrees of the directories in the
  # module search path, @INC; follow symlinks.
  # Note: The assumption is that no directory in @INC has embedded spaces
  #       or contains pattern metacharacters.
  globex '{' . (join ',', @INC) . '}/**/*.pm', { follow => 1 };

<强>参考MongoDB Aggregation

希望这有效。