Найти документы, где поле сравнивается с другим в массиве - PullRequest
0 голосов
/ 26 мая 2018

Допустим, у меня есть коллекция документов, которая выглядит следующим образом:

{
    "_id" : ObjectId("5afa6df3a24cdb1652632ef5"),
    "createdBy" : {
        "_id" : "59232a1a41aa651ddff0939f"
    },
    "owner" : {
        "_id" : "5abc4dc0f47f732c96d84aac"
    },
    "acl" : [
        {
            "profile" : {
                "_id" : "59232a1a41aa651ddff0939f"
            }
        },
        {
            "profile" : {
                "_id" : "5abc4dc0f47f732c96d84aac"
            }
        }
    ]
}

Я хочу найти все документы, где createdBy._id != owner._id И где createdBy._id появляется в одной из записей вacl массив.Со временем я захочу обновить все такие документы, чтобы установить поле owner._id равным полю createdBy._id.Сейчас я просто пытаюсь выяснить, как запросить подмножество документов, которые я хочу обновить.

До сих пор я придумал следующее:

db.boards.find({
  $where: "this.createdBy._id != this.owner._id", 
  $where: function() {
    return this.acl.some(
      function(e) => {
        e.profile._id === this.createdBy._id
      }, this);
  }
)

(я использовал синтаксис ES5 на случай, если ES6 не в порядке)

Но когда я запускаюВ этом запросе я получаю следующую ошибку:

Ошибка: ошибка: {"ok": 0, "errmsg": "Ошибка типа: e.profile не определен: \ n_funcs2 / <@: 2:36 \ n_funcs2 @: 2: 12 \ n "," code ": 139} </p>

Как мне выполнить этот запрос / что здесь происходит?Я ожидал, что мой запрос сработает, основываясь на прочитанных мною документах .Выше e должен быть элементом массива acl, поэтому я ожидаю, что у него будет поле profile, но это не так.

Обратите внимание, я использую Mongo 3.2, поэтому я не могу использовать $ expr , что, как я видел, некоторые ресурсы указывают на возможность.

Разрешение

Оказывается, я сделал неверное предположение о схеме этой коллекции.Причина, по которой я столкнулся с вышеуказанной ошибкой, состоит в том, что некоторые документы имеют массив acl с элементом, который не имеет поля profile.Приведенный ниже запрос проверяет этот случай.У него также есть один $where, потому что способ, которым я написал его изначально (с двумя), в конечном итоге дал мне ИЛИ условий вместо И.

db.boards.find({
  $where: function() {
    return this.acl.some(
      function(e) => {
        e.profile !== undefined && e.profile._id === this.createdBy._id && this.createdBy._id != this.owner._id
      }, this);
  }
)

1 Ответ

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

Вы все еще можете использовать aggregate() здесь с MongoDB 3.2, но просто используйте $redact вместо:

db.boards.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$and": [
          { "$ne": [ "$createdBy._id", "$owner._id" ] },
          { "$setIsSubset": [["$createdBy._id"], "$acl.profile._id"] }
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

Или с $where дляВ оболочке MongoDB 3.2 вам просто нужно сохранить скопированную копию this, и ваш синтаксис немного отключился:

db.boards.find({
  "$where": function() {
    var self = this;
    return (this.createdBy._id != this.owner._id)
      && this.acl.some(function(e) {
        return e.profile._id === self.createdBy._id
     })
  }
})

Или в среде, совместимой с ES6, тогда:

db.boards.find({
  "$where": function() {
    return (this.createdBy._id != this.owner._id)
      && this.acl.some(e => e.profile._id === this.createdBy._id)
  }
})

Агрегат - это наиболее эффективный вариант из двух, и он всегда должен быть предпочтительнее использования JavaScript-оценки

И, что бы ни стоило, более новый синтаксис с $expr будет выглядеть так:

db.boards.find({
  "$expr": {
    "$and": [
      { "$ne": [ "$createdBy._id", "$owner._id" ] },
      { "$in": [ "$createdBy._id", "$acl.profile._id"] }
    ]
  }
})

Использование $in вместо $setIsSubset, где синтаксис немного короче.


NOTE Единственная причина, по которой здесь выполняется сравнение JavaScript, заключается в том, что вы ошибочно сохранили значения ObjectId как "строки" в этих полях.Там, где есть «реальное» ObjectId, как в поле _id, для сравнения необходимо взять «строку» из valueOf(), чтобы сравнить:

    return (this.createdBy._id.valueOf() != this.owner._id.valueOf())
      && this.acl.some(e => e.profile._id.valueOf() === this.createdBy._id.valueOf())

Без этого это фактически «Сравнение объектов» с JavaScript, а { a: 1 } === { a: 1 } на самом деле false.Поэтому избегание этой сложности является еще одной причиной, по которой вместо этого существуют собственные операторы.

...