(Cloudant) 创建视图以组合两种文档类型
(Cloudant) Creating a view to combine two document types
假设我正在制作一个 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]"
。但是,我们不能这样做,因为车牌信息很容易更改。
听起来链式 mapreduce 可以提供您的解决方案?
https://examples.cloudant.com/sales/_design/sales/index.html
希望你还在。答案的要点是:在 CouchDb 中,当您觉得需要进行连接时,您 99% 的时间都在做错事。您需要做的是在一份文件中包含您需要的所有信息。
您需要养成在设计要保存的内容时考虑如何查询数据的习惯。你会发现用这个习惯代替"relational normalization"习惯是健康的
您在这里可以做的是将车牌号保存在服务记录文档中。不要害怕去规范化。因此,服务记录应如下所示:
{
"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 table 相对)。我们往往会忘记它,但很多时候(尤其是就销售而言),我们对快照感兴趣,而不是最新的关系数据(客户购买时的姓名是什么,税率是多少在他买的时候,等等)。但是关系系统让我们养成 "most up to date by default" 习惯,因为快照管理在那里涉及大量开销。
最重要的是,这种反规范化在 CouchDb 中绝对没问题。您处于预期用途中,不会在路上被咬伤。正如 CouchDb 所说:放轻松 ;)
假设我正在制作一个 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]"
。但是,我们不能这样做,因为车牌信息很容易更改。
听起来链式 mapreduce 可以提供您的解决方案? https://examples.cloudant.com/sales/_design/sales/index.html
希望你还在。答案的要点是:在 CouchDb 中,当您觉得需要进行连接时,您 99% 的时间都在做错事。您需要做的是在一份文件中包含您需要的所有信息。
您需要养成在设计要保存的内容时考虑如何查询数据的习惯。你会发现用这个习惯代替"relational normalization"习惯是健康的
您在这里可以做的是将车牌号保存在服务记录文档中。不要害怕去规范化。因此,服务记录应如下所示:
{
"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 table 相对)。我们往往会忘记它,但很多时候(尤其是就销售而言),我们对快照感兴趣,而不是最新的关系数据(客户购买时的姓名是什么,税率是多少在他买的时候,等等)。但是关系系统让我们养成 "most up to date by default" 习惯,因为快照管理在那里涉及大量开销。
最重要的是,这种反规范化在 CouchDb 中绝对没问题。您处于预期用途中,不会在路上被咬伤。正如 CouchDb 所说:放轻松 ;)