Использование $ in на нескольких полях в MongoDB - PullRequest
0 голосов
/ 03 мая 2018

Я застрял в проблеме, связанной с $in в MongoDB, но не получил желаемых результатов.

Вот мои данные коллекции MongoDB для лучшей визуализации

    [
        {
        "_id" : ObjectId("5aeaf7c73c3e9de82d91e439"),
        "companyID" : "4",
        "accounts" : [ 
            {
                "_id" : ObjectId("5aeaf7c720262a1db759edf5"),
                "userID" : "1",
                "preferences" : [ 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeaf7c720262a1db759edf7"),
                        "preferenceID" : "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T11:51:35.509Z"),
                        "updatedAt" : ISODate("2018-05-03T11:51:35.509Z")
                    }, 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeaf7c720262a1db759edf6"),
                        "preferenceID" : "6fb118-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T11:51:35.509Z"),
                        "updatedAt" : ISODate("2018-05-03T11:51:35.509Z")
                    }
                ]
            }
        ],
        "__v" : 0
    },

    {
        "_id" : ObjectId("5aeafe693c3e9de82d91e43a"),
        "companyID" : "5",
        "accounts" : [ 
            {
                "_id" : ObjectId("5aeafe698b1d5f2057419c99"),
                "userID" : "1",
                "preferences" : [ 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe698b1d5f2057419c9b"),
                        "preferenceID" : "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:19:53.436Z"),
                        "updatedAt" : ISODate("2018-05-03T12:19:53.436Z")
                    }, 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe698b1d5f2057419c9a"),
                        "preferenceID" : "6fb118-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:19:53.436Z"),
                        "updatedAt" : ISODate("2018-05-03T12:19:53.436Z")
                    }
                ]
            }
        ],
        "__v" : 0
    },
    {
        "_id" : ObjectId("5aeafe6d3c3e9de82d91e43b"),
        "companyID" : "6",
        "accounts" : [ 
            {
                "_id" : ObjectId("5aeafe6d8b1d5f2057419c9c"),
                "userID" : "1",
                "preferences" : [ 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe6d8b1d5f2057419c9e"),
                        "preferenceID" : "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:19:57.331Z"),
                        "updatedAt" : ISODate("2018-05-03T12:19:57.331Z")
                    }, 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe6d8b1d5f2057419c9d"),
                        "preferenceID" : "6fb118-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:19:57.331Z"),
                        "updatedAt" : ISODate("2018-05-03T12:19:57.331Z")
                    }
                ]
            }, 
            {
                "_id" : ObjectId("5aeafe738b1d5f2057419c9f"),
                "userID" : "2",
                "preferences" : [ 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe738b1d5f2057419ca1"),
                        "preferenceID" : "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:20:03.987Z"),
                        "updatedAt" : ISODate("2018-05-03T12:20:03.987Z")
                    }, 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe738b1d5f2057419ca0"),
                        "preferenceID" : "6fb118-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:20:03.987Z"),
                        "updatedAt" : ISODate("2018-05-03T12:20:03.987Z")
                    }
                ]
            }, 
            {
                "_id" : ObjectId("5aeafe778b1d5f2057419ca2"),
                "userID" : "3",
                "preferences" : [ 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe778b1d5f2057419ca4"),
                        "preferenceID" : "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:20:07.062Z"),
                        "updatedAt" : ISODate("2018-05-03T12:20:07.062Z")
                    }, 
                    {
                        "emailNotification" : true,
                        "smsNotification" : true,
                        "pushNotification" : false,
                        "webNotification" : false,
                        "lastUpdatedBy" : "SYSTEM",
                        "_id" : ObjectId("5aeafe778b1d5f2057419ca3"),
                        "preferenceID" : "6fb118-4c56-11e8-842f-0ed5f89f718b",
                        "createdAt" : ISODate("2018-05-03T12:20:07.062Z"),
                        "updatedAt" : ISODate("2018-05-03T12:20:07.062Z")
                    }
                ]
            }
        ],
        "__v" : 0
    }]

Что я пытаюсь получить из данных. Я пытаюсь получить пользовательские настройки каждой компании на основе идентификатора компании и идентификатора пользователя, но для нескольких компаний и нескольких пользователей, а не для одного.

Скажите, например, у меня есть эти входные данные [ userID: 2 , companyID: 6 ] и [userID:1, companyID:4]

Вывод , основанный на данных выше добавленных документов, должен быть

    [
        {
            "userID":2,
            "companyID":6,
            "preferences":[] // the preferences from that db
        },
        {
            "userID":1,
            "companyID":4,
            "preferences":[] // their respective preferences array
        }
    ]

Выше приведен желаемый результат, теперь мой подход к решению этого

подход

  • Я получаю входные параметры в виде массива объектов, например,

    input_json = [{
            "userID":2,
            "companyID":6
        },
        {
            "userID":1,
            "companyID":4
        },
    ]
    
  • После получения входного JSON я делаю два разных массива: пользовательский массив, массив компании, user_array будет содержать все идентификаторы пользователя, company_array будет содержать все идентификаторы компании, подобные этому

    user_array = input_json.map((elem)=>elem.userID) // which will hold all the company ID
    
    //For the above input_json, the value of `user_array` would be 
      //  `user_array: [2,1]`
    
    
    company_array = input_json.map((elem)=>elem.userID)
    //For the above input_json, the value of `company_array` would be 
      //  `company_array`:[4,4] 
    
  • После того, как я получил оба значения userIDs и companyID по отдельности, я запускаю запрос на MongoDB, используя $ in для сопоставления id из массива

    db.collection.find({
        userID:{
               $in:user_array 
        },
        accounts.companyID:{
            $in:company_array
        }
    })
    
  • Я не получаю желаемых результатов, я хочу, чтобы userID и companyID использовались в качестве уникального ключа для получения данных.

Но происходит то, что происходит сопоставление идентификатора пользователя в user_array и идентификатора компании в company_array. Однако я хочу, чтобы для получения данных были проверены и userID, и companyID.

Любая помощь будет высоко ценится :)

1 Ответ

0 голосов
/ 04 мая 2018

Просто немного расширив ваш пример, чтобы включить «возможный» случай, когда ваш массив запросов фактически запрашивает нескольких пользователей из одной компании.

Необходимым условием запроса является просто переназначение входного массива и его использование в аргументе запроса $or. который выглядит как:

{
  "$or": [
    {
      "companyID": "6",
      "accounts.userID": "3"
    },
    {
      "companyID": "6",
      "accounts.userID": "2"
    },
    {
      "companyID": "4",
      "accounts.userID": "1"
    }
  ]
}

Это будет соответствовать «документам», но соответствующая «пользовательская» информация содержится в массиве "accounts". Чтобы извлечь только эти элементы, нам нужно применить условие $filter, чтобы просто сохранить эти записи массива в соответствии с критериями. Тогда на самом деле нужно просто использовать $unwind для оставшегося содержимого массива и немного изменить форму документа, чтобы преобразовать его в желаемый формат вывода с помощью $project.

Весь сгенерированный оператор будет выглядеть так:

Company.aggregate([
  { '$match': { 
    '$or': [
      { companyID: '6', 'accounts.userID': '3' },
      { companyID: '6', 'accounts.userID': '2' },
      { companyID: '4', 'accounts.userID': '1' }
    ]
  }},
  { '$addFields': { 
    accounts: { 
     '$filter': { 
        input: '$accounts',
        cond: {
          '$or': [ 
            { '$and': [
              { '$eq': [ '$companyID', '6' ] },
              { '$eq': [ '$$this.userID', '3' ] }
            ] },
            { '$and': [
              { '$eq': [ '$companyID', '6' ] },
              { '$eq': [ '$$this.userID', '2' ] }
            ] }, 
            { '$and': [
              { '$eq': [ '$companyID', '4' ] }, 
              { '$eq': [ '$$this.userID', '1' ] }
            ] }
          ]
        }
      }
    }
  }},
  { '$unwind': '$accounts' },
  { '$project': {
    userID: '$accounts.userID',
    companyID: 1,
    preferences: '$accounts.preferences'
  }}
])

Содержимое $or для запроса и дополнительная форма $or для $filter в основном генерируются из ввода массив так:

    let query = {
      $or: input.map(({ userID, companyID }) =>
        ({ companyID, 'accounts.userID': userID }))
    };

    let condition = input.map(({ userID, companyID }) =>
      ({ "$and": [
        { "$eq": ["$companyID", companyID] },
        { "$eq": ["$$this.userID", userID] }
      ]})
    );

И затем используется в качестве аргументов в остальной части конструкции конвейера, которая в основном является статической. Обратите внимание, что использование в $filter как "cond" требует «логических операторов агрегирования», которые возвращают логическое значение на основе того, что они тестируют. Таким образом, они отличаются от операторов запроса в форме функции.

То же самое «спаривание» применяется к каждому условию $or, поэтому при поиске совпадения с этой комбинацией учитываются как значение "companyID", так и текущее значение "userID" в массиве счетов. В пределах $filter важно проверить "companyID" снаружи массива, одновременно проверяя текущий элемент массива.

Причина, по которой мы не можем сделать это со стандартным позиционным $ оператором проекции, по существу, связана с условием $or в запросе. Существует дополнительное ограничение «множественных совпадений», добавленное здесь для демонстрации, но из-за $or в запросе MongoDB не может определить, какой из «набора условий» фактически удовлетворяет позиции совпадения элемента для любого в любом случае отдельный документ.

Так что на самом деле не имеет значения, хотите ли вы, чтобы «один» пользователь соответствовал каждой компании или «многим», так как один и тот же фильтр агрегации должен применяться для извлечения правильных сведений о «пользователях» в любом случае.

Ниже приведен полный список для демонстрации:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/test';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

let input = [
  {
    "userID": 3,
    "companyID": 6
  },
  {
    "userID": 2,
    "companyID": 6
  },
  {
    "userID": 1,
    "companyID": 4
  }
];


// non-strict for testing
const Company = mongoose.model('Company', new Schema({},{ strict: false }));

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    // Clean data to actually be matching strings
    input = input.map(({ userID, companyID }) =>
      ({ userID: userID.toString(), companyID: companyID.toString() }));


    let query = {
      $or: input.map(({ userID, companyID }) =>
        ({ companyID, 'accounts.userID': userID }))
    };
    log(query);

    let condition = input.map(({ userID, companyID }) =>
      ({ "$and": [
        { "$eq": ["$companyID", companyID] },
        { "$eq": ["$$this.userID", userID] }
      ]})
    );
    log(condition);


    let result = await Company.aggregate([
      { "$match": query },
      { "$addFields": {
        "accounts": {
          "$filter": {
            "input": "$accounts",
            "cond": { "$or": condition }
          }
        }
      }},
      { "$unwind": "$accounts" },
      { "$project": {
        "userID": "$accounts.userID",
        "companyID": 1,
        "preferences": "$accounts.preferences"
      }}
    ]);

    log(result);

    mongoose.disconnect();
  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }


})()

И это дает вывод как:

[
  {
    "_id": "5aeaf7c73c3e9de82d91e439",
    "companyID": "4",
    "userID": "1",
    "preferences": [
      {
        "emailNotification": true,
        "smsNotification": true,
        "pushNotification": false,
        "webNotification": false,
        "lastUpdatedBy": "SYSTEM",
        "_id": "5aeaf7c720262a1db759edf7",
        "preferenceID": "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
        "createdAt": "2018-05-03T11:51:35.509Z",
        "updatedAt": "2018-05-03T11:51:35.509Z"
      },
      {
        "emailNotification": true,
        "smsNotification": true,
        "pushNotification": false,
        "webNotification": false,
        "lastUpdatedBy": "SYSTEM",
        "_id": "5aeaf7c720262a1db759edf6",
        "preferenceID": "6fb118-4c56-11e8-842f-0ed5f89f718b",
        "createdAt": "2018-05-03T11:51:35.509Z",
        "updatedAt": "2018-05-03T11:51:35.509Z"
      }
    ]
  },
  {
    "_id": "5aeafe6d3c3e9de82d91e43b",
    "companyID": "6",
    "userID": "2",
    "preferences": [
      {
        "emailNotification": true,
        "smsNotification": true,
        "pushNotification": false,
        "webNotification": false,
        "lastUpdatedBy": "SYSTEM",
        "_id": "5aeafe738b1d5f2057419ca1",
        "preferenceID": "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
        "createdAt": "2018-05-03T12:20:03.987Z",
        "updatedAt": "2018-05-03T12:20:03.987Z"
      },
      {
        "emailNotification": true,
        "smsNotification": true,
        "pushNotification": false,
        "webNotification": false,
        "lastUpdatedBy": "SYSTEM",
        "_id": "5aeafe738b1d5f2057419ca0",
        "preferenceID": "6fb118-4c56-11e8-842f-0ed5f89f718b",
        "createdAt": "2018-05-03T12:20:03.987Z",
        "updatedAt": "2018-05-03T12:20:03.987Z"
      }
    ]
  },
  {
    "_id": "5aeafe6d3c3e9de82d91e43b",
    "companyID": "6",
    "userID": "3",
    "preferences": [
      {
        "emailNotification": true,
        "smsNotification": true,
        "pushNotification": false,
        "webNotification": false,
        "lastUpdatedBy": "SYSTEM",
        "_id": "5aeafe778b1d5f2057419ca4",
        "preferenceID": "6fbd6c-4c56-11e8-842f-0ed5f89f718b",
        "createdAt": "2018-05-03T12:20:07.062Z",
        "updatedAt": "2018-05-03T12:20:07.062Z"
      },
      {
        "emailNotification": true,
        "smsNotification": true,
        "pushNotification": false,
        "webNotification": false,
        "lastUpdatedBy": "SYSTEM",
        "_id": "5aeafe778b1d5f2057419ca3",
        "preferenceID": "6fb118-4c56-11e8-842f-0ed5f89f718b",
        "createdAt": "2018-05-03T12:20:07.062Z",
        "updatedAt": "2018-05-03T12:20:07.062Z"
      }
    ]
  }
]

Что показывает, что мы вернули из документов только соответствующие компании и пользовательские комбинации.


ПРИМЕЧАНИЕ Не уверен, что при введенной вами выборке было "преднамеренное" , что значения даны как "числовые", а не как "строки", где, конечно, они на самом деле "строки" в данных, которым он должен соответствовать. Существует простая строка кода, которая преобразует числовые значения в строки, что, разумеется, не является необходимым, если и ваши входные типы, и сохраненные типы уже совпадают.

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

Mongoose не делает этого, потому что он не может сделать «предположение» на этапе конвейерного агрегирования, что данные, представленные в то время, находятся в том же состоянии, о котором знает схема. Операции агрегации обычно связаны с «переформированием документов», и по этой причине «схема» здесь не применяется.

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