如何使用.aggregate从多个集合中获取数据,包括Mongoose + NextJS的sum值

3ks5zfa0  于 2023-11-19  发布在  Go
关注(0)|答案(1)|浏览(137)

我试图从两个模式返回数据。其中一个包含'球员'的信息,另一个包含每个'球员'的比赛'得分'的信息。

这是我的'播放器'模式:

import { Schema, model, models } from "mongoose";

const PlayerSchema = new Schema({
    creator: {
        type: Schema.Types.ObjectId,
        ref: 'User',
    },

    name: {
        type: String,
        require: [ true, 'Name is required!' ],
    },

    email: {
        type: String,
        unique: [ true, 'Email already exist!' ],
        required: [ true, 'Email is required!' ],
    }
});

const Player = models.Player || model( 'Player', PlayerSchema );

export default Player;

字符串

**这是我的'Score'架构:(注意这一个还包含子文档内容)

import { Schema, model, models } from "mongoose";

const scoresToSave = new Schema({
    player: {
        type: Schema.Types.ObjectId,
        ref: 'Player',
    },
    score: { type: Number }
});

const ScoreSchema = new Schema({
    creator: {
        type: Schema.Types.ObjectId,
        ref: 'User',
    },

    tournament: {
        type: Schema.Types.ObjectId,
        ref: 'Tournament',
    },

    date: {
        type: Date,
        require: [ true, 'Date is required!' ],
    },

    scores: [scoresToSave],
});

const Score = models.Score || model( 'Score', ScoreSchema );

export default Score;

这是保存在收藏中的实际文档的示例。
玩家:

{
"_id":{"$oid":"652f3470729e8d8a59b221dd"},
"creator":{"$oid":"652af415729e8d8a59b2202f"},
"name":"Player 1",
"email":"[email protected]"
}

{
"_id":{"$oid":"652f36c9f6ad40c3aedf0cdf"},
"creator":{"$oid":"652af415729e8d8a59b2202f"},
"name":"Player 2",
"email":"[email protected]"
}

分数

{
"_id":{"$oid":"65386103b5e6b094e0980f2a"},
"creator":{"$oid":"652af415729e8d8a59b2202f"},
"tournament":{"$oid":"652f2515729e8d8a59b221b6"},
"date":{"$date":{"$numberLong":"1698192000000"}},
"scores":[
  {
    "player":{"$oid":"652f3470729e8d8a59b221dd"},
    "score":{"$numberInt":"10"},
    "_id":{"$oid":"65386103b5e6b094e0980f2b"}
  },
  {
    "player":{"$oid":"652f36c9f6ad40c3aedf0cdf"},
    "score":{"$numberInt":"20"},
    "_id":{"$oid":"65386103b5e6b094e0980f2c"}
  }
]
}

{
"_id":{"$oid":"6538611bb5e6b094e0980f59"},
"creator":{"$oid":"652af415729e8d8a59b2202f"},
"tournament":{"$oid":"652f2515729e8d8a59b221b6"},
"date":{"$date":{"$numberLong":"1698192000000"}},
"scores":[
  {
    "player":{"$oid":"652f3470729e8d8a59b221dd"},
    "score":{"$numberInt":"11"},
    "_id":{"$oid":"6538611bb5e6b094e0980f5a"}
  },
  {
    "player":{"$oid":"652f36c9f6ad40c3aedf0cdf"},
    "score":{"$numberInt":"21"},
    "_id":{"$oid":"6538611bb5e6b094e0980f5b"}
  }
]
}

这是我想创建的响应数据结构:

[
  {
    _id: new ObjectId("652f3470729e8d8a59b221dd"),
    name: 'Player 1',
    email: '[email protected]',
    totalScore: 21
  },
  {
    _id: new ObjectId("6536f85cb5e6b094e09809f1"),
    name: 'Player 2',
    email: '[email protected]',
    totalScore: 41
  },
]

我尝试获取该数据结构的实际代码是:

import { connectToDB } from "@utils/database";
import Player from "@models/player";

export const GET = async ( req ) => {
    const resources = {
        "_id": "$_id",
        name: { "$first": "$name" },
        email: { "$first": "$email" },
        "totalScore": { "$sum": "scores.scores.score" },
    };

    try {
        await connectToDB();

        const players = await Player.aggregate([
            {
                $group: resources
            }, {
                $lookup: {
                    from: "Scores",
                    localField: "_id",
                    foreignField: "scores.player",
                    as: "score"
                }
            }
        ]);

        console.log('TRY: ', players);

        return new Response(
            JSON.stringify(players),
            {status: 200}
        );
    } catch (error) {
        return new Response(
            "Failed to fetch all players",
            {status: 500}
        );
    }
}

以下是console.log('TRY: ', players);返回的内容:

TRY:  [
  {
    _id: new ObjectId("652f3470729e8d8a59b221dd"),
    name: 'Player 1',
    email: '[email protected]',
    totalScore: 0,
    score: []
  },
  {
    _id: new ObjectId("6536f85cb5e6b094e09809f1"),
    name: 'Player 2',
    email: '[email protected]',
    totalScore: 0,
    score: []
  },
]


有没有人能帮我理解我错过了什么?
谢谢你,谢谢

vecaoik1

vecaoik11#

我认为在scores集合上执行aggregate会更容易。因为scores.score字段位于该集合上,所以可以在该集合上执行$sum。可能有更有效的方法,但此查询应该返回您想要的结果:

const players = await Scores.aggregate([
  {
    $unwind: {
      path: "$scores"
    }
  },
  {
    $group: {
      _id: "$scores.player",
      totalScore: {
        $sum: "$scores.score"
      }
    }
  },
  {
    $lookup: {
      from: "players",
      localField: "_id",
      foreignField: "_id",
      as: "fromPlayers"
    }
  },
  {
    $replaceRoot: {
      newRoot: {
        $mergeObjects: [
          {
            $arrayElemAt: [
              "$fromPlayers",
              0
            ]
          },
          "$$ROOT"
        ]
      }
    }
  },
  {
    $project: {
      fromPlayers: 0,
      creator: 0
    }
  }
])

字符串
请参阅HERE以获取工作示例。

说明

  1. $unwind:扁平化scores数组,这样我们每个scores文档得到一个对象。这显然会重复文档,但更容易按玩家分组。
  2. $group:按玩家对每个对象进行分组,$sum为它们的分数。
  3. $lookup:现在加入基于_idplayers集合。但是因为$lookup返回一个数组,所以只需分配给一个名为fromPlayers的新字段。
  4. $replaceRoot:通过将fromPlayers数组与上一个管道阶段的原始文档合并,创建一个新的根文档。
  5. $project:现在从输出中删除fromPlayers数组和creator字段。

相关问题