在Mongo中存储查询

6
这是一个案例:一个网店,我想根据一组参数配置应该在商店中列出哪些商品。我希望这是可配置的,因为这使我可以尝试不同的参数,并轻松更改其值。
我有一个产品集合,我想根据多个参数进行查询。其中一些在此处找到:
在产品内部:
"delivery" : {
    "maximum_delivery_days" : 30,
    "average_delivery_days" : 10,
    "source" : 1,
    "filling_rate" : 85,
    "stock" : 0
}

但是还有其他参数存在。

决定是否包含某个产品的查询示例可能是:

"$or" : [
        {
            "delivery.stock" : 1
        },
        {
            "$or" : [
                    {
                            "$and" : [
                                    {
                                            "delivery.maximum_delivery_days" : {
                                                    "$lt" : 60
                                            }
                                    },
                                    {
                                            "delivery.filling_rate" : {
                                                    "$gt" : 90
                                            }
                                    }
                            ]
                    },
                    {
                            "$and" : [
                                    {
                                            "delivery.maximum_delivery_days" : {
                                                    "$lt" : 40
                                            }
                                    },
                                    {
                                            "delivery.filling_rate" : {
                                                    "$gt" : 80
                                            }
                                    }
                            ]
                    },
                    {
                            "$and" : [
                                    {
                                            "delivery.delivery_days" : {
                                                    "$lt" : 25
                                            }
                                    },
                                    {
                                            "delivery.filling_rate" : {
                                                    "$gt" : 70
                                            }
                                    }
                            ]
                    }
            ]
        }
]

现在为了使其可配置化,我需要能够处理布尔逻辑、参数和值。因此,我想到将这样的查询本身存储在Mongo中,并让我的Java应用程序检索它。接下来要做的是在筛选器中使用它(例如find或其他操作),并且对相应的产品选择进行处理。这种方法的优点是,我实际上可以在程序之外分析数据和查询的有效性。
我会按名称将其存储在数据库中。例如:
{ 
    "name": "query1",
    "query": { the thing printed above starting with "$or"... }
}

使用:

db.queries.insert({
    "name" : "query1",
    "query": { the thing printed above starting with "$or"... }
})

这将导致:
2016-03-27T14:43:37.265+0200 E QUERY    Error: field names cannot start with $ [$or]
    at Error (<anonymous>)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:161:19)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:165:18)
    at insert (src/mongo/shell/bulk_api.js:646:20)
    at DBCollection.insert (src/mongo/shell/collection.js:243:18)
    at (shell):1:12 at src/mongo/shell/collection.js:161

我可以使用Robomongo存储它,但并非总是成功。显然我做错了什么,但我完全不知道是什么。

如果失败了,我创建一个全新的集合再尝试,就会成功。这种情况很奇怪,超出了我的理解范围。

但当我尝试在“查询”中更新值时,更改不会生效。从未成功过,甚至有时候也不行。

但我可以创建一个新对象并丢弃之前的对象。所以,这个问题有一个变通方法。

db.queries.update(
    {"name": "query1"},
    {"$set": { 
            ... update goes here ...
        }
    }
)

这样做会导致:
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {
                "code" : 52,
                "errmsg" : "The dollar ($) prefixed field '$or' in 'action.$or' is not valid for storage."
        }
})

看起来与上面的信息非常接近。

不用说,我对这里发生的事情一无所知,所以我希望这里的一些技术专家能够为此提供一些帮助和指导。

4个回答

1
我认为错误信息包含了你需要考虑的重要信息:
查询错误: 字段名称不能以 $ 开头
由于你正在尝试在文档中存储查询(或其中一部分), 你最终会得到包含Mongo操作符关键字(如$or, $ne, $gt)的属性名称。实际上,Mongo文档引用了这个确切的场景- 强调添加
字段名称不能包含点(即.), 空字符和它们不能以美元符号(即$)开头...
我不会信任像Robomongo这样的第三方应用程序。我建议在mongo shell中直接调试/测试此问题。
我的建议是将查询的转义版本存储在您的文档中,以免干扰保留的操作符关键字。您可以使用可用的JSON.stringify(my_obj);将部分查询编码为字符串,然后在稍后选择检索时解析/解码它:JSON.parse(escaped_query_string_from_db)

那么,您是建议将字符串化的对象存储为字符串,而不是对象本身吗?您还写道:“我建议直接在mongo shell中调试/测试此问题。”如何操作?最后(为了提供更多信息),使用Robomongo存储的对象可作为普通对象使用,并且在检索后可以通过我的Java程序传递给查询。因此,这些确实有效。 - dexter
关于我所说的调试/测试 - 我的意思是你应该在命令行中直接进行测试以确保这种方法确实适用于你的情况,而不是通过第三方界面(Robomongo)进行测试。具体操作请参考mongo shell - Lix
根据你的应用程序,你可以使用Javascript来转义字符串...尽管你使用的任何语言都应该有类似的方式来转义/编码字符串,以便它们不会干扰其他功能... - Lix
再次强调 - 尽管Robomongo能够解读/插入文档,但仍需要通过您的应用程序验证功能。 - Lix
感谢您的帮助。但我仍然感到惊讶的是,一些对象竟然成功地被存储了下来(并且可以作为对象使用而无需进行任何转换)。 - dexter
显示剩余3条评论

1

将查询作为JSON对象存储在MongoDB中的方法是不可行的。

您可以潜在地将查询逻辑和字段存储在MongoDB中,但必须使用适当的MongoDB语法由外部应用程序构建查询。

MongoDB查询包含运算符,其中一些具有特殊字符。

MongoDB字段名称有规则限制,不允许使用特殊字符。请参阅:https://docs.mongodb.org/manual/reference/limits/#Restrictions-on-Field-Names

您有时可以成功使用Robomongo创建文档的可能原因是因为Robomongo将您的查询转换为字符串,并正确转义特殊字符发送到MongoDB。

这也解释了为什么您尝试更新它们从未起作用。您尝试创建一个文档,但实际上创建的是一个字符串对象,因此您的更新条件可能无法检索任何文档。


我以前在使用Robomongo时遇到过问题,主要是它处理/解析日期(Date对象与ISODate)的方式。如果它对接收到的查询字符串进行了某种诡计,我也不会感到惊讶... - Lix
我对API和Robo的作用有些困惑。因为在其他线程中提到,存储的对象绝对可以从我的Java中使用,并且可以作为查询参数传递(例如find(查询参数))。此外,由于Robo还将这些对象呈现为常规JSON,因此可能需要进行额外的解析步骤。但是,这也适用于Java API,因为在Java中检索到的相同对象可作为find中的参数使用。 - dexter
还是有点奇怪。也许我还缺少一些细节。例如:在 shell 中执行 db.queries.insert({ "name" : "stock", "value" : "\"$or\" : [{\"delivery.stock\":1}]" }) 是可以工作的,但在这种情况下 Robo 也将其视为字符串... - dexter
3
不管是否感到困惑,我认为你们两个都是正确的。即使它成功了,我也不应该依赖这种看起来不被支持的行为来计算结果,这是一件愚蠢的事情。我将尝试使用 JavaScript 和序列化来让它工作。 - dexter

0

显然,我以前在Mongo中存储查询的尝试是愚蠢的,正如@bigdatakid和@lix所回答的那样。因此,我最终做的是:我改变了字段的命名方式,以符合Mongo的要求。

例如,我使用_$or代替$or等,并且在名称中不使用.,而是使用#。我在我的Java代码中进行替换。

这样,我仍然可以轻松地在程序外尝试和测试查询。在我的Java程序中,我只需更改名称并使用查询即可,只需要两行代码。现在它完美地工作了。感谢你们提出的建议。

String documentAsString = query.toJson().replaceAll("_\\$", "\\$").replaceAll("#", ".");
    Object q = JSON.parse(documentAsString);

所以你仍然成功地使用这个解决方案从你的Mongo集合中运行了查询吗?你应该将其标记为正确答案。 - Lucbug
感谢您的留言。我选择了答案1,因为那基本上是正确的。我只是在这里使用了那些信息来解决问题。 - dexter

0

我看到你的方法有两个问题。

在下面的查询中

db.queries.insert({
   "name" : "query1",
   "query": { the thing printed above starting with "$or"... }
})

一个有效的 JSON 需要键值对。在这里,你正在存储一个没有 key 的对象到 "query" 中。你有两个选择。要么将查询作为文本存储,要么在花括号内创建另一个键。

第二个问题是,你正在存储未用引号包装的 query 值。所有字符串值都必须用引号包装。

因此,你的最终文档应该如下所示:

db.queries.insert({
    "name" : "query1",
    "query": 'the thing printed above starting with "$or"... '
})

现在试试,应该可以工作了。


1
我相信 {从"$or"开始打印的上面的东西...} 只是一个更大的查询对象的占位符。这个字符串只是作为一个例子给出的... - Lix
你可能是对的。即使字符串包含 $,在存储到 MongoDB 中时也必须进行引用。 - Saleem
啊...现在我明白你的@Saleem评论了) - dexter

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