【问题标题】:C# Mongodb cartesian product of multiple object array documentsC# Mongodb 多对象数组文档的笛卡尔积
【发布时间】:2023-03-19 14:33:01
【问题描述】:

尝试使用 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 个文档也有 3 个对象数组,这些对象数组将在数组中生成一个包含 27 个项目的结果集 - 或者说是 27 行。

希望找到如何使用 C#(Linq?)Mongodb 驱动程序来查询和返回这样的数据的解决方案,但即使是特定于 mongodb 的查询也将开放,因为我希望可以从那里反转逻辑。谢谢

【问题讨论】:

  • 是你的两个独立的集合还是你想迭代一个集合。你能解释更多你想要什么以及你尝试了什么
  • 我其实有个问题,为什么你的模型是{[movies],[actors]} 而不是Movie{ movie, showday, [Actors] } 请原谅我是题外话
  • 由于数据中根本没有关系,所以“数据库”本身无法做到这一点。 MongoDB 不像 RDBMS,它可以从多个来源任意提取数据并在其上运行功能输出。事实上,除了$lookup 等之外,所有操作实际上一次只针对一个集合,但这些操作员需要一个相关的密钥。您可以在代码中执行此操作,或使用.eval() 在服务器上运行代码。但是不要使用eval()。所以这是一个编码练习,而不是一个数据库解决方案。

标签: c# arrays mongodb linq


【解决方案1】:

您可以尝试以下聚合管道。

注意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
        }
       }
      }
     }
    }
   }
  }
 }
});

更新

由于第一个解决方案中的 $cond 和第二个解决方案中的 $setUnion 不正确,上述两种解决方案都不适用于跨数组重复值,如下面的 Asya Kamsky 的 cmets 所述。

正确的解决方法是

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"] 
        }
       }
      }
     }
    }
   }
  }
 }
});

参考: Cartesian product of multiple arrays in JavaScript

【讨论】:

  • 如果任何字段具有跨数组重复的任何值,则此方法不起作用
  • @AsyaKamsky 是的,100% 正确。我知道这一点,但找不到任何东西来替换条件表达式。我也添加了以前的修订版,我认为这是正确的。
  • 不,如果您有重复的元素,这将无法正常工作。
  • 您是指使用$mergeObjects 删除数组中的重复字段吗?还是使用$setUnions 删除数组中的重复文档?
  • 不,我是说如果您使用带有数据的文档测试代码: [ [ { "a" : 1 }, { "a" : 1 } ], [ { "a" : 1 }, { "a" : 1 } ], [ { "a" : 1 } ] ] 你会看到它返回了错误的结果数量 - 我希望 4 并且它返回 2。
猜你喜欢
  • 1970-01-01
  • 2012-05-14
  • 2011-12-18
  • 1970-01-01
  • 2016-07-10
  • 2013-09-28
  • 2021-07-16
  • 2022-01-17
相关资源
最近更新 更多