C# Mongodb 多个对象数组文档的笛卡尔积

6
尝试使用C# / Linq或原始的Mongodb查询本身来理解如何将多个数组作为笛卡尔积连接。例如,假设我有一个集合,我将其过滤为以下两个文档:
[
{"movie":"starwars","showday":"monday"},
{"movie":"batman","showday":"thursday"},
{"movie":"sleepless","showday":"tuesday"}
]

[
{"actor":"angelina","location":"new york"},
{"actor":"jamie","location":"california"},
{"actor":"mcavoy","location":"arizona"}
]

如何将每个数组中的每个项连接起来以产生以下类型的结果?
[{"movie":"starwars","showday":"monday","actor":"angelina","location":"new york"},
{"movie":"batman","showday":"thursday","actor":"angelina","location":"new york"},
{"movie":"sleepless","showday":"tuesday","actor":"angelina","location":"new york"},
{"movie":"starwars","showday":"monday","actor":"jamie","location":"california"},
{"movie":"batman","showday":"thursday","actor":"jamie","location":"california"},
{"movie":"sleepless","showday":"tuesday","actor":"jamie","location":"california"},
{"movie":"starwars","showday":"monday","actor":"mcavoy","location":"arizona"},
{"movie":"batman","showday":"thursday","actor":"mcavoy","location":"arizona"},
{"movie":"sleepless","showday":"tuesday","actor":"mcavoy","location":"arizona"}]

我正在寻找一种解决方案,可以适用于任意数量的文档。例如,在这个例子中,如果有第三个文档也有3个对象数组,那么会产生一个包含27个项目的结果集 - 或者说27行。

希望找到如何使用C#(Linq?)Mongodb驱动程序查询和返回此类数据的解决方案,但即使是mongodb特定的查询,我也会尝试从中反向推理出逻辑。谢谢


你的两个集合是分开的还是你想遍历一个集合?并且你能详细说明一下你想要什么以及你尝试了什么吗? - Neo-coder
我有一个问题,为什么你的模型是{[电影],[演员]}而不是电影{电影,上映日期,[演员]}?请原谅这个话题有些偏离。 - Munzer
1
由于数据中根本没有关系,因此“数据库”本身无法执行此操作。MongoDB不像关系型数据库那样可以从多个来源任意提取数据并对其运行功能输出。实际上,所有的操作都是针对一个集合进行的,除了 $lookup 等类似操作,但这些操作需要有关键字。你需要在代码中完成这个任务,或者使用 .eval() 在服务器上运行代码。但不要使用 eval()。因此,这是一个编程练习而不是数据库解决方案。 - Neil Lunn
1个回答

3
您可以尝试以下聚合管道。
请注意,聚合运算符mergeObjects现已在3.5.6 +的开发版本中发布,并将被整合到即将发布的3.6版本中。
db.collection.find();
{
 "data" : [
  [
   {
    "movie" : "starwars",
    "showday" : "monday"
   },
   {
    "movie" : "batman",
    "showday" : "thursday"
   },
   {
    "movie" : "sleepless",
    "showday" : "tuesday"
   }
  ],
  [
   {
    "actor" : "angelina",
    "location" : "new york"
   },
   {
    "actor" : "jamie",
    "location" : "california"
   },
   {
    "actor" : "mcavoy",
    "location" : "arizona"
   }
  ]
 ]
}

使用条件表达式进行聚合。

aggregate({
 $project: {
  cp: {
   $reduce: {
    input: "$data",
    initialValue: {
     $arrayElemAt: ["$data", 0] // Set the initial value to the first element of the arrays.
    },
    in: {
     $let: {
      vars: {
       currentr: "$$this", // Current processing element
       currenta: "$$value" // Current accumulated value 
      },
      in: {
       $cond: [{ // Conditional expression to return the accumulated value as initial value for first element
        $eq: ["$$currentr", "$$currenta"]
       },
       "$$currenta",
       { // From second element onwards prepare the cartesian product
        $reduce: {
         input: {
          $map: {
           input: "$$currenta",
           as: a"a",
           in: {
            $map: {
             input: "$$currentr",
             as: r"r",
             in: {
              $mergeObjects: ["$$a", "$$r"] // Merge accumulated value with the current processing element
             }
            }
           }
          }
         },
         initialValue: [],
         in: {
         $concatArrays: ["$$value", "$$this"] // Reduce the merged values which will be used as accumulator for next element
         }
        }
       }]
      }
     }
    }
   }
  }
 }
});

聚合(使用$setUnion)。

该解决方案仅添加了抑制条件表达式的内容,以提供更易读的管道。

aggregate({
 $project: {
  cp: {
   $reduce: {
    input: "$data",
    initialValue: {
     $arrayElemAt: ["$data", 0] // Set the initial value to the first element of the arrays.
    },
    in: {
     $let: {
      vars: {
       currentr: "$$this", // Current processing element
       currenta: "$$value" // Current accumulated value 
      },
      in:{ 
       $reduce: {
        input: {
         $map: {
          input: "$$currenta",
          as: "a",
          in: {
           $map: {
            input: "$$currentr",
            as: "r",
            in: {
             $mergeObjects: ["$$a", "$$r"] // Merge accumulated value with the current processing element
            }
           }
          }
         }
        },
        initialValue: [],
        in: {
         $setUnion: ["$$value", "$$this"] // Reduce the merged values which will be used as accumulator for next element
        }
       }
      }
     }
    }
   }
  }
 }
});

更新

正如Asya Kamsky下方的评论所指出的那样,上述两种方法都无法处理数组中重复值的情况,这是由于第一种方法中$cond不正确,第二种方法中$setUnion有误。

正确的解决方法是:

initialValue 开始设置为 [ {} ]

或者

更改 input 来排除第一个元素,例如: input: {$slice:["$data", 1, {$subtract:[{$size:"$data"},1]}]},

完整的聚合管道

aggregate({
 $project: {
  cp: {
   $reduce: {
    input: {$slice:["$data", 1, {$subtract:[{$size:"$data"},1]}]},
    initialValue: {$arrayElemAt:["$data",0]},
    in: {
     $let: {
      vars: {
       currentr: "$$this", 
       currenta: "$$value" 
      },
      in:{ 
       $reduce: {
        input: {
         $map: {
          input: "$$currenta",
          as: "a",
          in: {
           $map: {
            input: "$$currentr",
            as: "r",
            in: {
             $mergeObjects: ["$$a", "$$r"] 
            }
           }
          }
         }
        },
        initialValue: [],
        in: {
         $concatArrays: ["$$value", "$$this"] 
        }
       }
      }
     }
    }
   }
  }
 }
});

参考资料: JavaScript中多个数组的笛卡尔积


如果数组中有任何值重复,此代码将无法正常工作。 - Asya Kamsky
@AsyaKamsky 是的,那是100%正确的。我知道这一点,但只是找不到任何东西来替换条件表达式。我也添加了之前的修订,我相信那是正确的。 - s7vr
你是在指 $mergeObjects 的用法,它可以删除数组中重复的字段吗?还是指 $setUnions 的用法,它可以删除数组之间重复的文档? - s7vr
或者,只需将initialValue设置为[{}]并输入"$data",但完全摆脱$cond,您永远不需要它。 - Asya Kamsky
感谢您宝贵的反馈。已根据您提出的修正更新了答案。 - s7vr
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接