MongoDB查询获取具有外部链接文档计数的文档列表

MongoDB查询获取具有外部链接文档计数的文档列表,mongodb,nosql,mongodb-query,aggregation-framework,nosql-aggregation,Mongodb,Nosql,Mongodb Query,Aggregation Framework,Nosql Aggregation,我有一个mongodb数据库,其中包含的收集文档大致如下: //用户文档 { _id:$oid, 姓名:“姓名”, 说明:“说明”。 // ... } //图书文献 { _id:$oid, 用户ID:“…” 姓名:“姓名”, 描述:“描述” // ... } //页面文档 { _id:$oid, 书号:“…” 姓名:“姓名”, 描述:“描述” // ... } 一个用户有很多书,一本书有很多页。每个实体都是一个单独文档的原因是,用户可以拥有数千本书,一本书可以拥有数千页,因此,如果所有内容都在

我有一个mongodb数据库,其中包含的收集文档大致如下:

//用户文档
{
_id:$oid,
姓名:“姓名”,
说明:“说明”。
// ...
}
//图书文献
{
_id:$oid,
用户ID:“…”
姓名:“姓名”,
描述:“描述”
// ...
}
//页面文档
{
_id:$oid,
书号:“…”
姓名:“姓名”,
描述:“描述”
// ...
}
一个用户有很多书,一本书有很多页。每个实体都是一个单独文档的原因是,用户可以拥有数千本书,一本书可以拥有数千页,因此,如果所有内容都在一个文档中,我们可以很容易地达到16MB的限制

使用每本书的
pageCount
字段为指定的
userId
检索图书列表的最佳方法是什么

这是我需要的json结果

{
书籍:[{
_id:$oid,
名称:“名称1”,
description:“description1”,
页数:8
}, {
_id:$oid,
名称:“名称2”,
description:“description2”,
页数:12
},
// ...
]
}

对于SQL数据库,连接计数非常简单,但对于mongodb,除了进行单独的查询以获取书籍列表,然后获取每本书的页数之外,我看不到任何简单的解决方案。

您可以使用聚合框架中的
$lookup
阶段:

db.Users.aggregate([
    {$match: {_id: userId}},
    {$lookup: {
        from: "Book",
        localField: "userId",
        foreignField: "_id",
        as: "book"
    }},
    {$lookup: {
        from: "Page",
        localField: "bookId",
        foreignField: "book._id",
        as: "page"
    }}
])

并添加stage
$group
以计算页数。但我认为这个查询会很慢。如果您想在以后或已经这样做的情况下对集合进行切分,则不能使用$lookup

您可以使用聚合框架中的
$lookup
阶段:

db.Users.aggregate([
    {$match: {_id: userId}},
    {$lookup: {
        from: "Book",
        localField: "userId",
        foreignField: "_id",
        as: "book"
    }},
    {$lookup: {
        from: "Page",
        localField: "bookId",
        foreignField: "book._id",
        as: "page"
    }}
])

并添加stage
$group
以计算页数。但我认为这个查询会很慢。如果您想在MongoDB的聚合框架中使用$lookup,或者如果您已经使用了$lookup,那么您就不能使用$lookup,有一个名为的管道阶段,它允许您对同一数据库中的另一个集合进行左外部联接,以从“联接”中筛选文档收集以供处理

因此,使用这种武器,您可以运行聚合管道操作,将图书集合连接到页面集合

在管道步骤中,您可以通过从“连接”查询结果数组的大小来获得
pageCount

假设您的MongoDB服务器版本至少为3.4,请考虑运行以下聚合操作以获得所需的结果:

db.books.aggregate([
    { "$match": { "userId": userId } },
    { "$lookup": {
        "from": "pages",
        "localField": "_id",
        "foreignField": "bookId",
        "as": "pageCount"
    }},
    { "$addFields": {
        "pageCount": { "$size": "$pageCount" }
    }}
])

或者,您可以从
用户
集合运行管道,如下所示:

db.user.aggregate([
    { "$match": { "_id": userId } },
    { "$lookup": {
        "from": "books",
        "localField": "_id",
        "foreignField": "userId",
        "as": "books"
    }},
    { "$lookup": {
        "from": "pages",
        "localField": "books._id",
        "foreignField": "bookId",
        "as": "pages"
    }},
    { "$addFields": {
        "books": {
            "$map": {
                "input": "$books",
                "as": "book",
                "in": {
                    "name": "$$book.name",
                    "description": "$$book.description",
                    "pageCount": { "$size": "$$book.pages" }
                }
            }
        }
    }}
])

使用MongoDB的聚合框架,有一个称为的管道阶段,它允许您对同一数据库中的另一个集合进行左外部联接,以过滤“联接”集合中的文档进行处理

因此,使用这种武器,您可以运行聚合管道操作,将图书集合连接到页面集合

在管道步骤中,您可以通过从“连接”查询结果数组的大小来获得
pageCount

假设您的MongoDB服务器版本至少为3.4,请考虑运行以下聚合操作以获得所需的结果:

db.books.aggregate([
    { "$match": { "userId": userId } },
    { "$lookup": {
        "from": "pages",
        "localField": "_id",
        "foreignField": "bookId",
        "as": "pageCount"
    }},
    { "$addFields": {
        "pageCount": { "$size": "$pageCount" }
    }}
])

或者,您可以从
用户
集合运行管道,如下所示:

db.user.aggregate([
    { "$match": { "_id": userId } },
    { "$lookup": {
        "from": "books",
        "localField": "_id",
        "foreignField": "userId",
        "as": "books"
    }},
    { "$lookup": {
        "from": "pages",
        "localField": "books._id",
        "foreignField": "bookId",
        "as": "pages"
    }},
    { "$addFields": {
        "books": {
            "$map": {
                "input": "$books",
                "as": "book",
                "in": {
                    "name": "$$book.name",
                    "description": "$$book.description",
                    "pageCount": { "$size": "$$book.pages" }
                }
            }
        }
    }}
])

它没有直接回答这个问题,而是给出了一些关于

进行单独查询以获取书籍列表,然后获取每本书的页数

部分。这并不总是一件坏事。MunGDB在简单查询中非常有效,所以我给您一些数字来考虑单个查找管道与多个查询的性能,并鼓励您测试数据集上的典型查询。如果你不同时需要所有的数据,分页会产生巨大的影响

设置 一个由100个用户X 1000本书X 1000页组成的小型数据库,每个页面位于一个小小的1 vCPU/2 GB内存/50 GB磁盘/LON1上-16.04 droplet上的Ubuntu MongoDB 3.4.10

页面
集合创建如下:

for USERID in {1..100}; do   
    echo "" > pages.json;     
    for BOOKID in {1..1000}; do       
       ./node_modules/.bin/mgeneratejs "{\"bookId\": \"$USERID-$BOOKID\", \"name\": {\"\$sentence\":{\"words\":3}}, \"description\": \"\$paragraph\"}" -n 1000 >> pages.json
    done     
    cat pages.json | mongoimport -d so -c pages 
done
书籍
一本几乎是一样的

基本统计数据:

db.books.stats(1024*1024)
    "ns" : "so.books",
    "size" : 50,
    "count" : 100000,
    "avgObjSize" : 533,
    "storageSize" : 52,
    "nindexes" : 2,
    "totalIndexSize" : 1,
    "indexSizes" : {
            "_id_" : 0,
            "userId_1" : 0
    },

db.pages.stats(1024*1024)
    "ns" : "so.pages",
    "size" : 51673,
    "count" : 100000000,
    "avgObjSize" : 541,
    "storageSize" : 28920,
    "nindexes" : 2,
    "totalIndexSize" : 1424,
    "indexSizes" : {
            "_id_" : 994,
            "bookId_1" : 430
    },
$lookup 来自@chridam答案的管道

db.books.aggregate([
    { "$match": { "userId": 18 } },
    { "$lookup": {
        "from": "pages",
        "localField": "_id",
        "foreignField": "bookId",
        "as": "pageCount"
    }},
    { "$addFields": {
        "pageCount": { "$size": "$pageCount" }
    }}
]) 
快速响应:

    "op" : "command",
    "command" : {
            "aggregate" : "books"
    },
    "keysExamined" : 1000,
    "docsExamined" : 1000,
    "nreturned" : 101,
    "responseLength" : 57234,
    "millis" : 1028
对于前100个文档,允许您在一秒钟内开始处理文档

整个事件的总时间:

db.books.aggregate([
    { "$match": { "userId": 18 } },
    { "$lookup": {
        "from": "pages",
        "localField": "_id",
        "foreignField": "bookId",
        "as": "pageCount"
    }},
    { "$addFields": {
        "pageCount": { "$size": "$pageCount" }
    }}
]).toArray()
再增加8秒:

    "op" : "getmore",
    "query" : {
            "getMore" : NumberLong("32322423895"),
            "collection" : "books"
    },
    "keysExamined" : 0,
    "docsExamined" : 0,
    "nreturned" : 899,
    "responseLength" : 500060,
    "millis" : 8471
检索所有数据的总时间超过9秒

多重查询
  • 检索书籍:

    let bookIds = []; 
    db.books.find({userId:12}).forEach(b=>{bookIds.push(b._id);});
    
    在10毫秒内填充阵列:

    "op" : "query",
    "query" : {
            "find" : "books",
            "filter" : {
                    "userId" : 34
            }
    },
    "keysExamined" : 101,
    "docsExamined" : 101,
    "nreturned" : 101,
    "responseLength" : 54710,
    "millis" : 3
    

  • 计算页数:

    db.pages.aggregate([
        { $match: { bookId: { $in: bookIds } } }, 
        { $group: { _id: "$bookId", cnt: { $sum: 1 } } }
    ]).toArray()
    
    总共需要1.5秒:

    "op" : "command",
    "command" : {
            "aggregate" : "pages"
    },
    "keysExamined" : 1000001,
    "docsExamined" : 0,
    "nreturned" : 101,
    "responseLength" : 3899,
    "millis" : 1574
    

  • 合并结果

    不是查询,但应在应用程序级别执行。mongoshell javascript只需几毫秒,这使得检索所有数据的总时间不到2秒


  • 它没有直接回答这个问题,而是给出了一些关于

    进行单独查询以获取书籍列表,然后获取每本书的页数

    部分。这并不总是一件坏事。MunGDB在简单查询中非常有效,所以我给您一些数字来考虑单个查找管道与多个查询的性能,并鼓励您测试数据集上的典型查询。如果你不同时需要所有的数据,分页会产生巨大的影响

    设置 一个由100个用户X 1000本书X 1000页组成的小型数据库,每个页面位于一个小小的1 vCPU/2 GB内存/50 GB磁盘/LON1上-16.04 droplet上的Ubuntu MongoDB 3.4.10

    页面
    集合创建如下:

    for USERID in {1..100}; do   
        echo "" > pages.json;     
        for BOOKID in {1..1000}; do       
           ./node_modules/.bin/mgeneratejs "{\"bookId\": \"$USERID-$BOOKID\", \"name\": {\"\$sentence\":{\"words\":3}}, \"description\": \"\$paragraph\"}" -n 1000 >> pages.json
        done     
        cat pages.json | mongoimport -d so -c pages 
    done
    
    还有
    boo