Limit() и sort() порядок pymongo и mongodb

Несмотря на чтение ответов людей, заявляющих, что сортировка выполняется в первую очередь, доказательства показывают что-то другое, что предел делается до сортировки. Есть ли способ принудительно сортировать сначала?

views = mongo.db.view_logging.find().sort([('count', 1)]).limit(10)

Использую ли я .sort().limit() или .limit().sort(), предел имеет приоритет. Интересно, связано ли это с pymongo...

Ответы

Ответ 1

В соответствии с документацией , независимо от того, что идет первым в вашей цепочке команд, sort() всегда будет применяться до limit().

Вы также можете изучить .explain() результаты вашего запроса и посмотреть стадии выполнения - вы обнаружите, что этап ввода сортировки проверяет все отфильтрованные (в вашем случае все документы в коллекции), а затем применяется предел.


Перейдем к примеру.

Представьте, что существует база данных foo с коллекцией test, имеющей 6 документов:

>>> col = db.foo.test
>>> for doc in col.find():
...     print(doc)
{'time': '2016-03-28 12:12:00', '_id': ObjectId('56f9716ce4b05e6b92be87f2'), 'value': 90}
{'time': '2016-03-28 12:13:00', '_id': ObjectId('56f971a3e4b05e6b92be87fc'), 'value': 82}
{'time': '2016-03-28 12:14:00', '_id': ObjectId('56f971afe4b05e6b92be87fd'), 'value': 75}
{'time': '2016-03-28 12:15:00', '_id': ObjectId('56f971b7e4b05e6b92be87ff'), 'value': 72}
{'time': '2016-03-28 12:16:00', '_id': ObjectId('56f971c0e4b05e6b92be8803'), 'value': 81}
{'time': '2016-03-28 12:17:00', '_id': ObjectId('56f971c8e4b05e6b92be8806'), 'value': 90}

Теперь запустите запросы с разным порядком sort() и limit() и проверьте результаты и план объяснения.

Сортировка, а затем ограничение:

>>> from pprint import pprint
>>> cursor = col.find().sort([('time', 1)]).limit(3)  
>>> sort_limit_plan = cursor.explain()
>>> pprint(sort_limit_plan)
{u'executionStats': {u'allPlansExecution': [],
                     u'executionStages': {u'advanced': 3,
                                          u'executionTimeMillisEstimate': 0,
                                          u'inputStage': {u'advanced': 6,
                                                          u'direction': u'forward',
                                                          u'docsExamined': 6,
                                                          u'executionTimeMillisEstimate': 0,
                                                          u'filter': {u'$and': []},
                                                          u'invalidates': 0,
                                                          u'isEOF': 1,
                                                          u'nReturned': 6,
                                                          u'needFetch': 0,
                                                          u'needTime': 1,
                                                          u'restoreState': 0,
                                                          u'saveState': 0,
                                                          u'stage': u'COLLSCAN',
                                                          u'works': 8},
                                          u'invalidates': 0,
                                          u'isEOF': 1,
                                          u'limitAmount': 3,
                                          u'memLimit': 33554432,
                                          u'memUsage': 213,
                                          u'nReturned': 3,
                                          u'needFetch': 0,
                                          u'needTime': 8,
                                          u'restoreState': 0,
                                          u'saveState': 0,
                                          u'sortPattern': {u'time': 1},
                                          u'stage': u'SORT',
                                          u'works': 13},
                     u'executionSuccess': True,
                     u'executionTimeMillis': 0,
                     u'nReturned': 3,
                     u'totalDocsExamined': 6,
                     u'totalKeysExamined': 0},
 u'queryPlanner': {u'indexFilterSet': False,
                   u'namespace': u'foo.test',
                   u'parsedQuery': {u'$and': []},
                   u'plannerVersion': 1,
                   u'rejectedPlans': [],
                   u'winningPlan': {u'inputStage': {u'direction': u'forward',
                                                    u'filter': {u'$and': []},
                                                    u'stage': u'COLLSCAN'},
                                    u'limitAmount': 3,
                                    u'sortPattern': {u'time': 1},
                                    u'stage': u'SORT'}},
 u'serverInfo': {u'gitVersion': u'6ce7cbe8c6b899552dadd907604559806aa2e9bd',
                 u'host': u'h008742.mongolab.com',
                 u'port': 53439,
                 u'version': u'3.0.7'}}

Предел, а затем сортировка:

>>> cursor = col.find().limit(3).sort([('time', 1)])
>>> limit_sort_plan = cursor.explain()
>>> pprint(limit_sort_plan)
{u'executionStats': {u'allPlansExecution': [],
                     u'executionStages': {u'advanced': 3,
                                          u'executionTimeMillisEstimate': 0,
                                          u'inputStage': {u'advanced': 6,
                                                          u'direction': u'forward',
                                                          u'docsExamined': 6,
                                                          u'executionTimeMillisEstimate': 0,
                                                          u'filter': {u'$and': []},
                                                          u'invalidates': 0,
                                                          u'isEOF': 1,
                                                          u'nReturned': 6,
                                                          u'needFetch': 0,
                                                          u'needTime': 1,
                                                          u'restoreState': 0,
                                                          u'saveState': 0,
                                                          u'stage': u'COLLSCAN',
                                                          u'works': 8},
                                          u'invalidates': 0,
                                          u'isEOF': 1,
                                          u'limitAmount': 3,
                                          u'memLimit': 33554432,
                                          u'memUsage': 213,
                                          u'nReturned': 3,
                                          u'needFetch': 0,
                                          u'needTime': 8,
                                          u'restoreState': 0,
                                          u'saveState': 0,
                                          u'sortPattern': {u'time': 1},
                                          u'stage': u'SORT',
                                          u'works': 13},
                     u'executionSuccess': True,
                     u'executionTimeMillis': 0,
                     u'nReturned': 3,
                     u'totalDocsExamined': 6,
                     u'totalKeysExamined': 0},
 u'queryPlanner': {u'indexFilterSet': False,
                   u'namespace': u'foo.test',
                   u'parsedQuery': {u'$and': []},
                   u'plannerVersion': 1,
                   u'rejectedPlans': [],
                   u'winningPlan': {u'inputStage': {u'direction': u'forward',
                                                    u'filter': {u'$and': []},
                                                    u'stage': u'COLLSCAN'},
                                    u'limitAmount': 3,
                                    u'sortPattern': {u'time': 1},
                                    u'stage': u'SORT'}},
 u'serverInfo': {u'gitVersion': u'6ce7cbe8c6b899552dadd907604559806aa2e9bd',
                 u'host': u'h008742.mongolab.com',
                 u'port': 53439,
                 u'version': u'3.0.7'}}

Как вы можете видеть, в обоих случаях сортировка применяется сначала и затрагивает все 6 документов, а затем предел ограничивает результаты до 3.

И, планы выполнения выполняются точно так же:

>>> from copy import deepcopy  # just in case
>>> cursor = col.find().sort([('time', 1)]).limit(3)
>>> sort_limit_plan = deepcopy(cursor.explain())
>>> cursor = col.find().limit(3).sort([('time', 1)])
>>> limit_sort_plan = deepcopy(cursor.explain())
>>> sort_limit_plan == limit_sort_plan
True

Также смотрите:

Ответ 2

Логически это должно быть все, что приходит первым в конвейере, но MongoDB всегда сортирует сначала до предела.

В моей тестовой операции Сортировка имеет приоритет, независимо от того, идет ли она перед пропуском или после. Однако мне кажется, что это очень странное поведение.

Мой образец данных:

[
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef81"), 
        "number" : 48.98052410874508
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef82"), 
        "number" : 50.98747461471063
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef83"), 
        "number" : 81.32911244349772
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef84"), 
        "number" : 87.95549919039071
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef85"), 
        "number" : 81.63582683594402
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef86"), 
        "number" : 43.25696270026136
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef87"), 
        "number" : 88.22046335409453
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef88"), 
        "number" : 64.00556739160076
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef89"), 
        "number" : 16.09353150244296
    }, 
    {
        "_id" : ObjectId("56f845fea524b4d098e0ef8a"), 
        "number" : 17.46667776660574
    }
]

Код проверки Python:

import pymongo

client = pymongo.MongoClient("mongodb://localhost:27017")
database = client.get_database("test")
collection = database.get_collection("collection")

print("----------------[limit -> sort]--------------------------")
result = collection.find().limit(5).sort([("number", pymongo.ASCENDING)])
for r in result:
    print(r)

print("----------------[sort -> limit]--------------------------")
result = collection.find().sort([("number", pymongo.ASCENDING)]).limit(5)
for r in result:
    print(r)

Результат:

----------------[limit -> sort]--------------------------
{u'_id': ObjectId('56f845fea524b4d098e0ef89'), u'number': 16.09353150244296}
{u'_id': ObjectId('56f845fea524b4d098e0ef8a'), u'number': 17.46667776660574}
{u'_id': ObjectId('56f845fea524b4d098e0ef86'), u'number': 43.25696270026136}
{u'_id': ObjectId('56f845fea524b4d098e0ef81'), u'number': 48.98052410874508}
{u'_id': ObjectId('56f845fea524b4d098e0ef82'), u'number': 50.98747461471063}
----------------[sort -> limit]--------------------------
{u'_id': ObjectId('56f845fea524b4d098e0ef89'), u'number': 16.09353150244296}
{u'_id': ObjectId('56f845fea524b4d098e0ef8a'), u'number': 17.46667776660574}
{u'_id': ObjectId('56f845fea524b4d098e0ef86'), u'number': 43.25696270026136}
{u'_id': ObjectId('56f845fea524b4d098e0ef81'), u'number': 48.98052410874508}
{u'_id': ObjectId('56f845fea524b4d098e0ef82'), u'number': 50.98747461471063}

Ответ 4

В документации mongodb указано, что метод skip() управляет начальной точкой набора результатов, а затем sort() и заканчивается методом limit().

Это независимо от порядка вашего кода. Причина в том, что mongo получает все методы для запроса, затем он заказывает методы ограничения пропускной способности в этом точном порядке, а затем запускает запрос.

Ответ 5

Принятый ответ не работает для меня, но это делает:

last5 = db.collection.find(   {'key': "YOURKEY"},   sort=[( '_id', pymongo.DESCENDING )] ).limit(5)

с пределом снаружи и сортировкой внутри аргумента find.