【问题标题】:Elasticsearch - Rank userIds based on scoreElasticsearch - 根据分数对用户 ID 进行排名
【发布时间】:2017-10-01 13:20:21
【问题描述】:

我正在尝试将旧 MySQL 数据库的一些查询迁移到新的 Elasticsearch 设置。数据稍微复杂一点,但归结为以下几点:

我有一个包含很多分数的索引。每个分数代表玩家在特定游戏中的得分。

{
  "userId": 2,
  "scoreId": 3457,
  "game": {
    "id": 6,
    "name": "scrabble"
  },
  "date": 1340047100,
  "score": 56,
  // and more game data
}

scoreId 是该分数的唯一 ID,game.id 是该类型游戏的 ID。

{
  "userId": 6,
  "gameId": 3479,
  "game": {
    "id": 5,
    "name": "risk"
  },
  "date": "1380067200",
  "score": 100,
  // and more game data
}

多年来,人们玩了许多不同的游戏,我想对每种游戏的最佳玩家进行排名。排名基于每位玩家的最佳 6 场比赛。因此,例如,如果一个玩家玩了 10 次拼字游戏,那么只有其 6 个最好的分数才计入其总分。

我想创建一个类似的列表:

// Scrabble ranking:
# | user | total points  
1 |  2   | 4500
2 |  6   | 3200
2 |  23  | 1500

迁移的原因是旧的 MySQL 查询首先获取每个游戏的所有不同用户的列表,然后为每个用户执行另一个查询以获得最好的 6 个分数。我希望我可以使用弹性的聚合在一个查询中完成所有操作,但到目前为止我还不能让它工作。

问题是,在阅读了弹性文档几个小时后,我的问题似乎比示例更复杂。也许如果有人能指出我正确的方向,我可以继续搜索。至少这对我没有任何帮助:

GET /my-index/scores/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term": { "game.id": 6 }}
      ]
    }
  },
  "aggs": {
    "scores": {
      "terms": {
        "field": "userId"
      }
    },
    "top_scores_user": {
      "top_hits": {
        "sort": [{
          "score": {
            "order": "desc"
          }
        }],
        "size" : 6
      }
    }
  },
   "size": 0
}

我使用的是 elastic 2.3,但如果真的有必要,我可以升级。

【问题讨论】:

  • 您能详细说明为什么您尝试的查询不能满足您的要求吗?没有仔细看(测试一些东西),但第一次阅读它似乎是正确的......
  • 我在当前查询中看到的问题是您无法按用户的 6 个最佳分数之和对用户进行排序,即对于每个用户,您可以获得 6 个最佳分数,但您不能根据它们的总和对它们进行排名。对吗?
  • 实际上你有一个小错误(不是格式错误),但功能。第二个聚合应该是第一个聚合的子聚合。
  • 平心而论,@AndreiStefan 不是我发现了查询中的错误 :-)
  • @Val 也会发现这一点,我敢肯定 :-)。

标签: elasticsearch aggregate


【解决方案1】:

使用top_hits 不会让您实现所需的功能,因为您无法对热门聚合中的每个文档返回的字段进行操作。

解决此问题的一种方法是为用户使用顶级 terms 聚合(如您所做的那样),然后为每个用户使用另一个 terms 子聚合,您可以按降序排序和只取6个最好的。最后,使用pipeline sum_bucket aggregation,您可以总结每个用户的这 6 个分数。

POST /my-index/scores/_search    
{
  "size": 0,
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "game.id": 6
          }
        }
      ]
    }
  },
  "aggs": {
    "users": {
      "terms": {              <--- segment by user
        "field": "userId"
      },
      "aggs": {
        "best_scores": {
          "terms": {          <--- 6 best scores for user
            "field": "score",
            "order": {
              "_term": "desc"
            },
            "size": 6
          },
          "aggs": {
            "total_score": {
              "sum": {
                "field": "score"
              }
            }
          }
        },
        "total_points": {     <--- total points for the user based on 6 best scores
          "sum_bucket": {
            "buckets_path": "best_scores > total_score"
          }
        }
      }
    }
  }
}

请注意,此解决方案的一个缺点是,如果用户有两次完全相同的分数,您将获得 7 个最佳分数,而不是 6 个最佳分数,并且total_score 的值会太高。我们可以使用avg 而不是sum 度量聚合,但是如果我们这样做,我们将忽略其中一个分数出现,这也不好。

还请注意,根据用户的total_points 值对用户进行排序是理想的,但无法使用管道聚合进行排序(因为它们在缩减阶段之后运行)。排序需要在客户端进行。

【讨论】:

  • 关于您的第一条评论,您使用的是什么客户端语言?
  • 关于您的第二条评论,答案是否定的。这不会很有趣,因为解决方案太简单了:-)
  • 你成功了!使用脚本不是作弊,但我不确定它在这里会有什么帮助。还在想……
  • 好吧,脚本基本上完成了您使用自己的编程语言所做的工作。现在它取决于脚本的速度和您选择的编程语言的速度。但是,使用脚本您将无法使用 Elasticsearch 和 Lucene 的真正/原生功能。这就是为什么我们建议尽可能地调整数据/映射/查询以适应您的需求,以使用 ES 的本机查询/数据结构,而不是从查询的角度使一切成为可能。但我同意在某些情况下无法更改数据,例如,唯一的选择是脚本。
  • 很公平,@AndreiStefan,但在这个特定的上下文中,我不确定脚本如何帮助克服我提到的两个缺点,即对用户术语进行排序并确保 total_score 包含只有6分,不多不少。除非我们当然可以完全修改数据模型。
猜你喜欢
  • 2021-07-22
  • 1970-01-01
  • 1970-01-01
  • 2019-12-31
  • 2021-05-30
  • 1970-01-01
  • 2020-12-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多