Используйте $ lookup с условным соединением - PullRequest
1 голос
/ 19 апреля 2019

при условии, что у меня есть следующие документы

Пользователь

{
    uuid: string,
    isActive: boolean,
    lastLogin: datetime,
    createdOn: datetime
}

Проекты

{
    id: string,
    users: [
        {
            uuid: string,
            otherInfo: ...
        },
        {... more users}
    ]
}

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

Теперь, 2 недели работают нормально, но я не могу понять, как сделать часть "5 недель и у меня нет проектов"

Я придумал что-то вроде ниже, но последняя часть не работает, потому что $exists, очевидно, не является оператором верхнего уровня.

Кто-нибудь когда-нибудь делал что-нибудь подобное? Спасибо!

return await this.collection
    .aggregate([
        {
            $match: {
                $and: [
                    {
                        $expr: {
                            $allElementsTrue: {
                                $map: {
                                    input: [`$lastLogin`, `$createdOn`],
                                    in: { $lt: [`$$this`, twoWeeksAgo] }
                                }
                            }
                        }
                    },
                    {
                        $or: [
                            {
                                isActive: false
                            },
                            {
                                $and: [
                                    {
                                        $expr: {
                                            $allElementsTrue: {
                                                $map: {
                                                    input: [`$lastLogin`, `$createdOn`],
                                                    in: { $lt: [`$$this`, fiveWeeksAgo] }
                                                }
                                            }
                                        }
                                    },
                                    {
                                        //No projects exists on this user
                                        $exists: {
                                            $lookup: {
                                                from: _.get(Config, `env.collection.projects`),
                                                let: {
                                                    currentUser: `$$ROOT`
                                                },
                                                pipeline: [
                                                    {
                                                        $project: {
                                                            _id: 0,
                                                            users: {
                                                                $filter: {
                                                                    input: `$users`,
                                                                    as: `user`,
                                                                    cond: {
                                                                        $eq: [`$$user.uuid`, `$currentUser.uuid`]
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                ]
                                            }
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        }
    ])
    .toArray();

1 Ответ

1 голос
/ 19 апреля 2019

Не знаю, почему вы думали, что $expr требовалось в начальном $match, но на самом деле:

const getResults = () => {

  const now = Date.now();
  const twoWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 2 ));
  const fiveWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 5 ));

  // as long a mongoDriverCollectionReference points to a "Collection" object
  // for the "users" collection

  return mongoDriverCollectionReference.aggregate([   
    // No $expr, since you can actually use an index. $expr cannot do that
    { "$match": {
      "$or": [
        // Active and "logged in"/created in the last 2 weeks
        { 
          "isActive": true,
          "$or": [
            { "lastLogin": { "$gte": twoWeeksAgo } },
            { "createdOn": { "$gte": twoWeeksAgo } }
          ]
        },
        // Also want those who...
        // Not Active and "logged in"/created in the last 5 weeks
        // we'll "tag" them later
        { 
          "isActive": false,
          "$or": [
            { "lastLogin": { "$gte": fiveWeeksAgo } },
            { "createdOn": { "$gte": fiveWeeksAgo } }
          ]
        }
      ]
    }},

    // Now we do the "conditional" stuff, just to return a matching result or not

    { "$lookup": {
      "from":  _.get(Config, `env.collection.projects`), // there are a lot cleaner ways to register models than this
      "let": {
        "uuid": {
          "$cond": {
            "if": "$isActive",   // this is boolean afterall
            "then": null,       // don't really want to match
            "else": "$uuid"     // Okay to match the 5 week results
          }
        }
      },
      "pipeline": [
        // Nothing complex here as null will return nothing. Just do $in for the array
        { "$match": {  "$in": [ "$$uuid", "$users.uuid" ] } },

        // Don't really need the detail, so just reduce any matches to one result of [null]
        { "$group": { "_id": null } }
      ],
      "as": "projects"
    }},

    // Now test if the $lookup returned something where it mattered
    { "$match": {
      "$or": [
        { "active": true },                   // remember we selected the active ones already
        {
          "projects.0": { "$exists": false }  // So now we only need to know the "inactive" returned no array result.
        }
      ]
    }}
  ]).toArray();   // returns a Promise
};

Это довольно просто, как вычисляемые выраженияvia $expr на самом деле очень плохо, а не то, что вы хотите на первом этапе конвейера.Также «не то, что вам нужно» , поскольку createdOn и lastLogin действительно не должны были быть объединены в массив для $allElementsTrue, который просто был бы AND условие, в котором вы описали логику, действительно означало бы ИЛИ .Так что $or здесь просто отлично.

Так же, как и $or при разделении условий для isActive из true/false.Опять же, это или"две недели" ИЛИ"пять недель".И это, безусловно, не требует $expr, поскольку стандартное сопоставление диапазонов неравенства работает нормально и использует «индекс».

Тогда вы действительно просто хотите сделать «условным» вещей в let для $lookup вместо вашего "существует ли оно" мышления.Все, что вам действительно нужно знать (поскольку выбор диапазона дат фактически уже сделан), это то, является ли active сейчас true или false.Где это active (имеется в виду, что по вашей логике вас не волнуют проекты), просто установите $$uuid, использованный в $match стадии конвейера, в значение null, чтобы оно не совпадало, и$lookup возвращает пустой массив .Где false (также уже соответствует условиям даты из более ранних), тогда вы используете фактическое значение и «объединяетесь» (где, конечно, есть проекты).

Тогда просто сохранить * 1068 просто* пользователи, а затем только тестируют оставшиеся false значения для active, чтобы увидеть, действительно ли массив "projects" из $lookup действительно что-то возвращает.Если этого не произошло, то у них просто нет проектов.

Вероятно, следует отметить, что, поскольку users является "массивом" в коллекции projects, вы используете $in для условия $match для одного значения массива.

Обратите внимание, что для краткости мы можем использовать $groupвнутри внутреннего конвейера, чтобы вернуть только один результат, а не много совпадений с фактическими проектами.Вас не волнует содержание или "количество" , но просто если один был возвращен или ничто .Снова следуя представленной логике.

Это дает вам желаемые результаты, и это делает это эффективным способом и фактически использует индексы там, где они доступны.

Также return await определенно не делаетто, что вы думаете, это делает, и на самом деле это предупреждающее сообщение ESLint (я предлагаю вам включить ESLint в вашем проекте), так как это не разумно.На самом деле он ничего не делает, так как в любом случае вам нужно было бы await getResults() (в соответствии с именованием примера), поскольку ключевое слово await - это не "магия" , а просто способ красивее написания then().Надеюсь, что будет легче понять, как только вы поймете, что такое async/await для синтаксически.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...