Найти ВСЕ записи, для которых ни один элемент в массивах не имеет значения - PullRequest
2 голосов
/ 03 октября 2019

Я хочу найти ВСЕ записи, в которых НЕТ элемента массива EITHER (вещи1 или вещи2) имеет значение "color". (поэтому ВСЕ цвета либо '', либо нулевые, либо не существуют)

Вот структура данных -

{
    _id: objectId(),
    things1: [
        {
            height:
            weight:
            color:
        }
    ],
    things2: [
        {
            size:
            flavor:
            color:
        }
    ]
}

Я пробовал это:

.find({
    $and: [
        {
            things1:{ 
                $elemMatch: { 
                    $or: [
                        {
                            color: ''
                        },
                        {
                            color: null
                        },
                        {
                            color: { $exists: false }
                        }
                    ]
                } 
            } 
        },
        {
            things2:{ 
                $elemMatch: { 
                    $or: [
                        {
                            color: ''
                        },
                        {
                            color: null
                        },
                        {
                            color: { $exists: false }
                        }
                    ]
                } 
            } 
        }
    ]
})

Это возвращает ЛЮБЫЕ записи, где ЛЮБОЙ элемент имеет пустой, нулевой или несуществующий «цвет» ... это означает, что если есть вещи в «things1», а две из трех имеют цвет, а третий - пустая строка, ивсе элементы в "things2" имеют цвет - вышеупомянутый запрос вернет этот документ

Где я хочу ТОЛЬКО записи, где ВСЕ поля "color" пустые, нулевые или не существуют.

Любая помощь оценена спасибо

Ответы [ 2 ]

3 голосов
/ 03 октября 2019

Мы можем представить себе обратный маршрут, т. Е. строка, которая должна быть либо нулевой, либо отсутствующей, либо пустой, это в основном строка без символов. Мы можем вычислить NOR of (присутствует заполненный цветв массиве things1, заполненный цвет присутствует в массиве things2.

Например:

db.collection.find({
    $nor:[
        {
            "things1.color":/.+/
        },
        {
            "things2.color":/.+/
        }
    ]
}).pretty()
1 голос
/ 03 октября 2019

Я собираюсь упростить ваш набор данных, чтобы читателям было легче понять пример, но логика все еще остается той же

{ "a" : [ { "sample" : 1 } ], "b" : [ { "sample" : 1 } ] }       // No color
{ "a" : [ { "color" : "b" } ], "b" : [ { "color" : "b" } ] }     // YES - Valid color
{ "b" : [ { "color" : "" }, { "color" : "a" } ] }                // Empty color 
{ "b" : [ { "color" : null }, { "color" : "a" } ] }              // Null color
{ "b" : [ { "sample" : 1 } ] }                                   // No color
{ "b" : [ { "color" : "b" } ] }                                  // YES - Valid Color
{ "a" : [ { "color" : "" }, { "color" : "a" } ] }                // Empty color
{ "a" : [ { "color" : null }, { "color" : "a" } ] }              // Null Color
{ "a" : [ { "sample" : 1 } ] }                                   // No color
{ "a" : [ { "color" : "b" } ] }                                  // YES - Valid color

В основном все те же комбинации, что и у васищем, так как здесь есть два свойства, где одно или оба содержат объекты в массиве, где вы хотите, чтобы ВСЕ элементы в массиве имели свойство color:

  • существующий
  • Не пусто
  • Не пусто

Короче говоря, только три из этих документов соответствуют требованиям. Вот как их получить:

db.collection.find({
  "$or": [
    {
      "a.color": { "$exists": true },
      "a": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
    },
    {
      "b.color": { "$exists": true },
      "b": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
    }
  ]
})

При $or у нас есть два парных условия. Будучи отдельно (очень важно) для проверки существования именованного пути, а затем для поиска случаев, когда ЛЮБЫЕ объекты в массиве соответствуют условиям, которые исключают их (ноль или пусто) и отклонить эти документы, используя выражение $not.

$or используется только для представления пар для каждого дискретного поля, содержащего массив.

Результаты, конечно, следующие:

{ "a" : [ { "color" : "b" } ], "b" : [ { "color" : "b" } ] }
{ "b" : [ { "color" : "b" } ] }
{ "a" : [ { "color" : "b" } ] }

Будучи только теми документами, в которых один или оба из предоставленных внешних ключей содержали массивы, где ВСЕ элементы имеют свойство color со значением не null.


В том случае, если вы действительно имеете в виду, что такие поля, как things1 и things2, могут динамически меняться от документа к документу и, возможно, иметь things3, а вам нужно ALL свойств с их содержащими массивами для соответствия условиям, тогда вам в основном не повезло для стандартного запроса, и вам нужно будет вернуться к aggregate().

ВНапример, если бы мы добавили документ, подобный следующему:

{ "a": [{ "color": "b" }, "c": [{ "color": "" }] }

Тогда приведенная выше базовая форма запроса все равно вернет этот документ, поэтому вместо этого вы будете использовать aggregate():

db.collection.aggregate([
  { "$addFields": {
    "comb": {
      "$reduce": {
        "input": {
          "$map": {
            "input": {
              "$filter": {
                "input": { "$objectToArray": "$$ROOT" },
                "cond": { "$in": [ "$$this.k", [ "a", "b", "c" ] ] }
              }
            },
            "as": "el",
            "in": {
              "$map": {
                "input": "$$el.v",
                "in": {
                  "$mergeObjects": [
                    { "type": "$$el.k" },
                    "$$this"
                  ]
                }
              }
            }
          }
        },
        "initialValue": [],
        "in": { "$concatArrays": [ "$$value", "$$this" ] }
      }
    }
  }},
  { "$match": {
    "comb.color": { "$exists": true },
    "comb": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
  }},
  { "$addFields": {
    "comb": "$$REMOVE"
  }} 
])

Но это действительно не желательно. Обратите внимание, что для динамического обхода клавиш вам нужен $objectToArray, чтобы эффективно превратить все ключи в документе в одну запись массива. Затем, конечно, $filter для ожидаемых клавиш или, в качестве альтернативы, просто измените логику на , исключите такие вещи, как _id и другие значения, которые не будут применяться.

Это тогда объединит эти массивы вместе, в то время как переназначит имя key как свойство в массиве. Отмечая главным образом, что реальная главная точка здесь - это не "a.color" и "b.color", у нас просто есть одиночный путь, который теперь "comb" представляет комбинированное отображение.

Это приведет кожидаемый результат, но реальное решение здесь должно быть видно в реализованной логике конвейера, и вместо нескольких свойств документа с массивами, лучший подход - это отдельный массив , в котором используется только thing1или thing2 (или в данном случае "a" или "b" или "c"), чтобы быть просто другим значением свойства с последовательно именованным в этом массиве.

Таким образом, эта форма хранения ваших данных далекоболее эффективный:

{ "data" : [ { "type" : "a", "color" : "" }, { "type" : "a", "color" : "a" }, { "type" : "b", "color" : "" }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "color" : null }, { "type" : "a", "color" : "a" }, { "type" : "b", "color" : null }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "sample" : 1 }, { "type" : "b", "sample" : 1 } ] }
{ "data" : [ { "type" : "a", "color" : "b" }, { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "b", "color" : "" }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "b", "color" : null }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "b", "sample" : 1 } ] }
{ "data" : [ { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "a", "color" : "" }, { "type" : "a", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "color" : null }, { "type" : "a", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "sample" : 1 } ] }
{ "data" : [ { "type" : "a", "color" : "b" } ] }
{ "data" : [ { "type" : "a", "color" : "b" }, { "type" : "c", "color" : "" } ] }

Если это была структура вашей коллекции, то запрос элементов массива ALL становится очень простым и является в основном самым последним этапом aggregate(), который по сути переводитсяваша существующая структура в этой форме:

db.collection.find({
  "data.color": { "$exists": true },
  "data": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
})

И тогда это по существу те же три документа, которые соответствуют требованиям:

{ "data" : [ { "type" : "a", "color" : "b" }, { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "a", "color" : "b" } ] }

Нетздесь следует отметить, что непротиворечивое имя пути, такое как "data.type", имеет много преимуществ, которые также делают это имя полезным для параметров запроса. Вы также можете просто добавить его в условия фильтрации, если вы просто хотите искать вещи типа "где" равно thing1, и это также значительно упрощает обновление документов.

Стоит рассмотреть, так как базовыеформы запросов, которые не полагаются на манипуляции с операторами aggregate(), работают намного лучше в долгосрочной перспективе.

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