Mongoose匹配元素或带有$ in语句的空数组

问题描述 投票:0回答:1

我正在尝试选择隐私设置与提供的文档相匹配的任何文档以及任何没有任何隐私设置的文档(即公共)。

当前的行为是,如果我有一个带有引用另一个集合的对象id数组的模式:

privacy: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Category',
    index: true,
    required: true,
    default: []
}],

我想过滤我的类别和公共类别的所有内容,在我们的案例中没有隐私设置的内容。即一个空数组[]

我们目前使用或查询查询

{"$or":[
    {"privacy": {"$size": 0}},
    {"privacy": {"$in":
        ["5745bdd4b896d4f4367558b4","5745bd9bb896d4f4367558b2"]}
    }
]}

我想通过仅提供一个空数组[]作为$ in语句中的比较选项来查询它。在mongodb中哪些是可能的:

db.emptyarray.insert({a:1})
db.emptyarray.insert({a:2, b:null})
db.emptyarray.insert({a:2, b:[]})
db.emptyarray.insert({a:3, b:["perm1"]})
db.emptyarray.insert({a:3, b:["perm1", "perm2"]})
db.emptyarray.insert({a:3, b:["perm1", "perm2", []]})
> db.emptyarray.find({b:[]})
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce0"), "a" : 2, "b" : [ ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce3"), "a" : 3, "b" : [ "perm1", "perm2", [ ] ] }
> db.emptyarray.find({b:{$in:[]}})
> db.emptyarray.find({b:{$in:[[], "perm1"]}})
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce0"), "a" : 2, "b" : [ ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce1"), "a" : 3, "b" : [ "perm1" ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce2"), "a" : 3, "b" : [ "perm1", "perm2" ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce3"), "a" : 3, "b" : [ "perm1", "perm2", [ ] ] }
> db.emptyarray.find({b:{$in:[[], "perm1", null]}})
{ "_id" : ObjectId("5a305f3dd89e8a887e629cde"), "a" : 1 }
{ "_id" : ObjectId("5a305f3dd89e8a887e629cdf"), "a" : 2, "b" : null }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce0"), "a" : 2, "b" : [ ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce1"), "a" : 3, "b" : [ "perm1" ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce2"), "a" : 3, "b" : [ "perm1", "perm2" ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce3"), "a" : 3, "b" : [ "perm1", "perm2", [ ] ] }
> db.emptyarray.find({b:{$in:[[]]}})
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce0"), "a" : 2, "b" : [ ] }
{ "_id" : ObjectId("5a305f3dd89e8a887e629ce3"), "a" : 3, "b" : [ "perm1", "perm2", [ ] ] }

也许是这样的:

"privacy_locations":{
     "$in": ["5745bdd4b896d4f4367558b4","5745bd9bb896d4f4367558b2",[]]
}

但是这个查询可以从控制台(CLI)运行,但不会在它抛出强制转换错误的代码中运行:

{
    "message":"Error in retrieving records from db.", 
    "error":
        {
        "message":"Cast to ObjectId failed for value \"[]\" at ...
        }
}

现在我完全理解正在进行转换,因为Schema被定义为ObjectId。

但我仍然发现这种方法缺少两种可能的情况。

我相信可以在$ in语句中查询(在MongoDB中)null选项或空数组。

array: {$in:[null, [], [option-1, option-2]}

它是否正确?

我一直在想,我的问题的最佳解决方案(无法在选项中选择或为空)可能是将空数组作为具有ALL的修复选项的数组。隐私设置意味着ALL而不是现在的设置,如果没有设置,则认为是全部。

但我不想要现有代码的主要重构,我只需要看看我是否可以进行更好的查询或更高性能的查询。

今天我们让查询使用带有索引问题的$ OR语句。即使速度很快,我也想关注这个问题,即使这不是一个错误。

我将不胜感激任何意见或指导。

mongodb mongoose mongodb-query mongoose-schema
1个回答
0
投票

半简短的答案是,模式是privacy属性(ObjectIdArray)的混合类型,同时声明它在模式中严格地类型为ObjectId

由于MongoDB是无模式的,因此它将允许每个文档的任何文档形状,并且不需要验证查询文档以匹配模式。另一方面,Mongoose旨在应用模式实施,因此它将在尝试查询数据库之前验证针对模式的查询文档。 { privacy: { $in: [[]] } }的查询文档将无法通过验证,因为空数组不是错误所指示的有效ObjectId

模式需要将类型声明为Mixed(不支持ref)以继续使用空数组作为可接受的类型以及ObjectId

// Current
const FooSchema = new mongoose.Schema({
  privacy: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Category',
    index: true,
    required: true,
    default: []
  }]
});

const Foo = connection.model('Foo', FooSchema);

const foo1 = new Foo();
const foo2 = new Foo({privacy: [mongoose.Types.ObjectId()]});

Promise.all([
  foo1.save(),
  foo2.save()
]).then((results) => {
  console.log('Saved', results);
  /*
  [
    { __v: 0, _id: 5a36e36a01e1b77cba8bd12f, privacy: [] },
    { __v: 0, _id: 5a36e36a01e1b77cba8bd131, privacy: [ 5a36e36a01e1b77cba8bd130 ] }
  ]
  */

  return Foo.find({privacy: { $in: [[]] }}).exec();
}).then((results) => {
  // Never gets here
  console.log('Found', results);
}).catch((err) => {
  console.log(err);
  // { [CastError: Cast to ObjectId failed for value "[]" at path "privacy" for model "Foo"] }
});

和工作版本。还要注意调整以正确应用所需的标志,索引标志和默认值。

// Updated
const FooSchema = new mongoose.Schema({
  privacy: {
    type: [{
      type: mongoose.Schema.Types.Mixed
    }],
    index: true,
    required: true,
    default: [[]]
  }
});

const Foo = connection.model('Foo', FooSchema);

const foo1 = new Foo();
const foo2 = new Foo({
  privacy: [mongoose.Types.ObjectId()]
});

Promise.all([
  foo1.save(),
  foo2.save()
]).then((results) => {
  console.log(results);
  /*
    [
      { __v: 0, _id: 5a36f01733704f7e58c0bf9a, privacy: [ [] ] },
      { __v: 0, _id: 5a36f01733704f7e58c0bf9c, privacy: [ 5a36f01733704f7e58c0bf9b ] }
    ]
  */

  return Foo.find().where({
    privacy: { $in: [[]] }
  }).exec();
}).then((results) => {
  console.log(results);
  // [ { _id: 5a36f01733704f7e58c0bf9a, __v: 0, privacy: [ [] ] } ]
});
© www.soinside.com 2019 - 2024. All rights reserved.