如何使用mongoDb搜索单词序列集合中的输入单词

时间:2013-10-15 02:48:21

标签: sql mongodb mongodb-query

我对mongodb相对较新,到目前为止,我已经无法解决以前使用SQL后端解决的问题。

问题本身很简单:我在数据库中保存了大量任意长度的有序字序列,如:

Sequence #0: [ "word1", "word34", "word56", …, "word_66" ]
Sequence #1: [ "word45", "word2", "word334", "word45", …, "word_668" ] 
...

在数据库序列中,同一个单词可能会出现多次。

给定像[“word2”,“word334”]这样的输入字序列,我想要检索包含输入字的所有数据库序列,它们的顺序与它们的顺序相同。唯一的特点是:在输入的单词之间可以有一个单词(最多)。因此,例如,序列[“a”,“a”,“b”,“c”]将匹配输入[“a”,“c”],“b”是“跳过的单词”在这种情况下。让我们假设在执行任何搜索之前,所有序列都已使用任何数据模型写入数据库中;只有查询是有意义的。

使用SQL,这个问题很容易通过对规范化表的单个查询来解决。但是我想用mongoDb来解决同样的问题。

首先,我尝试在mongoDb中复制表格,就像我在SQL中一样。我想我会按照mongo查询语言调整查询。这样做不是很好,因为我没有办法在同一个find()中比较来自同一个集合的两个文档 - (这是一种模仿某种JOIN的天真尝试)。

接下来,我尝试在一个数组中,在“序列”文档中对序列字进行分组,并确保在同一文档中提供所有必要的信息。使用aggregate()管道,我试图看看我是否能够“链接”几个$ match阶段。在$ match阶段我能做的最好的事情是过滤掉包含所有输入单词的序列(使用$ all),但我不能再进一步因为我永远找不到mongoDb表达方式来表达“比较文档数组中的一个单词到同一文档数组中另一个单词的位置“(显然,你不能在查询比较运算符的右侧放置一个字段,即使是在同一个文档中)。

然后我虽然可以通过编写一个javascript函数来计算一个文档的解决方案并在$ match之后的$ where阶段调用它。我也没有成功使用这种方法,因为我找不到将输入序列(数组变量)传递给$ where子句的方法。我甚至尝试手工编写基于输入的查询字符串,以便它只包含已解析的常量来运行它,但无济于事(显然,$ where不喜欢将局部变量作为params传递,而不是像数组常量)。我尝试将功能上传到服务器,以防万一,但我也没有运气,限制似乎或多或少相同。

此时,我能看到的唯一可行的方法是将所有序列单词放在一个大字符串中,并使用可执行匹配的$ regex运算符。这可能有点难看,因为可能是“跳过的单词”,而且由于正则表达式而且非常慢,并且因为在这种情况下也不会使用索引。我没有尝试成熟的mapReduce管道,但乍一看它似乎就像聚合()的扩展,所以可能同样不适合解决这个问题。

我已经阅读了几次文档,并没有发现任何可以帮助我的其他“特殊功能”或概念。知道MongoDB建模的人能否推动我采用一些有效的方法?什么是mongoDb友好的数据模型呢?我不是在寻找与此特定问题相关的一些设计大纲的代码。 THX。

1 个答案:

答案 0 :(得分:0)

我终于解决了这个问题,虽然我不确定我的解决方案有多优化(我会做一些自动化测试来确定它的速度/可扩展性)。

因为mongoDb无法在同一个查询中将两个单独的文档进行比较(可能是为了确保查询可以并行化),所以很难仅依靠其查询“语言”来计算任何类型的连接属性。就我而言,我的结构相当于一个简单的DAG。因此,不是使用指示其位置的索引存储序列的各个节点(单词)(并且让查询语言通过SQL中的索引的间隔检查来推断连接),而是使用边缘作为基本文档,即:word_i(开始节点)连接到word_j(结束节点)。对于这个边缘文档,我添加了序列ID,以及订购信息。要创建数据库集合,因为我可以在匹配之间跳过一个单词,我需要为每个起始单词添加两个边。我还添加了所有索引,以便$ sort和$ match阶段可以利用它们。

根据输入序列,我首先使用具有几个阶段的聚合器:

第1阶段:当我想搜索输入序列时(例如:[“word_a”,“word_b”,“word_c”]),我首先将所有与推断相匹配的数据库边缘向上舍入($ match)在这种情况下,通过输入序列:(“word_a” - >“word_b”)和(“word_b” - >“word_c”)。当然,这将检索误报,因为我不能表示只要匹配,“word_b”在两个边缘都是相同的实例,并且我无法确保相同的边缘不会匹配两次该序列与另一条边根本不匹配,我也会得到匹配太少的序列。

第2阶段:我按序列ID排序结果并开始单词索引。

阶段3:我按序列_id对先前的结果进行分组。阶段2中的排序将边缘按序列中的外观顺序排列。同时,我$ addToSet一组中的所有启动节点,另一组中的结束节点。我还总计了每组中匹配边的数量。

阶段4:I $匹配删除所有不包含足够边缘的组以匹配输入(这样可以删除不包含我想要的所有边缘的序列)。

第5,6,7,8阶段:我的下一个目标是确保考虑我想要的所有单词,因此我计算在第3阶段计算的每个集合中的元素数量($ unwind the set ,然后是一个$ group,$ sum:计数字段中的1。)

阶段9:I $匹配只保留具有相同单词集基数的边缘与从输入序列派生的相同。 (这实现了与我第一次尝试解决方案的“全部”完全相同的目标。)

阶段10:我$ project总结下一步的所有字段。

我在聚合器中唯一无法计算的是边缘之间的连接:我不知道边缘的末端节点是否与后续边缘的起始节点匹配。但是因为匹配序列的集合现在减少到最小并且所有边缘按照序列中的出现顺序排序,我可以在Javascript中快速传递以仅保留所需边缘连续出现的序列(由单词跳过容差约束)。我会尝试在聚合器中填充所有这些,但不幸的是$ where在那里似乎没有用。

在任何情况下,这似乎足够快,无论输入长度如何(在随机生成的样本数据库上),速度似乎都是恒定的,并且在javascript中花费的时间可以忽略不计。正如预期的那样,边缘表增长了很多但是使用mongo,这应该不是我希望的问题。我当然也不能轻易改变单词跳过约束值(例如2),因为这需要修改主集合。

结论:虽然可能有更快更好的解决方案,但通过这项工作,我更加熟悉了mongoDb。我希望mongo团队最终能够加强他们的“单文档评估语言集”,以涵盖其他不会破坏其并行模型的便捷运算符,例如数组的$ size,$ removeFromSet,在右边的字段运算符,测试数组值,更多算术运算符,如$ sqrt等......