MongoDB:使用字段值作为字段名称

时间:2020-07-17 09:28:39

标签: mongodb aggregation-framework

我在MongoDB中有下一个示例文档。

db={
  "contracts": [
    {
      "bid": 1, // id in businesses collection
      "type": "A",
      "name": "N1"
    },
    {
      "bid": 1,
      "type": "B",
      "name": "N2"
    },
    {
      "bid": 1,
      "type": "C",
      "name": "N3"
    }
  ],
  "businesses": [
    {
      "id": 1,
      "contract_settings": {
        "A": {
          "price": 100
        },
        "B": {
          "price": 200
        },
        "default": "A"
      }
    }
  ]
}

我想根据合同的类型查找合同的价格。如果合同的类型不在contract_settings中,那么我应该使用默认值。

例如,对于当前方案,我希望输出为

  "contracts": [
    {
      "bid": 1,
      "type": "A",
      "name": "N1",
      "price": 100
    },
    {
      "bid": 1,
      "type": "B",
      "name": "N2",
      "price": 200
    },
    {
      "bid": 1,
      "type": "C",
      "name": "N3",
      "price":100 // because default settings are settings for type "A"
    }
  ]
}

Contract_settings总是具有某些类型,“ default”总是与现有类型相关。

是否可以使用字段值(方案中的contracts.type)作为字段名称来从business.contract_settings获取设置?

请注意,contract_settings可以包含任意名称,因此我不能使用这样的解决方案 similar problem

here is playground

PS。如果contract_settings是jsonb字段,并使用这样的代码,则可以解决postgres中的相同问题

    ((CASE WHEN businesses.contract_settings::jsonb ? contracts.contract_type::text
            THEN businesses.contract_settings -> contracts.contract_amount::text
            ELSE businesses.contract_settings -> (businesses.contract_settings ->> 'default') END)->>'price')::double precision

1 个答案:

答案 0 :(得分:1)

每当您要在Mongo中“迭代”一个对象时,都会变得很混乱,因为Mongo要求您将该对象转换为数组并对其执行数组操作。

如果可能的话,我建议重新考虑contract_setting模式,这就是鉴于当前结构,这就是我要解决的问题:

db.contracts.aggregate([
  {
    $lookup: {
      from: "businesses",
      localField: "bid",
      foreignField: "id",
      as: "businesses"
    }
  },
  {
    $unwind: "$businesses" /**I'm assuming there's always 1.*/
  },
  {
    $addFields: {
      matchedPrice: {
        $reduce: {
          input: {
            $filter: {
              input: {
                $objectToArray: "$businesses.contract_settings"
              },
              as: "setting",
              cond: {
                $eq: [
                  "$$setting.k",
                  "$type"
                ]
              }
            }
          },
          initialValue: null,
          in: "$$this.v.price"
        }
      }
    }
  },
  {
    $addFields: {
      price: {
        $ifNull: [
          "$matchedPrice",
          {
            $reduce: {
              input: {
                $filter: {
                  input: {
                    $objectToArray: "$businesses.contract_settings"
                  },
                  as: "setting",
                  cond: {
                    $eq: [
                      "$$setting.k",
                      "$businesses.contract_settings.default"
                    ]
                  }
                }
              },
              initialValue: null,
              in: "$$this.v.price"
            }
          }
        ]
      }
    }
  },
  {
    $project: {
      price: 1,
      bid: 1,
      type: 1,
      name: 1
    }
  }
])

MongoPlayground