【问题标题】:Dynamic Sticky Sorting in Mongo for a simple value or listMongo中的动态粘性排序以获得简单的值或列表
【发布时间】:2014-05-20 00:32:29
【问题描述】:

我正在尝试对一组记录进行动态粘性排序,每个查询的粘性值都不同。让我举个例子吧。以下是一些示例文档:

{first_name: 'Joe', last_name: 'Blow', offices: ['GA', 'FL']}
{first_name: 'Joe', last_name: 'Johnson', offices: ['FL']}
{first_name: 'Daniel', last_name: 'Aiken', offices: ['TN', 'SC']}
{first_name: 'Daniel', last_name: 'Madison', offices: ['SC', 'GA']}
... a bunch more names ...

现在假设我想按姓氏的字母顺序显示姓名,但我想将所有记录与顶部的名字“Joe”挂钩。

在 SQL 中这是相当直接的:

SELECT * FROM people ORDER first_name == 'Joe' DESC, last_name

将表达式放入排序条件的能力使这变得微不足道。使用聚合框架我可以做到这一点:

[
  {$project: {
    first_name: 1,
    last_name: 1
    offices: 1,
    sticky: {$cond: [{$eq: ['$first_name', 'Joe']}, 1, 0]}
  }},
  {$sort: [
    'sticky': -1,
    'last_name': 1
  ]}
]

基本上,我使用聚合框架创建一个动态字段,如果名称为 Joe,则为 1,如果名称不是 Joe,则为 0,然后按相反顺序排序。当然,在构建我的聚合管道时,我可以轻松地将“Joe”更改为“Daniel”,现在“Daniel”将与顶部挂钩。这部分是我所说的动态粘性排序的意思。我的粘性排序值将逐个查询改变

现在这对于像字符串这样的基本值非常有用。当我尝试对包含数组的值进行相同的操作时,问题就来了。假设我想锁定“FL”办公室中的所有用户。凭借 Mongo 对数组的原生理解,我认为我可以做同样的事情。所以:

[
  {$project: {
    first_name: 1,
    last_name: 1
    offices: 1,
    sticky: {$cond: [{$eq: ['$offices', 'FL']}, 1, 0]}
  }},
  {$sort: [
    'sticky': -1,
    'last_name': 1
  ]}
]

但这根本不起作用。我确实发现,如果我将其更改为以下内容,它将把 Joe Johnson(他只在 FL 办公室)放在首位:

[
  {$project: {
    first_name: 1,
    last_name: 1
    offices: 1,
    sticky: {$cond: [{$eq: ['$offices', ['FL']]}, 1, 0]}
  }},
  {$sort: [
    'sticky': -1,
    'last_name': 1
  ]}
]

但这并没有让 Joe Blow 位居榜首(他在佛罗里达州和乔治亚州)。我相信它正在做简单的匹配。所以我的第一次尝试根本不起作用,因为 $eq 返回 false 因为我们正在将数组与字符串进行比较。第二次尝试适用于 Joe Johnson,因为我们正在比较完全相同的数组。但是自从 ['GA', 'FL'] != ['FL'] 之后,Joe Blow 就不起作用了。另外,如果我想将 FL 和 SC 都放在顶部,我不能给它值 ['FL', 'SC'] 来比较。

接下来我尝试使用 $setUnion 和 $size 的组合。

[
  {$project: {
    first_name: 1,
    last_name: 1
    offices: 1,
    sticky: {$size: {$setUnion: ['$offices', ['FL', 'SC']]}}
  }},
  {$sort: [
    'sticky': -1,
    'last_name': 1
  ]}
]

我尝试过使用 $let 和 $literal 的各种组合,但它总是抱怨我试图将文字数组传递给 $setUnion 的参数。具体来说:

disallowed field type Array in object expression

有什么办法吗?

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    无法重现您的错误,但您的问题中有一些“错字”,因此我无法确定您的实际情况。

    但假设您实际上使用的是 MongoDB 2.6 或更高版本,那么您可能需要$setIntersection$setIsSubset 运算符而不是$setUnion。这些运算符暗示了它们所比较的数组的“匹配”内容,其中$setUnion 只是将提供的数组与现有的数组结合起来:

    db.people.aggregate([
        { "$project": {
            "first_name": 1,
            "last_name": 1,
            "sticky": { 
                "$size": { 
                    "$setIntersection": [ "$offices", [ "FL", "SC" ]] 
                }
            },
            "offices": 1
        }},
        { "$sort": {
            "sticky": -1,
            "last_name": 1
        }}
    ])
    

    在没有set operators 的先前版本中,您只是使用$unwind 来处理数组,并且在$group 中使用与以前相同的$cond 操作将它们重新组合在一起:

    db.people.aggregate([
        { "$unwind": "$offices" },
        { "$group": {
            "_id": "$_id",
            "first_name": { "$first": "$first_name" },
            "last_name": { "$first": "$last_name",
            "sticky": { "$sum": { "$cond": [
                { "$or": [
                    { "$eq": [ "$offices": "FL" ] },
                    { "$eq": [ "$offices": "SC" ] },
                ]},
                1,
                0
            ]}},
            "offices": { "$push": "$offices" }
        }},
        { "$sort": {
            "sticky": -1,
            "last_name": 1
        }}
    ])
    

    但你肯定是在正确的轨道上。只需选择正确的设置操作或其他方法即可获得您的精确需求。


    或者,既然您已经发布了获得所需内容的方式,那么编写这种“有序匹配”的更好方法是:

    db.people.aggregate([
        { "$project": {
            "first_name": 1,
            "last_name": 1,
            "sticky": { "$cond": [
                { "$anyElementTrue": {
                    "$map": {
                        "input": "$offices",
                        "as": "o",
                        "in": { "$eq": [ "$$o", "FL" ] }
                    }
                }},
                2,
                { "$cond": [
                    { "$anyElementTrue": {
                        "$map": {
                            "input": "$offices",
                            "as": "o",
                            "in": { "$eq": [ "$$o", "SC" ] }
                        }
                    }},
                    1,
                    0
                ]}
            ]},
            "offices": 1
        }},
        { "$sort": {
            "sticky": -1,
            "last_name": 1
        }}
    ])
    

    这将使包含“FL”的“办公室”优先于“SC”,因此优先于所有其他文件,并在单个字段中进行操作。人们也应该很容易看到如何在没有集合运算符的早期版本中使用$unwind 将其抽象为表单。您只需通过嵌套$cond 语句为顶部的项目提供更高的“权重”值。

    【讨论】:

    • 感谢您的反馈。我在最新的 Mongo 上。我的意思是setIntersection。所以我打错了(在我的问题和代码中)。我可能还有其他拼写错误,因为我试图将我的问题与提交的 JSON 隔离,而不是生成该 JSON 的 PHP。因此,如果我坚持下去,使用 set 方法可能会奏效。我发布的替代方法实际上效果更好,因为它首先放置了我想要粘的第一个项目,然后是第二个,等等,而不是像集合运算符那样混合它们。去看看我是在正确的轨道上,它是可能的设置操作。
    • @EricAnderson 主要关注一个绝对不会做你想做的事的操作员。我建议的任何文字问题实际上都在您的生成器中,因为上面显示的代码将直接放入 shell(或有效地解析为 JSON )并以通常所需的结果执行。您发布的答案仍然包含无效的 JSON,您不应该需要变量映射。我会追查这个问题的实际出处,但不是 MongoDB。
    【解决方案2】:

    我想我找到了最好的方法。

    [
      {$project: {
        first_name: 1,
        last_name: 1
        offices: 1,
        sticky_0: {
          $cond: [{
            $anyElementTrue: {
              $map: {
                input: "$offices",
                as:    "j",
                in:    {$eq: ['$$j', 'FL']}
              }
            }
          }, 0, 1]
        },
        sticky_1": {
          $cond: [{
            $anyElementTrue: {
              $map: {
                input: '$offices',
                as:    'j',
                in:    {$eq: ['$$j', 'SC']}
              }
            }
          }, 0, 1]
        }
      }},
      {$sort: [
        'sticky_0': 1,
        'sticky_1': 1,
        'last_name': 1
      ]}
    ]
    

    基本上,在构建我的管道时,我会遍历每个我想要粘贴的项目,并从该项目创建它自己的虚拟字段,只检查一个值。为了只检查一个值,我使用了 $cond、$anyElementTrue 和 $map 的组合。这有点令人费解,但它有效。很想听听是否有更简单的东西。

    【讨论】:

      猜你喜欢
      • 2013-07-12
      • 2022-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多