如何在不使用上限集合的情况下在MongoDB中存储一组有序的文档

Bon*_*Oak 6 sorting mongodb

什么是在MongoDB中存储一组文件的好方法,其中订单很重要?我需要轻松地将文档插入任意位置,并可能在以后重新排序.

我可以为每个项目分配一个越来越多的数字并按其排序,或者我可以排序_id,但我不知道如何在其他文档之间插入另一个文档.说我要插入一个元素之间的事情sequence5和元素用sequence6

我的第一个猜测是增加sequence以下所有元素,以便使用类似的查询为新元素留出空间db.items.update({"sequence":{$gte:6}}, {$inc:{"sequence":1}}).我对数据库管理的有限理解告诉我,像这样的查询会很慢并且通常是一个坏主意,但我很高兴能够得到纠正.

我想我可以将新元素设置sequence5.5,但我认为这会很快变得混乱.(再次,如果我错了,请纠正我.)

我可以使用带有保证订单的上限集合,但如果我需要增加集合,那么我会遇到问题.(再一次,我也可能错了.)

我可以让每个文档包含对下一个文档的引用,但这需要查询列表中的每个项目.(你得到一个项目,将其推送到结果数组,然后根据next当前项目的字段获取另一个项目.)除了明显的性能问题,我也无法将一个已排序的mongo游标传递给我{#each}spacebars阻止表达式,并在数据库更改时让它更新.(我正在使用Meteor全栈javascript框架.)

我知道一切都有它的优点和缺点,我可能只需要使用上面列出的一个选项,但我想知道是否有更好的方法来做事.

Bat*_*eam 5

根据您的需求,一种方法可能是设计模式,以使每个文档能够容纳一个以上的文档,并且其本身就可以作为一个有顶盖的容器

{
  "_id":Number,
  "doc":Array
}
Run Code Online (Sandbox Code Playgroud)

集合中的每个文档将充当加盖的容器,并且这些文档将作为数组存储在doc字段中。该doc字段为数组,将保持插入顺序。您可以将文档数量限制为n。因此_id,每个容器文档的字段将以递增n,表示容器文档可以容纳的文档数。

做这些你避免添加extra fields到文档,extra indicesunnecessary sorts

插入第一条记录

即当集合为空时。

var record = {"name" : "first"};
db.col.insert({"_id":0,"doc":[record]});
Run Code Online (Sandbox Code Playgroud)

插入后续记录

  • 标识最后一个容器文档的_id和所number保存的文档。
  • 如果保存的文档数少于n,则使用新文档更新容器文档,否则创建新的容器文档。

假设每个人container document最多可以容纳5文件,我们要插入一个新文件。

var record = {"name" : "newlyAdded"};

// using aggregation, get the _id of the last inserted container, and the 
// number of record it currently holds.
db.col.aggregate( [ {
    $group : {
        "_id" : null,
        "max" : {
            $max : "$_id"
        },
        "lastDocSize" : {
            $last : "$doc"
        }
    }
}, {
    $project : {
        "currentMaxId" : "$max",
        "capSize" : {
            $size : "$lastDocSize"
        },
        "_id" : 0
    }
// once obtained, check if you need to update the last container or 
// create a new container and insert the document in it.
} ]).forEach( function(check) {
    if (check.capSize < 5) {
        print("updating");
        // UPDATE
        db.col.update( {
            "_id" : check.currentMaxId
        }, {
            $push : {
                "doc" : record
            }
        });
    } else {
        print("inserting");
        //insert
        db.col.insert( {
            "_id" : check.currentMaxId + 5,
            "doc" : [ record ]
        });
    }
})
Run Code Online (Sandbox Code Playgroud)

请注意,在aggregation服务器端运行,并且效率很高,还请注意,aggregation会返回文档而不是版本中的游标previous to 2.6。因此,您需要修改上面的代码以仅从单个文档中进行选择,而不是迭代游标。

在文档之间插入新文档

现在,如果您想在1和之间插入一个新文档2,我们知道该文档应放在容器内,_id=0并应放置在该容器数组中的second位置doc

因此,我们利用$each$position运算符插入特定位置。

var record = {"name" : "insertInMiddle"};

db.col.update(
{
    "_id" : 0
}, {
    $push : {
        "doc" : {
            $each : [record],
            $position : 1
        }
    }
}
);
Run Code Online (Sandbox Code Playgroud)

处理流量

现在,我们需要照顾overflowing每个文档中的文档container,比如说我们在中间的容器中插入一个新文档_id=0。如果容器中已经有5文件,那么我们需move the last document to the next container要这样做,直到所有容器都在其容量范围内保存文件为止,如果最终需要,我们需要创建一个容器来保存溢出的文件。

这个复杂的操作应该服务器端完成。为了解决这个问题,我们可以创建一个脚本,例如下面的脚本,并register使用mongodb 编写脚本。

db.system.js.save( {
    "_id" : "handleOverFlow",
    "value" : function handleOverFlow(id) {
        var currDocArr = db.col.find( {
            "_id" : id
        })[0].doc;
        print(currDocArr);
        var count = currDocArr.length;
        var nextColId = id + 5;
        // check if the collection size has exceeded
    if (count <= 5)
        return;
    else {
        // need to take the last doc and push it to the next capped 
    // container's array
    print("updating collection: " + id);
    var record = currDocArr.splice(currDocArr.length - 1, 1);
    // update the next collection
    db.col.update( {
        "_id" : nextColId
    }, {
        $push : {
            "doc" : {
                $each : record,
                $position : 0
            }
        }
    });
    // remove from original collection
    db.col.update( {
        "_id" : id
    }, {
        "doc" : currDocArr
    });
    // check overflow for the subsequent containers, recursively.
    handleOverFlow(nextColId);
}
}
Run Code Online (Sandbox Code Playgroud)

因此after every insertion in between,我们可以function通过传递容器ID 来调用它handleOverFlow(containerId)

按顺序获取所有记录

只需在中使用$unwind运算符即可aggregate pipeline

db.col.aggregate([{$unwind:"$doc"},{$project:{"_id":0,"doc":1}}]);
Run Code Online (Sandbox Code Playgroud)

重新订购文件

您可以使用“ _id”字段将每个文档存储在加盖的容器中:

.."doc":[{"_id":0,","name":"xyz",...}..]..
Run Code Online (Sandbox Code Playgroud)

获取要对其项目进行重新排序的带帽容器的“ doc”数组。

var docArray = db.col.find({"_id":0})[0];
Run Code Online (Sandbox Code Playgroud)

更新其ID,以便在排序后更改项目的顺序。

根据其_id对数组进行排序。

docArray.sort( function(a, b) {
    return a._id - b._id;
});
Run Code Online (Sandbox Code Playgroud)

使用新的doc数组更新加盖的容器。

但话又说回来,一切都归结为哪种方法可行并最适合您的要求。

提出您的问题:

在顺序很重要的MongoDB中存储一组文档的好方法是什么?我需要轻松地将文档插入任意位置,并可能在以后重新排序。

文档为数组。

说我想在序列为5的元素和序列为6的元素之间插入某些内容?

如我的答案所述$each$positiondb.collection.update()函数中使用and 运算符。

我对数据库管理的有限了解告诉我,这样的查询会很慢,而且通常是个坏主意,但我很高兴得到纠正。

是。除非集合中的数据很少,否则它将影响性能。

我可以使用有上限的集合,该集合有保证的顺序,但是如果我需要增加集合的数量,那么我会遇到问题。(再次,我可能也错了。)

是。使用上限集合,您可能会丢失数据。

  • @BonsaiOak-mongodb将文档的大小限制为16 mb。此外,将所有文档转储到单个容器中时,如果对它们运行某些聚合运算符,则会导致内存不足的问题。固定和限制容器中文档的数量有助于您根据文档索引识别文档。例如。当上限大小为5时,始终保证第4个文档将位于第一个容器中,第6个文档位于第二个容器中。在应用程序层中事先了解这一点可以减少很多编码工作。 (3认同)

Wil*_*ver 1

对于任何集合的任意排序,您需要一个字段来对其进行排序。我称我的为“序列”。

schema:
{
 _id: ObjectID,
 sequence: Number,
 ...
}

db.items.ensureIndex({sequence:1});

db.items.find().sort({sequence:1})
Run Code Online (Sandbox Code Playgroud)

  • 那么我该如何重新订购商品呢?我正在编辑我的问题以澄清这一点。 (2认同)