如果不存在则插入,否则删除MongoDB。

3

我在 MongoDB (2.6.4) 中有一个查询,试图实现一个简单的赞/踩机制。当用户点击赞时,需要执行以下操作:

如果用户已经点过赞,则取消赞。

否则,添加赞并删除踩(如果存在)。

到目前为止,我形成的查询(不正确)如下:

db.collection.aggregate([
{
    $project: {
        "_id" : ObjectId("53e4d45c198d7811248cefca"),
        "upvote": {
           "$cond": 
            [
            {"$in": ["$upvote",1] },
            {"$pull": {"upvote" : 1}},
            {"$addToSet": {"upvote" : 1}, "$pull": {"downvote": 1}}
            ]
        }
    }
}
])

其中'1'是试图点赞的用户ID。

upvotedownvote都是包含已经点赞和踩过的用户ID的数组。

对于查询的输出,我只需要一个布尔值:true如果$cond的结果为true,否则为false


使用聚合框架无法更改您的数据... - Leonid Beschastny
好的。我该怎么做? - myusuf
你应该使用 updatefindAndModify 进行更新操作。 - Leonid Beschastny
我检查了一下。问题在于我只能删除或更新,但我需要进行 upsert 操作,这种情况下我不能使用删除,因为那要求我使用更新。我需要同时使用两者。希望我的意思表达清楚:D - myusuf
在当前的Mongodb版本中,您可以在findAndModify中执行upsert操作。 - YulePale
1个回答

7
这不是一个好的实现赞和踩的方式。除了聚合框架不是任何更新文档的机制之外,你似乎因为你想要实现的逻辑而倾向于认为它可能是一个解决方案。但聚合操作并不会进行更新。
在你的“问题”模式中,你需要像这样的结构:
{
    "_id": ObjectId("53f51a844ffa9b02cf01c074"),
    "upvoted": [],
    "downvoted": [],
    "upvoteCount": 0,
    "downvoteCount": 0
}

这是可以与原子更新很好配合使用的东西,并且可以同时为对象提供一些有状态的信息。

对于"upvoted"和"downvoted"数组,我们将考虑"users"投票具有类似的唯一ObjectId值。因此,我们要做的是从任一数组中$push$pull,并在每个操作中同时"增加/减少"计数器值。

以下是点赞的工作方式:

db.questions.update(
    { 
        "_id": ObjectId("53f51a844ffa9b02cf01c074"),
        "upvoted": { "$ne": ObjectId("53f51c0a4ffa9b02cf01c075") }
        "downvoted": ObjectId("53f51c0a4ffa9b02cf01c075")
    },
    {         
        "$push": { "upvoted": ObjectId("53f51c0a4ffa9b02cf01c075") },
        "$inc": { "upvoteCount": 1, "downvoteCount": -1 },
        "$pull": { "downvoted": ObjectId("53f51c0a4ffa9b02cf01c075") },
    }
)

db.questions.update(
    { 
        "_id": ObjectId("53f51a844ffa9b02cf01c074"),
        "upvoted": { "$ne": ObjectId("53f51c0a4ffa9b02cf01c075") }
    },
    {
        "$push": { "upvoted": ObjectId("53f51c0a4ffa9b02cf01c075") },
        "$inc": { "upvoteCount": 1 },
    }
)

实际上这是两个操作,你可以通过Bulk operations API来完成(可能是最好的方式),但这有其重要性。第一个语句只会匹配当前用户在数组中记录了“downvote”的文档。因为我们已经将该用户id值推送到“downvotes”数组中,所以如果它不在那里,则不进行更新。但你需要同时从相应的数组中推送和拉取,并且同时“增加/减少”计数字段。
对于第二个语句,它只会匹配第一个语句没有匹配到的内容,你可以公正地评估现在你不需要触及“downvotes”,而只需处理upvote字段。在两种情况下,安全的做法是确保主条件是当前用户id值不存在于“upvoted”数组中。
对于downvotes,字段仅相反:
db.questions.update(
    { 
        "_id": ObjectId("53f51a844ffa9b02cf01c074"),
        "downvoted": { "$ne": ObjectId("53f51c0a4ffa9b02cf01c075") }
        "upvoted": ObjectId("53f51c0a4ffa9b02cf01c075")
    },
    {         
        "$pull": { "upvoted": ObjectId("53f51c0a4ffa9b02cf01c075") },
        "$inc": { "upvoteCount": -1, "downvoteCount": 1 },
        "$push": { "downvoted": ObjectId("53f51c0a4ffa9b02cf01c075") },
    }
)

db.questions.update(
    { 
        "_id": ObjectId("53f51a844ffa9b02cf01c074"),
        "downvoted": { "$ne": ObjectId("53f51c0a4ffa9b02cf01c075") }
    },
    {
        "$push": { "downvoted": ObjectId("53f51c0a4ffa9b02cf01c075") },
        "$inc": { "downvoteCount": 1 },
    }
)

自然而然,您可以看到逻辑进展,只需取消有关用户的任何“赞成/反对”即可。如果您愿意,您还可以在客户端中智能处理此信息,不仅显示当前用户是否已经“赞成/反对”,还可以控制单击操作并消除不必要的请求。

我正在向客户展示他是否已点赞或点踩等。我在我的应用程序中做了与你指出的相同的事情。我试图实现的目标是将整个操作压缩为单个查询。使用BulkQueryAPI比运行2个查询更好吗?(我想是的..) - myusuf
@myusuf 从技术上讲,你仍然发送了两个更新,但是使用批量操作,两者都发送到服务器并同时响应。因此,它减少了流量和等待/处理逻辑,因此更好。这是我认为处理此问题的最佳方法,而无需创建可能冗余的“事务”类型文档并依靠聚合将其组合在一起。您可以“聪明地”处理状态,然后在处理更新时基本上了解是否需要从“赞成/反对”中拉取。那样你只需要一个。我列出两个更新表单,因为它是安全的。 - Neil Lunn
@NeilLunn 谢谢你的回答。但是为什么不只使用一个查询,通过从匹配参数中删除“downvoted”字段来实现呢?例如:db.questions.update( { "_id": ObjectId("53f51a844ffa9b02cf01c074"), "upvoted": { "$ne": ObjectId("53f51c0a4ffa9b02cf01c075") } }, { "$push": { "upvoted": ObjectId("53f51c0a4ffa9b02cf01c075") }, "$inc": { "upvoteCount": 1, "downvoteCount": -1 }, "$pull": { "downvoted": ObjectId("53f51c0a4ffa9b02cf01c075") }, } ) - YulePale

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接