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

时间:2014-10-06 16:44:28

标签: mongodb sorting

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

我可以为每个项目分配一个不断增加的数字并按其排序,或者我可以按_id排序,但我不知道如何在其他文档之间插入另一个文档。假设我想在sequence 5的元素和sequence 6的元素之间插入内容?

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

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

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

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

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

4 个答案:

答案 0 :(得分:4)

根据您的要求,其中一种方法可能是设计您的架构,使每个文档具有来容纳多个文档,并且本身充当加盖容器

{
  "_id":Number,
  "doc":Array
}

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

通过执行此操作,您避免extra fields添加到文档extra indicesunnecessary sorts

插入第一条记录

即收集时为空。

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

插入后续记录

  • 标识最后一个容器文档的_idnumber 它持有的文件。
  • 如果其保留的文档数量少于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 ]
        });
    }
})

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

在文档之间插入新文档

现在,如果您想在文档12之间插入新文档,我们知道该文档应该位于具有_id=0的容器内,并且应该放在second位于该容器的doc数组中。

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

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

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

处理流量

现在,我们需要处理每个overflowing中的文档container,比如我们在_id=0的容器中插入一个新文档。如果容器已经有5个文档,我们需要move the last document to the next container并执行此操作,直到所有容器都保存其容量内的文档,如果最终需要,我们需要创建一个容器来保存溢出的文档。 / p>

为了解决这个问题,我们可以使用mongodb创建一个脚本,例如下面的脚本和register

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);
}
}

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

按顺序获取所有记录

只需使用$unwind中的aggregate pipeline运算符。

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

重新订购文件

您可以将每个文档存储在带有“_id”字段的上限容器中:

.."doc":[{"_id":0,","name":"xyz",...}..]..

抓住您想要的上限容器的“doc”数组    重新订购商品。

var docArray = db.col.find({"_id":0})[0];

更新他们的ID,以便在排序后项目的顺序会发生变化。

根据数组_ids对数组进行排序。

docArray.sort( function(a, b) {
    return a._id - b._id;
});

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

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

回答你的问题:

  

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

文档作为数组。

  

我想在一个序列为5的元素和一个序列为6的元素之间插入一些东西?

使用$each函数中的$positiondb.collection.update()运算符,如我的回答所示。

  

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

是。它会影响性能,除非集合的数据非常少。

  

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

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

答案 1 :(得分:1)

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

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

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

db.items.find().sort({sequence:1})

答案 2 :(得分:1)

MongoDB中的_id字段是一个唯一的索引键,类似于关系数据库中的主键。如果文档中存在固有顺序,理想情况下,您应该能够将唯一键与每个文档相关联,并且键值反映顺序。因此,在准备要插入的文档时,请明确添加_id字段作为此键(如果不这样做,mongo会自动使用BSON objectid创建它)。

就检索结果而言,MongoDB does not guarantee the order of return documents unless you explicitly use .sort()。如果您不使用.sort(),结果通常会按自然顺序(插入顺序)返回。再次,无法保证此行为。

我建议您在插入时用您的订单覆盖_id,并在检索时使用排序。由于_id是必需且自动索引的实体,因此您不会浪费任何定义排序键的空间,并为其存储索引。

答案 3 :(得分:-1)

以下是一些可能相关的常规排序数据库答案的链接:

https://softwareengineering.stackexchange.com/questions/195308/storing-a-re-orderable-list-in-a-database/369754

我建议使用Floating point解决方案-添加一个position列:

在位置列中使用浮点数。 然后,您可以重新排序列表,仅更改“已移动”行中的位置列。 如果您的用户希望将“红色”放置在“蓝色”之后但在“黄色”之前,那么您只需要计算

red.position = ((yellow.position - blue.position) / 2) + blue.position

几百万个重新定位后,您可能会得到很小的浮点数,以至于没有“介于”之间的浮点数,但这与看到独角兽的可能性差不多。

检索它时,您只需说col.sort()就可以对其进行排序,而无需任何客户端代码(例如在链接列表解决方案中)