Elasticsearch - 根据得分排名userIds

Tie*_*eme 6 aggregate elasticsearch

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

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

{
  "userId": 2,
  "scoreId": 3457,
  "game": {
    "id": 6,
    "name": "scrabble"
  },
  "date": 1340047100,
  "score": 56,
  // and more game data
}
Run Code Online (Sandbox Code Playgroud)

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

{
  "userId": 6,
  "gameId": 3479,
  "game": {
    "id": 5,
    "name": "risk"
  },
  "date": "1380067200",
  "score": 100,
  // and more game data
}
Run Code Online (Sandbox Code Playgroud)

多年来,我们玩了许多不同的游戏,我想为每种类型的游戏排名最好的玩家.排名基于每位玩家的最佳6场比赛.因此,例如,如果玩家玩拼字游戏10次,则只有6个最佳分数计入其总分.

我想创建一个列表,如:

// Scrabble ranking:
# | user | total points  
1 |  2   | 4500
2 |  6   | 3200
2 |  23  | 1500
Run Code Online (Sandbox Code Playgroud)

迁移的原因是旧的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
}
Run Code Online (Sandbox Code Playgroud)

我正在使用弹性2.3,但如果真的有必要,我有机会升级.

Val*_*Val 4

使用top_hits不会让您实现所需的效果,因为您无法对顶部点击聚合中每个文档返回的字段进行操作。

解决这个问题的一种方法是对用户使用顶级terms聚合(就像您所做的那样),然后对每个用户使用另一个terms分数子聚合,您可以按降序排序,并只取 6 个最好的分数。最后,使用管道sum_bucket聚合,您可以总结每个用户的这 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"
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

另请注意,根据用户的值对用户进行排序是理想的选择total_points,但不可能使用管道聚合进行排序(因为它们在归约阶段之后运行)。排序需要在客户端进行。

  • 很公平,@AndreiStefan,但在这个特定的上下文中,我不确定脚本如何帮助克服我提到的两个缺点,即对用户术语进行排序并确保“total_score”仅包含 6 个分数,且不多不少。除非我们当然可以完全修改数据模型。 (2认同)