Mongodb 使用命名键查询和转换文档
我是一个SQL老手,现在正在使用现有的Mongo数据库 与使用嵌入文档的数组(键信息是数组元素中的一个字段)不同,每个嵌入文档都是其自己的字段,并以键作为字段名 假设一个名为Mongodb 使用命名键查询和转换文档,mongodb,mongodb-query,aggregation-framework,Mongodb,Mongodb Query,Aggregation Framework,我是一个SQL老手,现在正在使用现有的Mongo数据库 与使用嵌入文档的数组(键信息是数组元素中的一个字段)不同,每个嵌入文档都是其自己的字段,并以键作为字段名 假设一个名为bank\u branchs的集合。与此相反: { branch : "St. Louis", branch_employees : [ { name : "Mary", id : "M12345" hire_date : Date("2010-05-20")
bank\u branchs
的集合。与此相反:
{
branch : "St. Louis",
branch_employees : [
{
name : "Mary",
id : "M12345"
hire_date : Date("2010-05-20")
},
{
name : "John",
id : "J29876",
hire_date : Date("2015-03-23")
}
]
},
{
branch : "Jefferson City",
branch_employees : [
{
name : "Lisa",
id : "L87653"
hire_date : Date("2016-01-07")
}
]
}
。。。我们有这样的文件:
{
branch : "St. Louis",
branch_employees : {
M12345 : {
name : "Mary",
hire_date : Date("2010-05-20")
},
J29876 : {
name : "John",
hire_date : Date("2015-03-23")
}
}
{
branch : "Jefferson City",
branch_employees : {
L87653 : {
name : "Lisa",
hire_date : Date("2016-01-07")
}
}
}
(这是一个虚构的结构来说明问题。)
在MongoDB聚合管道或其他管道中,是否有任何方法可以执行以下任一操作
$unwind
管道操作仅适用于阵列。)$where
和javascript
和/或自定义javascript存储函数来解决。(我以前从未使用过存储函数。)但我怀疑第二个问题只能通过编程来解决
我可以通过编写Python和进行迭代来满足我的用例。但我宁愿编写查询来查找记录,也不愿以编程方式过滤记录。(唯一保证无bug的代码是您不必编写的代码。)
有什么建议吗?非常感谢,提前。关于这一点,我可以漫无边际地说下去,但你至少已经知道了最好的答案。将数据转换为数组。“查询”文档的唯一方法实际上是在服务器上操纵文档(每个文档处理),将这些“命名键”强制转换为数组 MongoDB的最新版本中有更多的“现代”方法,这意味着您不必“必须”使用,但主要的警告仍然是,这仍然是错误的形式,您只是“不能使用索引”来加快查询结果 在回答您的基本问题时: 查询 如果您希望根据您在分行员工雇用日期的条件“查找文档”,则可以使用以下表达式:
db.bank_branches.find(
function() {
return Object.keys(this.branch_employees).some(e =>
e.hire_date => new Date("2016-01-01") && e.hire_date < new Date("2017-01-01")
)
}
)
基本上是对条件求值,然后根据条件的布尔结果通过$$KEEP
返回文档,或者通过$$PRUNE
返回文档。这与类似,只是它是一个本机操作符,而不是使用解释的JavaScript,本质上是“整体”查询条件,而从技术上讲,它只是另一个查询参数,可以与其他条件一起使用
从MongoDB 3.6中,我们可以得到这样的结果,除了本机编码的运算符外,它可以缩短,甚至可以用作与之相同的“附加参数”语法:
db.bank_branches.find({
"$expr": {
"$anyElementTrue": {
"$map": {
"input": { "$objectToArray": "$branch_employees" },
"in": {
"$and": [
{ "$gte": [ "$$this.hire_date", new Date("2016-01-01") ] },
{ "$lt": [ "$$this.hire_date", new Date("2017-01-01") ] }
]
}
}
}
}
})
但它们基本上仍然很可怕,因为你所能做的就是“扫描整个集合”以获得结果。因此,更好的“数组”语法是:
db.bank_branches.find({
"branch_employees": {
"$elemMatch": {
"hire_date": { "$gte": new Date("2016-01-01"), "$lte": new Date("2017-01-01") }
}
}
})
当然可以使用索引,因为“branch\u employees.hire\u date”
的路径是一致的,并且不使用“命名键”作为所需属性的中间路径的一部分。这是你想要这种结构的主要原因
重塑
为了实际获得该“数组形状”中的文档,我们应该通过如何构造查询得到一些指示
因此,在第一个选项中,如果写入“新集合”是您的一个选项,那么在现代版本中,您应该能够在服务器本身上完成整个转换:
db.bank_branches.aggregate([
{ "$project": {
"branch": 1,
"branch_employees": {
"$map": {
"input": { "$objectToArray": "$branch_employees" },
"in": {
"$arrayToObject": {
"$concatArrays": [
[{ "k": "id", "v": "$$this.k" }],
{ "$objectToArray": "$$this.v" }
]
}
}
}
}
}},
{ "$out": "new_branches" }
])
或者,如果您没有可用的现代运算符或无法写入新集合,则基本上会循环集合并回写属性的新数据:
var ops = [];
db.bank_branches.find({ "branch_employees.0": { "$exists": false } }).forEach( doc => {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": {
"branch_employees": Object.keys(doc.branch_employees).map(id =>
Object.assign({ id },doc.branch_employees[id])
)
}
}
}
});
if ( ops.length > 1000 ) {
db.bank_branches.bulkWrite(ops);
ops = [];
}
})
if ( ops.length > 0 ) {
db.bank_branches.bulkWrite(ops);
ops = [];
}
两者本质上都使用相同的技术,即获取对象子键的值,并将其与底层对象合并为返回的“数组”元素
N.B在表达式中运行的代码是在JavaScript中并在服务器上运行的,因此它仍然是一个“语言不可知的解决方案”,其中要计算的“JavaScript”实际上在其他语言中作为“字符串”提交 其他表达式基本上分解为所选语言中的BSON表示。Python和Ruby在这种语法方面与JavaScript几乎相同,同样的BSON约定适用于所有地方 这里给出的其他例程用于数据的“转换”,作为“一次性”操作,它应该始终足以在shell的JavaScript执行环境中运行 因此,没有“需要”使用JavaScript(表达式除外),但这里给出的示例是通用格式,每个人都可以在MongoDB安装提供的shell中运行
感谢您抽出时间回答我的问题!您提供了很多我不知道的信息,包括更新的MongoDB功能。例如,我不知道
$objectToArray
。(我们的服务器有点落后。)我在发布我的问题后发现的另一个选项是使用MapReduce
将命名键“展开”为更具表格格式。但我非常感谢你的解释和例子:它们看起来比我想到的任何东西都好。再次感谢。@AnnL。为了弄清楚答案,mapReduce
将简单地使用与答案内容相同的基本JavaScript技术,使用Object.keys()
列出子键,然后迭代子键。关于数据“聚合”的问题也没有什么,这是mapReduce
的目的,尽管我展示了.aggregate()
方法,但它的唯一用法是
var ops = [];
db.bank_branches.find({ "branch_employees.0": { "$exists": false } }).forEach( doc => {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": {
"branch_employees": Object.keys(doc.branch_employees).map(id =>
Object.assign({ id },doc.branch_employees[id])
)
}
}
}
});
if ( ops.length > 1000 ) {
db.bank_branches.bulkWrite(ops);
ops = [];
}
})
if ( ops.length > 0 ) {
db.bank_branches.bulkWrite(ops);
ops = [];
}