У меня есть коллекция отзывов об обратной связи (Communication, Timeliness & Delivery), все они могут содержать 1-5 значений. То, что мне нужно, - подсчитать, сколько людей оценило 5 звезд по каждому рейтингу, затем 4, затем 3, а затем до 1.
Это моя пользовательская схема
var UserSchema = new mongoose.Schema({
username: String,
fullname: String,
email: {
type: String,
lowercase: true,
unique: true
},
password: String,
feedback: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Feedback'
}]
});
Схема обратной связи
var FeedbackSchema = new mongoose.Schema({
postname: String,
userWhoSentFeedback: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
message: String,
feedbacktype: String,
thumbsup: Boolean,
rating: {
delivery: Number,
timeliness: Number,
communication: Number
}
});
Пока я использую $ match, $ unwind, $ lookup и $ count, пытаясь получить значение, но не удалось. Здесь код im работает...
router.get('/testagg', (req, res)=>{
User.aggregate([
{"$match":
{ "username": "user1"}
},
{
"$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "outputResult"
}
}, {"$unwind": "$outputResult"},
{"$match": {"outputResult.rating.communication": {$eq: 1}}},{"$count": 'Communication_1'},
{
"$project": {
outputResult: 1,
Communication_1: 1
}
}
], (err, user)=>{
console.log(user)
res.json(user);
})
})
с этим кодом, это результат, который я получил
[
{
"Communication_1": 1
}
]
Поэтому я попытался получить все рейтинговые номера для связи, выполнив несколько совпадений $ (что не работает)
router.get('/testagg', (req, res)=>{
User.aggregate([
{"$match":
{ "username": "user1"}
},
{
"$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "outputResult"
}
}, {"$unwind": "$outputResult"},
{"$match": {"outputResult.rating.communication": {$eq: 1}}},{"$count": 'Communication_1'},
{"$match": {"outputResult.rating.communication": {$eq: 2}}},{"$count": 'Communication_2'},
{"$match": {"outputResult.rating.communication": {$eq: 3}}},{"$count": 'Communication_3'},
{"$match": {"outputResult.rating.communication": {$eq: 4}}},{"$count": 'Communication_4'},
{"$match": {"outputResult.rating.communication": {$eq: 5}}},{"$count": 'Communication_5'},
{
"$project": {
outputResult: 1,
Communication_1: 1,
Communication_2: 1,
Communication_3: 1,
Communication_4: 1,
Communication_5: 1
}
}
], (err, user)=>{
console.log(user)
res.json(user);
})
})
Но я получил пустой ответ. Поэтому я думаю, что делаю это неправильно.
Любая помощь будет оценена! Спасибо!
**Обновить
Я также пробовал этот код. Просто чтобы получить значение связи только 1 и 4.
router.get('/testagg', (req, res)=>{
User.aggregate([
{"$match":
{ "username": "user1"}
},
{
"$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "outputResult"
}
}, {"$unwind": "$outputResult"}, {
"$project": {
"outputResult.rating": 1,
comm1: { $cond: [{$eq: ['$outputResult.rating.communication', 1]}, 1, 0]},
comm4: { $cond: [{$eq: ['$outputResult.rating.communication', 4]}, 1, 0]}
}
},
{ $group: {
_id: '$outputResult.rating',
total: { $sum: 1 },
comm1: { $sum: '$comm1'},
comm4: { $sum: '$comm4'}
}
}
], (err, user)=>{
console.log(user)
res.json(user);
})
})
И это результат, который я получаю
[
{
"_id": {
"communication": 1,
"timeliness": 1,
"delivery": 1
},
"total": 1,
"comm1": 1,
"comm4": 0
},
{
"_id": {
"communication": 5,
"timeliness": 5,
"delivery": 5
},
"total": 1,
"comm1": 0,
"comm4": 0
},
{
"_id": {
"communication": 4,
"timeliness": 4,
"delivery": 5
},
"total": 1,
"comm1": 0,
"comm4": 1
}
]
Ну, он считает это, но это не тот, который я хочу, я хочу, чтобы иметь общее количество каждого рейтинга
Это результат, который я хочу
{
"comm1" : 1,
"comm2" : 0,
"comm3" : 0,
"comm4" : 1,
"comm5" : 1
}
Вероятно, более большой вопрос заключается в том, что вы фактически объединяете документы? Или вы на самом деле просто хотите этого для "единого пользователя"? Потому что это действительно отличает то, как вы "должны" подходить к этому. Но позвольте обратиться к этому в контексте того, что вы можете сделать, чтобы получить ваши результаты.
Я честно думаю, что вы смотрите на это неправильно. Если вы собираетесь получать несколько рейтингов для нескольких ключей ("связь", "доставка", "своевременность"), тогда попытка "именованного ключа" для каждого громоздка и громоздка. На самом деле ИМХО, выход вроде бы совершенно "грязный".
Как я вижу, вам лучше использовать естественные структуры для "списков", которые являются "массивами", которые вы можете легко итерации и доступа в более позднем коде.
С этой целью я предлагаю вместо этого искать результаты, содержащие каждый из этих "ключей", а затем содержащий его собственный массив для "баллов" и их "отличных значений", которые вы на самом деле после. Это гораздо более чистый и понятный для машинного подхода подход, поскольку альтернативный вариант будет означать "повторение ключей" в более позднем коде, который тогда действительно "грязный".
Поэтому, чтобы передать это в структуру агрегации, учитывая, что вы фактически фактически "агрегируете" по документам, тогда вы должны сделать что-то вроде этого:
User.aggregate(
[
{ "$match": { "username": "user1" }
{ "$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "feedback"
}},
{ "$unwind": "$feedback" },
{ "$project": {
"username": 1,
"feedback": [
{ "k": "delivery", "v": "$feedback.rating.delivery" },
{ "k": "timeliness", "v": "$feedback.rating.timeliness" },
{ "k": "communication", "v": "$feedback.rating.communication" }
]
}},
{ "$unwind": "$feedback" },
{ "$group": {
"_id": {
"username": "$username",
"type": "$feedback.k",
"score": "$feedback.v",
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": {
"username": "$_id.username",
"type": "$_id.type"
},
"scores": {
"$push": {
"score": "$_id.score",
"count": "$count"
}
}
}},
{ "$group": {
"_id": "$_id.username",
"feedback": {
"$push": {
"type": "$_id.type",
"scores": "$scores"
}
}
}}
],
function(err,users) {
}
)
Основной процесс здесь заключается в том, что после $lookup
для "join" вы $unwind
результирующий массив, потому что хотите в любом случае агрегировать данные из этого "по всем документам". Следующее, что мы хотим сделать, это на самом деле сделать ваши вышеупомянутые "ключи" членами массива. Это связано с тем, что для дальнейшего процесса мы также будем $unwind
этот контент, чтобы создать эффективный новый документ для каждого члена "обратной связи", а также "за ключ".
Следующие этапы выполняются с помощью $group
, которые находятся в последовательности:
Группируйте и подсчитывайте "отличные" ключи обратной связи для каждого балла в соответствии с тем, как они были сформированы для предоставленного "имени пользователя".
Группируйте по "ключам", чтобы разделить документы на "имя пользователя", добавив "счеты" и их различные значения в массив.
Группируйте только "имя пользователя", чтобы также создать запись массива на "ключ", содержащий массив "баллов".
Альтернативный подход к этому заключается в том, что вы только запрашиваете "одно имя пользователя", и в любом случае эти данные получаются в "одном документе". Таким образом, единственное, что вы действительно хотите сделать "на сервере", - это $lookup
для выполнения "join". После этого гораздо проще обрабатывать массив "обратной связи" с вашим кодом клиента, создавая точно такие же отличные результаты.
Просто используя $lookup
для соединения, а затем обработайте результаты:
User.aggregate(
[
{ "$match": { "username": "user1" }
{ "$lookup": {
"from": "feedbacks",
"localField": "feedback",
"foreignField": "_id",
"as": "feedback"
}},
],
function(err,users) {
users = users.map(doc => {
doc.feedback = [].concat.apply([],doc.feedback.map(
r => Object.keys(r.rating).map(k => ({ k: k, v: r.rating[k] }) )
)).reduce((a,b) => {
if ( a.findIndex(e => JSON.stringify({ k: e.k , v: e.v }) == JSON.stringify(b) ) != -1 ) {
a[a.findIndex(e => JSON.stringify({ k: e.k , v: e.v }) == JSON.stringify(b) )].count += 1;
} else {
a = a.concat([{ k: b.k, v: b.v, count: 1 }]);
}
return a;
},[]).reduce((a,b) => {
if ( a.findIndex(e => e.type == b.k) != -1 ) {
a[a.findIndex(e => e.type == b.k)].scores.push({ score: b.v, count: b.count })
} else {
a = a.concat([{ type: b.k, scores: [{ score: b.v, count: b.count }] }]);
}
return a;
},[]);
return doc;
});
res.json(users)
}
)
Фактически, если это "единственный" пользователь, то вы, вероятно, res.json(users[0])
в конце, так как результаты .aggregate()
всегда являются массивом независимо от того, сколько результатов было возвращено.
Это действительно просто использование функций .map()
и .reduce()
JavaScript над массивом "feedback"
, чтобы изменить и вернуть "различные значения". Существуют такие же методы, но если это действительно один ответ на документ или даже небольшой ответ, без фактической необходимости "сворачивать документы", то это более чистый и вероятный "более быстрый" способ обработки.
Теоретически мы могли бы написать "очень сложную" версию конвейера агрегации, которая выполняла точно такие же шаги для отдельных документов, как показано здесь в коде. Тем не менее, он "очень сложный" и опирается на современные методы, поскольку на самом деле нет значительного "сокращения" - это данные, переданные с сервера.
Поэтому, если вам нужно это "через документы", используйте полный агрегатный конвейер в первом листинге. Но если вам нужен только "одиночный пользователь" за раз или "отличные результаты" для каждого пользователя при небольшом выборе, тогда код обработки клиента - это путь.
Оба выпускают один и тот же результат, что, как я упоминаю, гораздо более дружелюбно, чем направление, в котором вы направлялись, и, безусловно, проще обрабатывать в дальнейшем код по мере необходимости:
{
"_id" : "user 1",
"feedback" : [
{
"type" : "communication",
"scores" : [
{
"score" : 2,
"count" : 2
},
{
"score" : 4,
"count" : 1
},
{
"score" : 5,
"count" : 1
},
{
"score" : 3,
"count" : 1
}
]
},
{
"type" : "delivery",
"scores" : [
{
"score" : 3,
"count" : 1
},
{
"score" : 4,
"count" : 1
},
{
"score" : 2,
"count" : 1
},
{
"score" : 5,
"count" : 2
}
]
},
{
"type" : "timeliness",
"scores" : [
{
"score" : 1,
"count" : 1
},
{
"score" : 5,
"count" : 1
},
{
"score" : 4,
"count" : 1
},
{
"score" : 3,
"count" : 1
},
{
"score" : 2,
"count" : 1
}
]
}
]
}
_id
. Вы бы громко вздохнули, если бы знали о дополнительных внутренних инструкциях, необходимых для простого поиска отдельного документа по чему-либо, кроме первичного ключа.