Mongodb 使用命名键查询和转换文档

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")

我是一个SQL老手,现在正在使用现有的Mongo数据库

与使用嵌入文档的数组(键信息是数组元素中的一个字段)不同,每个嵌入文档都是其自己的字段,并以键作为字段名

假设一个名为
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聚合管道或其他管道中,是否有任何方法可以执行以下任一操作

  • 查询员工嵌入文档的组件字段,以便我可以(例如)获取2016年雇佣员工的所有分支机构,而不必事先知道所有嵌入文档的所有字段名称

  • “解开”这些对象,这样我就可以拥有一个分支员工文档数组,而不必事先知道所有嵌入文档的所有字段名?(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 = [];
    }