Подсчет различных значений на ключ из массива

1

У меня есть коллекция отзывов об обратной связи (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
}
Теги:
mongoose
aggregation-framework

1 ответ

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, которые находятся в последовательности:

  1. Группируйте и подсчитывайте "отличные" ключи обратной связи для каждого балла в соответствии с тем, как они были сформированы для предоставленного "имени пользователя".

  2. Группируйте по "ключам", чтобы разделить документы на "имя пользователя", добавив "счеты" и их различные значения в массив.

  3. Группируйте только "имя пользователя", чтобы также создать запись массива на "ключ", содержащий массив "баллов".


Обработайте курсор, когда вы на самом деле не "агрегируете",

Альтернативный подход к этому заключается в том, что вы только запрашиваете "одно имя пользователя", и в любом случае эти данные получаются в "одном документе". Таким образом, единственное, что вы действительно хотите сделать "на сервере", - это $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
                                }
                        ]
                }
        ]
}
  • 0
    Потрясающие! Я попробую это, когда вернусь домой и оставлю вам отзыв. Да, я только пытаюсь получить его для одного пользователя
  • 0
    @ Джон тогда попробуй дома. Если вы просто сопоставляете отдельные документы, тогда вам «следует» использовать _id . Вы бы громко вздохнули, если бы знали о дополнительных внутренних инструкциях, необходимых для простого поиска отдельного документа по чему-либо, кроме первичного ключа.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню