MongoDB 中的自连接查询

Self joining query in MongoDB

我们正在探索从关系数据库迁移到 MongoDB 的可能性,并且在处理查询时遇到困难,数据模式是这样的:

data_store:
    id,
    userId,
    study,
    form,
    formData: {
        element1: value1,
        element2: value2,
        .....
    }

formData 是一个 json 动态数组 element:value 对,不同的表单有不同的预定义元素列表。

要求是在一个研究中给定2个表单,我们需要为同一用户在一行中显示它们所有元素的数据,即我们需要通过userId加入并以平面结构显示formData。

另外,一个用户可能对同一个表单有多个数据输入,所以如果一个用户对表单 A 有 3 个条目,对表单 B 有 4 个条目,我们预计结果中有 12 行。

示例数据:

id: "id1",
user: "user1",
study: "study",
form: "f1",
formData: [
    { "e11": "value1" },
    { "e12": "value2" },
    { "e13": "value3" }
]

,

id: "id2",
user: "user1",
study: "study",
form: "f1",
formData: [
    { "e11": "value4" },
    { "e12": "value5" },
    { "e13": "value6" }
]

,

id: "id3",
user: "user1",
study: "study",
form: "f2",
formData: [
    { "e21": "value7" },
    { "e22": "value8" }
]

,

id: "id4",
user: "user1",
study: "study1",
form: "f2",
formData: [
    { "e21": "value9" },
    { "e22": "value10" }
]

,

id: "id2",
user: "user2",
study: "study1",
form: "f2",
formData: [
    { "e21": "value11" },
    { "e22": "value12" }
   

以上示例数据的预期结果是:

行# 学习 用户 f1.e11 f1.e12 f1.e13 f2.e21 f2.e22
1 学习 用户 1 值 1 值2 值3 值7 值8
2 学习 用户 1 值 1 值2 值3 值9 值 10
3 学习 用户 1 值4 值5 值6 值7 值8
4 学习 用户 1 值4 值5 值6 值9 值 10
5 学习 用户 2 值 11 值 12

关系数据库中类似的查询可能是这样的:

select t1.*, t2.*
from data_store t1, data_store t2
where t1.form = 'f1'
and t2.form = 'f2'
and t1.userId = t2.userId;

我很难将其转换为 MongoDB 查询,任何人都可以提供一些帮助,将不胜感激。

您可以使用下面的聚合查询来获得所需的结果。

db.collection.aggregate([
  {
    $group: {
      _id: {
        "user": "$user",
        "form": "$form",
        
      },
      "formData": {
        "$push": {
          "$concatArrays": [
            "$formData",
            [
              {
                "study": "$study"
              }
            ]
          ]
        },
        
      },
      
    }
  },
  {
    $group: {
      _id: {
        "user": "$_id.user",
        
      },
      formData: {
        "$push": "$formData"
      }
    }
  },
  {
    $project: {
      "formData": {
        "$map": {
          "input": {
            "$arrayElemAt": [
              "$formData",
              0
            ]
          },
          "as": "f1",
          "in": {
            "$cond": {
              "if": {
                "$gt": [
                  {
                    "$size": "$formData"
                  },
                  1
                ]
              },
              "then": {
                "$map": {
                  "input": {
                    "$arrayElemAt": [
                      "$formData",
                      1
                    ]
                  },
                  "as": "f2",
                  "in": {
                    "$concatArrays": [
                      "$$f1",
                      "$$f2"
                    ],
                    
                  },
                  
                },
                
              },
              "else": [
                "$$f1"
              ]
            },
            
          },
          
        },
        
      },
      
    }
  },
  {
    $unwind: {
      path: "$formData",
      
    }
  },
  {
    $unwind: {
      path: "$formData",
      
    }
  },
  {
    $replaceRoot: {
      newRoot: {
        "$mergeObjects": [
          {
            "user": "$_id.user"
          },
          {
            "$reduce": {
              "input": "$formData",
              "initialValue": {},
              "in": {
                "$mergeObjects": [
                  "$$this",
                  "$$value"
                ],
                
              }
            },
            
          },
          
        ],
        
      }
    }
  }
])

Mongo Playground Working Sample

如果你想要每个阶段的解释,请告诉我。

限制: 这仅在有两种形式的研究 f1f2.

时才有效

我正在努力使这个动态化,完成后会更新这个答案。

编辑:

利用这个动态运行的更新查询。

db.collection.aggregate([
  {
    "$match": {
      "form": {
        "$in": [
          "f1",
          "f2"
        ]
      },
      
    },
    
  },
  {
    "$group": {
      "_id": "$user",
      "formData": {
        "$push": {
          "$mergeObjects": [
            {
              "$reduce": {
                "input": "$formData",
                "initialValue": {},
                "in": {
                  "$mergeObjects": [
                    "$$value",
                    {
                      "$arrayToObject": {
                        "$map": {
                          "input": {
                            "$objectToArray": "$$this"
                          },
                          "as": "formValue",
                          "in": {
                            "k": {
                              "$concat": [
                                "$form",
                                "-",
                                "$$formValue.k"
                              ]
                            },
                            "v": "$$formValue.v"
                          }
                        },
                        
                      },
                      
                    },
                    
                  ]
                },
                
              }
            },
            {
              "study": "$study",
              "user": "$user",
              "form": "$form"
            },
            
          ],
          
        },
        
      },
      
    },
    
  },
  {
    "$project": {
      "formData": {
        "$cond": {
          "if": {
            "$gt": [
              {
                "$size": "$formData"
              },
              1
            ]
          },
          "then": {
            $reduce: {
              input: {
                $range: [
                  0,
                  {
                    $size: "$formData"
                  }
                ]
              },
              initialValue: [],
              in: {
                $concatArrays: [
                  "$$value",
                  {
                    $let: {
                      vars: {
                        i: "$$this"
                      },
                      in: {
                        $map: {
                          input: {
                            $range: [
                              {
                                $add: [
                                  1,
                                  "$$i"
                                ]
                              },
                              {
                                $size: "$formData"
                              }
                            ]
                          },
                          in: [
                            {
                              "$let": {
                                "vars": {
                                  "arrayElem1": {
                                    $arrayElemAt: [
                                      "$formData",
                                      "$$i"
                                    ]
                                  },
                                  "arrayElem2": {
                                    $arrayElemAt: [
                                      "$formData",
                                      "$$this"
                                    ]
                                  },
                                  
                                },
                                "in": {
                                  "$cond": {
                                    "if": {
                                      "$and": [
                                        {
                                          "$ne": [
                                            "$$arrayElem1.form",
                                            "$$arrayElem2.form"
                                          ]
                                        },
                                        //     {
                                        //       "$eq": [
                                        //         "$$arrayElem1.user",
                                        //         "$$arrayElem2.user"
                                        //        ]
                                        //      },
                                        
                                      ],
                                      
                                    },
                                    "then": {
                                      "$mergeObjects": [
                                        "$$arrayElem2",
                                        "$$arrayElem1",
                                        
                                      ],
                                      
                                    },
                                    "else": "$$REMOVE",
                                    
                                  },
                                  
                                },
                                
                              }
                            }
                          ]
                        }
                      }
                    }
                  }
                ]
              }
            }
          },
          "else": [
            "$formData"
          ]
        },
        
      },
      
    },
    
  },
  {
    "$project": {
      "formData": {
        "$reduce": {
          "input": "$formData",
          "initialValue": [],
          "in": {
            "$concatArrays": [
              "$$value",
              {
                "$cond": {
                  "if": {
                    "$ne": [
                      "$$this",
                      [
                        null
                      ]
                    ]
                  },
                  "then": "$$this",
                  "else": []
                }
              }
            ]
          }
        }
      }
    }
  },
  {
    "$unwind": "$formData"
  },
  {
    "$replaceRoot": {
      "newRoot": "$formData"
    }
  },
  
])

Mongo Playground Working Sample