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

时间:2017-11-15 21:47:43

标签: mongodb mongodb-query aggregation-framework

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

不是使用嵌入文档数组,而关键信息是数组元素中的一个字段,每个嵌入文档都是自己的字段,键作为字段名称。

假设一个名为bank_branches的集合。而不是这个:

{
  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 Aggregate Pipeline或其他方面,有没有办法执行以下任一操作?

  1. 查询员工嵌入式文档的组件字段,以便我可以(例如)获得2016年雇用的员工的所有分支,而不事先知道所有嵌入式文档的所有字段名称< / EM>

  2. &#34;开卷&#34;这些对象,以便我可以拥有一系列分支员工文档,再次提前知道所有嵌入文档的所有字段名称? (MongoDB $unwind管道操作仅适用于数组。)

  3. 我怀疑第一个可以使用$wherejavascript以及/或自定义javascript存储函数来解决。 (我之前从未使用过存储过的函数。)但我怀疑第二个只能通过编程方式解决。

    我可以通过编写Python并进行迭代来满足我的用例。但我宁愿编写查询来查找记录而不是以编程方式过滤它们。 (唯一保证无错误的代码是您不必编写的代码。)

    建议,有人吗?非常感谢,提前。

1 个答案:

答案 0 :(得分:1)

我可以对此喋喋不休,但你至少已经意识到最好的答案了。将数据转换为数组。 “查询”文档的唯一方法是在服务器上操作文档(按文档处理),将这些“命名键”强制转换为数组。

最近发布的MongoDB中有更多“现代”方法,这意味着你不必“使用”$where,但仍有一个主要的警告,即它仍然是错误的形式,你只需要“无法使用索引”来加快查询结果。

在基本解决问题时:

查询

如果您希望根据您的条件在分支机构员工雇用日期“查找文档”,那么您可以使用$where的表达式,如下所示:

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

我们使用JavaScript评估的主要原因是您需要以查询DSL无法表达的方式“遍历文档的密钥”,因此需要针对每个文档评估条件而不是使用索引。

使用MongoDB 3.4.4以后的更新版本,您可以使用$objectToArrayObject.keys()的目的相同,并将其用作$redact聚合管道阶段内表达式的一部分:

db.bank_branches.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$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") ] }
              ]
            }
          }
        }
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

$redact基本上评估一个条件,并根据条件的布尔结果通过$$KEEP或“删除”文件返回文件。这类似于$where,除了它是本机运算符而不是使用解释的JavaScript,本质上是“整体”查询条件而不是$where,它在技术上只是另一个查询参数,可以与其他查询参数一起使用条件。

从MongoDB 3.6我们得到$expr,它允许缩短,甚至可以用作与$where相同的“附加参数”语法,除了本机编码运算符:

$$PRUNE

但它们仍然基本上都很糟糕,因为你所能做的只是“扫描整个集合”以获得结果。所以更好的“数组”语法是:

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_employees.hire_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" }
])

两者基本上都使用相同的技术来获取对象的子键的值,并将其与底层对象合并为作为“数组”返回的元素。

  

NB $where表达式中运行的代码使用JavaScript并在服务器上运行,因此它仍然是“语言无关的解决方案”,其中要评估的“JavaScript”是实际上以其他语言的“字符串”提交。

     

其他表达式基本上分解为选择语言中的BSON表示。就这种语法而言,Python和Ruby几乎与JavaScript完全相同,并且相同的BSON约定适用于所有地方。

     

此处的其他例程用于“转换”数据,作为“一次性”操作应该始终足以在shell的JavaScript执行环境中运行。

     

因此没有“需要”使用JavaScript($where表达式除外),但这里的示例以通用格式给出,每个人都可以在MongoDB安装提供的shell中运行。