【问题标题】:(Cloudant) Creating a view to combine two document types(Cloudant) 创建视图以组合两种文档类型
【发布时间】:2016-06-09 21:30:09
【问题描述】:

假设我正在创建一个 Cloudant 数据库来存储我车队的所有服务记录(我没有,但问题几乎相同。)为此,我有两种类型的记录:

汽车:

  {
    "type": "Car",
    "_id": "VIN 1",
    "plateNumber": "ecto-1",
    "plateState": "NY",
    "make": "Cadillac",
    "model": "Professional Chassis",
    "year": 1959
  }

  {
    "type": "Car",
    "_id": "VIN 2",
    "plateNumber": "mntclmbr",
    "plateState": "VT",
    "make": "Jeep",
    "model": "Wrangler",
    "year": 2016
  }

及服务记录:

  {
    "type": "ServiceRecord",
    "_id": "service1",
    "carServiced": "VIN 1",
    "date": [1984, 6, 8],
    "item": "Cleaning (Goo)",
    "cost": 300
  }

  {
    "type": "ServiceRecord",
    "_id": "service2",
    "carServiced": "VIN 1",
    "date": [1984, 6, 9],
    "item": "Cleaning (Marshmellow)",
    "cost": 800
  }

  {
    "type": "ServiceRecord",
    "_id": "service3",
    "carServiced": "VIN 2",
    "date": [2016, 4, 2],
    "item": "Alignment",
    "cost": 150
  }

关于它的工作原理有几点需要注意:

  • 永远不会改变汽车的 VIN 号用作文档 _id。
  • 如果汽车在新州注册或使用新车牌号,则不应丢失汽车的服务记录。
  • 由于汽车数量众多,并且需要维修的频率很高,如果需要添加、删除或更改服务记录,则编辑汽车文档是不合理的。

目前,我有几个视图可以查找信息。

首先,我有一张从车牌到 VIN 的地图:

function(doc){
   if (doc.type == "Car"){
      emit([doc.plateState, doc.plateNumber], doc._id);
   }
}

// Results in:
["NY", "ecto-1"] -> "VIN 1"
["VT", "mntclmbr"] -> "VIN 2"

其次,我有一张从所有汽车的 VIN 到服务记录的地图:

function(doc){
   if (doc.type == "ServiceRecord"){
      emit(doc.carServiced, doc);
   }
}

// Results in:
"VIN 1" -> {"_id": "service1", ...}
"VIN 1" -> {"_id": "service2", ...}
"VIN 2" -> {"_id": "service3", ...}

最后,我有一张地图,从所有汽车的 VIN 和服务日期到该日期发生的特定服务:

function(doc){
   if (doc.type == "ServiceRecord"){
      var key = [doc.carServiced, doc.date[0], doc.date[3], doc.date[2]];
      emit(key, doc);
   }
}

// Results in:
["VIN 1", 1984, 6, 8] -> {"_id": "service1", ...}
["VIN 1", 1984, 6, 9] -> {"_id": "service2", ...}
["VIN 2", 2016, 4, 2] -> {"_id": "service3", ...}

通过这三张地图,我可以找到三种不同的东西:

  • 任何汽车的车牌识别号。
  • 任何汽车的服务记录(按 VIN)。
  • 任何汽车在任何特定年份、月份或日期的 VIN 服务记录。

但是,无法通过车牌找到汽车的所有服务记录。 (至少不是一步。)为此,我需要一张这样的地图:

["NY", "ecto-1"] -> {"_id": "service1", ...}
["NY", "ecto-1"] -> {"_id": "service2", ...}
["VT", "mntclmbr"] -> {"_id": "service3", ...}

更复杂的是,我希望能够按车牌和日期查找服务记录,使用如下地图:

["NY", "ecto-1", 1984, 6, 8] -> {"_id": "service1", ...}
["NY", "ecto-1", 1984, 6, 9] -> {"_id": "service2", ...}
["VT", "mntclmbr", 2016, 4, 2] -> {"_id": "service3", ...}

不幸的是,我不知道如何生成这样的地图,因为密钥需要来自两个文档的信息。我只能从 Car 文档中获取车牌信息,而我只能从 ServiceRecord 文档中获取服务信息(包括文件 _id 的 emit 值)。

到目前为止,我唯一的想法是做两个查询:一个从车牌信息中获取 VIN,另一个从 VIN 中获取服务记录。它们将是快速查询,所以这不是一个大问题,但我觉得有更好的方法。

有谁知道更好的方法是什么?

(奖励:两次查询方法不允许以有效的方式按状态查找所有服务记录。我描述的最后一张地图可以做到这一点。因此,任何可以描述解决方案的人都可以获得互联网积分它也提供了该功能。)

**编辑:另一个问题,here,被建议为可能的重复。这绝对是一个类似的问题,但是提供的解决方案并不能解决这个问题。具体来说,最佳解决方案建议将文档的位置存储在树中。在这种情况下,这将类似于 ServiceRecord 文档中的 "index":[State, Number, Year, Month, Day]"。但是,我们不能这样做,因为车牌信息很容易改变。

【问题讨论】:

标签: join view couchdb cloudant relational-algebra


【解决方案1】:

希望你还在。答案的要点是:在 CouchDb 中,当您觉得需要进行连接时,您 99% 的时间都在做错事。您需要做的是在一个文档中包含您需要的所有信息。

在设计要保存的内容时,您需要养成思考如何查询数据的习惯。你会发现用这种习惯代替“关系规范化”习惯是健康的。

您可以在此处将车牌号保存在服务记录文档中。不要害怕去规范化。因此,服务记录应如下所示:

{
    "type": "ServiceRecord",
    "_id": "service3",
    "carServiced": "VIN 2",
    "carPlateNumber": "mntclmbr", 
    "date": [2016, 4, 2],
    "item": "Alignment",
    "cost": 150
}

您可以从这里轻松地做任何您想做的事。话虽如此,我作为架构师可以闻到,您可能每个月都会发明新的方法来查询这些数据。出于这个原因,我个人更喜欢将整个汽车文档存储在服务记录中:

{
    "type": "ServiceRecord",
    "_id": "service3",
    "carServiced":  {
        "type": "Car",
        "_id": "VIN 2",
        "plateNumber": "mntclmbr",
        "plateState": "VT",
        "make": "Jeep",
        "model": "Wrangler",
        "year": 2016
      }, 
   "date": [2016, 4, 2],
        "item": "Alignment",
        "cost": 150
}

这绝对没问题。特别是因为服务记录是及时的快照,您无需担心更新信息。实际上,我发现这是 CouchDb 特别出色的场景之一,因为存储快照基本上是免费的午餐(而不是在关系系统中管理 cars_snapshot 表)。而且我们往往会忘记它,但很多时候(特别是就销售而言),我们对快照感兴趣,而不是最新的关系数据(客户购买时的姓名是什么,税率是多少在他购买时等)。但是关系系统让我们养成了“默认情况下最新”的习惯,因为快照管理涉及大量开销。

最重要的是,这种非规范化在 CouchDb 中绝对没问题。您处于预期用途中,不会在后面被咬。正如 CouchDb 所说:放松一下;)

【讨论】:

  • 这是一个很好的答案。一个警告是,对于我的实际问题,这里 确实 的“ServiceRecord”的类似物需要更新。例如,如果我的示例中的 Jeep 获得了新的车牌“rollovr”,则必须更新该车辆的所有服务记录。因为有必要能够找到车辆曾经拥有的每条服务记录,即使车辆已重新注册。 (因此我试图通过 VIN 进行跟踪,这不会改变。)
  • 我实施的解决方案介于两者之间。仍然有包含有关组的一般信息的“Car”文档,以及包含有关该组成员的特定信息的“ServiceRecord”文档。然后每个“ServiceRecord”都有一个indexKey 字段,例如{"_id":"VIN 2", "plateNumber": "mntclmbr", "plateState":"VT"},用于索引。在汽车重新注册的半罕见事件中,我们可以使用更新的车牌信息搜索并更新所有服务记录。因此,低效(但罕见)的更新换取了非常高效(且常见)的查找。
  • 很高兴您发现它有帮助!一种想法是,plateState 可能是它自己的文档,看起来像:{ id: plateState1, plateText: 'ASF256' },然后汽车会像 { id: 'car1', plateStateId: 'plateState1' } 这样引用 plateState,这样您就不必进行批量更新。但 !我们回到了 2 查询解决方案。但这解决了您提到的按状态查询的问题,并且正如您所提到的,查询会非常快。你会有意见:getPlateIdByPlateText/State,和getServicedCarByPlateId
  • 是的!对于我们的 API,我使用类似于 /api/lookup/byVIN/:vin/api/lookup/byPlate/:plateState/:plateNumber 的端点来查找汽车,并使用 /api/lookup/byVIN/:vin/[all|latest|:serviceDate]/api/lookup/byPlate/:plateState/:plateNumber/[all|latest|:serviceDate] 来查找服务记录。所有 GET 端点只需要一个 Cloudant 查询。一些 POST 端点需要两个,但预计这些端点很少见,应该是完全可以接受的。
  • 最佳实践文档指出:根据数据更改的频率和时间将数据拆分为许多小文档。 github.com/jo/…(也描述了一对N关系)
【解决方案2】:

听起来链式 mapreduce 可以提供您的解决方案? https://examples.cloudant.com/sales/_design/sales/index.html

【讨论】:

  • 当我开始阅读此链接时,我感到非常兴奋,因为它看起来很有希望。不幸的是,因为它是一个 reduce 函数,所以您只能获取聚合数据。因此,例如,我可能可以使用它来计算总维修成本,按车牌计算,但不是所有服务记录本身的列表。 (至少,并非没有大量存储和性能损失。)
  • James 您是否注意到dbcopy 选项,您将视图复制到新数据库的位置?使用该策略,您可以做您想做的事,即使您必须创建一个减少步骤实际上没有做任何事情的情况。这会导致存储“惩罚”,但可能不会导致性能下降?
  • 我用dbcopy 尝试了几种不同的方法,但我想不出任何方法可以将我需要的数据放入单个文档中以在其上构建视图。我很可能只是没有正确理解。你能举个例子说明我的 map 和 reduce 函数是什么样的吗?
  • 此链接指向受密码保护的私人页面。
猜你喜欢
  • 2015-10-03
  • 1970-01-01
  • 1970-01-01
  • 2019-05-08
  • 1970-01-01
  • 1970-01-01
  • 2015-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多