Запит обмеження / зміщення та відліку мангуста


84

Трохи непарний для продуктивності запиту ... Мені потрібно виконати запит, який робить загальну кількість документів, а також може повернути набір результатів, який може бути обмежений та зміщений.

Отже, у мене є 57 документів, і користувач хоче, щоб 10 документів компенсували на 20.

Я можу подумати про 2 способи зробити це, спочатку це запит для всіх 57 документів (повертається як масив), а потім за допомогою array.slice повертає документи, які вони хочуть. Другий варіант - запустити 2 запити, перший із яких використовує рідний метод "count" монго, потім виконати другий запит, використовуючи вбудований $ limit монго та агрегатори $ skip.

Як ви думаєте, що б масштабувалося краще? Робити все це в одному запиті, або запускати два окремі?

Редагувати:

// 1 query
var limit = 10;
var offset = 20;

Animals.find({}, function (err, animals) {
    if (err) {
        return next(err);
    }

    res.send({count: animals.length, animals: animals.slice(offset, limit + offset)});
});


// 2 queries
Animals.find({}, {limit:10, skip:20} function (err, animals) {            
    if (err) {
        return next(err);
    }

    Animals.count({}, function (err, count) {
        if (err) {
            return next(err);
        }

        res.send({count: count, animals: animals});
    });
});

Я не впевнений щодо Mongoose, однак count()функція за замовчуванням у PHP не враховує limitта не skipвраховує, якщо не сказано, що просто запустити один запит на обмеження та пропустити, а потім отримання підрахунку повинно дати тут найефективніше рішення. Однак як ви дізнаєтесь, що є 57 документів, якщо ви не зробите двох запитів для підрахунку того, що зараз там є? У вас є статичне число, яке ніколи не змінюється? Якщо ні, тоді вам потрібно буде зробити як пропуск, так і ліміт, тоді підрахунок.
Sammaye

Вибачте, я говорив про використання рідного методу підрахунку монгоdb.collection.find(<query>).count();
leepowell

Вибачте, це був я, я неправильно прочитав ваше запитання. Хмммм, насправді я не впевнений, що було б краще, чи завжди ваш результат буде дуже низьким, як 57 документів? Якщо так, то зріз на стороні клієнта може бути на мілісекунди більш продуктивним.
Sammaye

Я додав приклад до оригінального запитання, я не думаю, що дані колись сягнуть 10 000+, але потенційно це могло б.
Leepowell

На 10 тис. Записів можна помітити, що обробка пам'яті JS є менш ефективною, ніж count()функція MongoDB. count()Функція в MongoDB є відносно повільної , але він по - , як і раніше в значній мірі так само швидко , як і більшості клієнтських варіацій на великих наборах , і це може бути швидше , ніж на стороні клієнта підрахунок тут можливо. Але ця частина суб’єктивна для вашого власного тестування. Майте на увазі, що я раніше легко підрахував масиви довжиною 10 тис., Тому це може бути швидше на стороні клієнта, дуже важко сказати, що елементи 10 тис.
Sammaye

Відповіді:


129

Я пропоную вам використовувати 2 запити:

  1. db.collection.count()поверне загальну кількість елементів. Це значення зберігається десь у Монго, і воно не обчислюється.

  2. db.collection.find().skip(20).limit(10)тут я припускаю, що ви можете використати сортування за якимсь полем, тому не забудьте додати індекс до цього поля. Цей запит також буде швидким.

Я вважаю, що вам не слід запитувати всі елементи, а потім виконувати пропуск і зняття, оскільки пізніше, коли у вас є великі дані, у вас будуть проблеми з передачею та обробкою даних.


1
Те, що я пишу, - це лише коментар без будь-яких припущень, але я чув, що .skip()інструкція важка для ЦП, оскільки вона переходить до початку збору і доходить до значення, зазначеного в параметрі .skip(). Це може мати реальний вплив на велику колекцію! Але я не знаю, який з них у .skip()будь-якому випадку найважчий між використанням, або отримати всю колекцію та обробку за допомогою JS ... Що ви думаєте?
Захарі Дахан,

2
@Stuffix Я чув ті самі побоювання щодо використання .skip(). Ця відповідь торкається її і радить використовувати фільтр у полі дати. Можна використовувати це за допомогою методів .skip()& .take(). Це здається гарною ідеєю. Однак у мене виникають проблеми із запитанням цього OP про те, як отримати підрахунок загальної кількості документів. Якщо фільтр використовується для боротьби з наслідками продуктивності .skip(), як ми можемо отримати точний підрахунок? Кількість, що зберігається в базі даних, не відображатиме наш відфільтрований набір даних.
Michael Leanos

Привіт @MichaelLeanos, я стикаюся з тим самим питанням: тобто як отримати підрахунок загальної кількості документів. Якщо використовується фільтр, то як ми можемо отримати точний підрахунок? Ви отримали рішення для цього?
virsha

@virsha, використовуйте cursor.count()для повернення кількості відфільтрованих документальних мереж (він не виконає запит, а поверне вам кількість відповідних документів). Переконайтеся, що властивості фільтрації та замовлення проіндексовані, і все буде добре.
user854301

@virsha Використання cursor.count()має працювати, як зазначив @ user854301. Однак у підсумку я зробив додавання кінцевої точки до свого API ( /api/my-colllection/stats), яку я використовував для повернення різних статистичних даних у свої колекції за допомогою функції db.collection.stats від Mongoose . Оскільки мені це дійсно потрібно було лише для мого інтерфейсу, я просто поставив запит до кінцевої точки, щоб повернути цю інформацію незалежно від моєї сервісної сторінки.
Майкл Леанос

19

Замість того, щоб використовувати 2 окремі запити, ви можете використовувати aggregate()в одному запиті:

Сукупний "$ facet" можна отримати швидше, загальний підрахунок та дані з пропуском та обмеженням

    db.collection.aggregate([

      //{$sort: {...}}

      //{$match:{...}}

      {$facet:{

        "stage1" : [ {"$group": {_id:null, count:{$sum:1}}} ],

        "stage2" : [ { "$skip": 0}, {"$limit": 2} ]
  
      }},
     
     {$unwind: "$stage1"},
  
      //output projection
     {$project:{
        count: "$stage1.count",
        data: "$stage2"
     }}

 ]);

виведення наступним чином: -

[{
     count: 50,
     data: [
        {...},
        {...}
      ]
 }]

Крім того, загляньте на https://docs.mongodb.com/manual/reference/operator/aggregation/facet/


2

Після того, як мені довелося вирішити цю проблему самостійно, я хотів би спиратися на відповідь користувача854301.

Mongoose ^ 4.13.8 Я зміг використати функцію, що викликається, toConstructor()що дозволило мені уникати побудови запиту кілька разів, коли застосовуються фільтри. Я знаю, що ця функція доступна і в старих версіях, але вам доведеться перевірити документи Mongoose, щоб підтвердити це.

Далі використовуються обіцянки Bluebird:

let schema = Query.find({ name: 'bloggs', age: { $gt: 30 } });

// save the query as a 'template'
let query = schema.toConstructor();

return Promise.join(
    schema.count().exec(),
    query().limit(limit).skip(skip).exec(),

    function (total, data) {
        return { data: data, total: total }
    }
);

Тепер запит підрахунку поверне загальну кількість відповідних записів, а повернені дані будуть підмножиною загальної кількості записів.

Зверніть увагу на () навколо запиту (), який створює запит.



0
db.collection_name.aggregate([
    { '$match'    : { } },
    { '$sort'     : { '_id' : -1 } },
    { '$facet'    : {
        metadata: [ { $count: "total" } ],
        data: [ { $skip: 1 }, { $limit: 10 },{ '$project' : {"_id":0} } ] // add projection here wish you re-shape the docs
    } }
] )

Замість того, щоб використовувати два запити, щоб знайти загальну кількість і пропустити відповідний запис.
$ грань - найкращий та оптимізований спосіб.

  1. Зіставте рекорд
  2. Знайти total_count
  3. пропустити запис
  4. А також може змінити дані відповідно до наших потреб у запиті.

1
Будь ласка, додайте до своєї відповіді якесь пояснення, щоб інші могли навчитися з цього
Ніко Хаасе,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.