Сортировка MongoDB по весу $ или агрегированный запрос

#node.js #mongodb #mongodb-query

#node.js #mongodb #mongodb-запрос

Вопрос:

Я выполняю запрос с $ или агрегацией по двум полям в базе данных. Первое условие, которое я хочу быть более релевантным, чем второе, поэтому я хочу сначала отсортировать те, которые соответствуют первому условию, а затем вернуть последнее после.

Вот некоторый код:

   const regexp = new RegExp('^'   req.query.search, 'i')

  const query = {
    $or: [{ word: regexp }, { 'lang.english': regexp }],
  }

  const words = await collection
    .find(query, {
      collation: {
        locale: 'sv',
        strength: 1,
      },
      projection: {
        '-_id': 1,
        'word': 1,
        'lang.english': 1,
        'lang.definitionEnglish': 1,
        'lang.definition': 1,
      },
    })
    .skip(skips)
    .limit(page_size)
    .toArray()
  

Итак, в основном, те результаты, которые совпадают с word регулярным выражением, должны быть первыми, затем lang.english

Комментарии:

1. Вы также можете попробовать использовать $facet для группировки, а затем объединить результирующие значения из каждого аспекта в требуемом порядке.

Ответ №1:

Для начала давайте добавим некоторые данные в тестовую коллекцию MongoDB, чтобы поиграть с ними.

 db.test.insertMany([
    { word: 'TEST123', lang: { english: 'wibble1' } },
    { word: 'wibble2', lang: { english: 'Test123' } },
    { word: 'test345', lang: { english: 'wibble3' } },
    { word: 'wibble4', lang: { english: 'testy101' } }
]);

{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("5f6924b271e546940acc10f7"),
                ObjectId("5f6924b271e546940acc10f8"),
                ObjectId("5f6924b271e546940acc10f9"),
                ObjectId("5f6924b271e546940acc10fa")
        ]
}
  

Это имеет ту же структуру, что и указано в вопросе.

Затем мы можем создать агрегированный запрос, который будет соответствовать тому же, что и ваша находка, но на этот раз добавим дополнительное поле, по которому мы можем сортировать. Мы сделаем это 1, если поле word совпадает, затем 2 для чего-либо еще.

 db.test.aggregate([
   { $match:  { $or: [{ word: regexp }, { 'lang.english': regexp }] } },
   { $addFields: { sortingValue: { $cond: { if: { $regexMatch: { input: "$word" , regex: regexp } }, then: 1, else: 2 } } } }
]);

{
        "_id" : ObjectId("5f6924b271e546940acc10f7"),
        "word" : "TEST123",
        "lang" : {
                "english" : "wibble1"
        },
        "sortingValue" : 1
}
{
        "_id" : ObjectId("5f6924b271e546940acc10f8"),
        "word" : "wibble2",
        "lang" : {
                "english" : "Test123"
        },
        "sortingValue" : 2
}
{
        "_id" : ObjectId("5f6924b271e546940acc10f9"),
        "word" : "test345",
        "lang" : {
                "english" : "wibble3"
        },
        "sortingValue" : 1
}
{
        "_id" : ObjectId("5f6924b271e546940acc10fa"),
        "word" : "wibble4",
        "lang" : {
                "english" : "testy101"
        },
        "sortingValue" : 2
}
  

Теперь мы можем добавить обычную сортировку, пропуск и ограничение.

 var regexp = new RegExp('^'   'test', 'i')
var skip = 0;
var limit = 3;

db.test.aggregate([
   { $match:  { $or: [{ word: regexp }, { 'lang.english': regexp }] } },
   { $addFields: { sortingValue: { $cond: { if: { $regexMatch: { input: "$word" , regex: regexp } }, then: 1, else: 2 } } } },
   { $sort: { sortingValue: 1 } },
   { $skip: skip },
   { $limit: limit }
]);

{
        "_id" : ObjectId("5f6924b271e546940acc10f9"),
        "word" : "test345",
        "lang" : {
                "english" : "wibble3"
        },
        "sortingValue" : 1
}
{
        "_id" : ObjectId("5f6924b271e546940acc10f7"),
        "word" : "TEST123",
        "lang" : {
                "english" : "wibble1"
        },
        "sortingValue" : 1
}
{
        "_id" : ObjectId("5f6924b271e546940acc10f8"),
        "word" : "wibble2",
        "lang" : {
                "english" : "Test123"
        },
        "sortingValue" : 2
}