配列フィールドが空でないMongoDBレコードを見つける


503

私のすべてのレコードには、「画像」というフィールドがあります。このフィールドは文字列の配列です。

この配列が空でない最新の10レコードが必要です。

私はググってみましたが、不思議なことに私はこれについてあまり知りませんでした。私は$ whereオプションを読みましたが、それがネイティブ関数にとってどれほど遅いか、そしてより良い解決策があるかどうか疑問に思っていました。

そしてそれでも、それはうまくいきません:

ME.find({$where: 'this.pictures.length > 0'}).sort('-created').limit(10).execFind()

何も返しません。this.pictures長さビットなしで離れることは機能しますが、もちろん空のレコードも返します。

回答:


829

キーがないドキュメントもある場合は、次を使用できます。

ME.find({ pictures: { $exists: true, $not: {$size: 0} } })

$ sizeが関係する場合、MongoDBはインデックスを使用しないため、より良い解決策を次に示します。

ME.find({ pictures: { $exists: true, $ne: [] } })

MongoDB 2.6リリース以降、オペレーターと比較できますが$gt、予期しない結果が生じる可能性があります(この回答で詳細な説明見つけることができます):

ME.find({ pictures: { $gt: [] } })

6
配列が存在し、空ではないことを確認するため、これは正しいアプローチです。
LeandroCR 2015年

どのようにして同じ機能を実現できますかmongoengine
Rohit Khatri

54
ME.find({ pictures: { $gt: [] } })新しいMongoDBバージョンであっても、注意は危険です。リストフィールドにインデックスがあり、そのインデックスがクエリ中に使用されると、予期しない結果が生じます。たとえばdb.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()、正しい数値をdb.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()返し、を返します0
wojcikstefan 2017年

1
これはあなたのために動作しない理由については、以下の私の詳細な回答を参照してください。stackoverflow.com/a/42601244/1579058
wojcikstefan

6
@wojcikstefanのコメントは、実際に特定の状況下で一致するドキュメントを返さない最後の提案を人々が使用できないようにするために、賛成投票する必要があります。
Thomas Jung

181

特にmongodbのドキュメントをさらに調べ、少し戸惑った後、これが答えでした。

ME.find({pictures: {$exists: true, $not: {$size: 0}}})

27
これは機能しません。これが以前に機能したかどうかはわかりませんが、「pictures」キーを持たないオブジェクトも返されます。
rdsoze 14

17
@rdsozeが実際に言ったことが真実であるときに、この回答が63の賛成票を持っていることは信じられないことpicturesです。クエリは、フィールドのないレコードも返します。
Dan Dascalescu 2014

5
$ sizeがlinkに含まれている場合、mongoDBはインデックスを使用しませんので注意してください。{$ ne:[]}と、場合によっては{$ ne:null}を含めた方がよいでしょう。
Levente Dobson、

17
@rdsozeの質問の最初の行は、「すべてのレコードに "pictures"というフィールドがあります。このフィールドは配列です」と述べています。さらに、これは完全に現実的で一般的なシナリオです。この答えは間違いではありません。書かれたとおりの質問に対して正確に機能し、別の問題を解決しないという事実に対して批判または反対票を投じることはばかげています。
Mark Amery

1
@Cecすべてのドキュメントによると、クエリで$ sizeを使用すると、インデックスを使用せずに結果を高速化できます。したがって、そのフィールドにインデックスがあり、それを使用したい場合は、{$ ne:[]}のような他のアプローチに固執します。それがうまくいく場合は、インデックスを使用します。
Levente Dobson

108

これもあなたのために働くかもしれません:

ME.find({'pictures.0': {$exists: true}});

2
いいね!これにより、最小サイズを確認することもできます。配列に常にインデックスが付けられているかどうか知っていますか?pictures.2存在するが存在pictures.1しない場合があるでしょうか?
anushr 2013年

2
$existsオペレータはオフセットブール値、ではありません。@tenbatsuはのtrue代わりに使用する必要があり1ます。
ekillaby 14年

2
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? はい、そのケースは起こり得ます。
Bndr 2014

@TheBndrこれはpictures、配列ではなくサブドキュメントの場合にのみ発生します。例pictures: {'2': 123}
JohnnyHK 2015

4
これは便利で直感的ですが、パフォーマンスが重要である場合は注意してくださいpictures。インデックスがオンであっても、コレクション全体のスキャンを実行します。
wojcikstefan 2017年

35

クエリを実行するときは、精度とパフォーマンスの2つの点に注意してください。それを念頭に置いて、MongoDB v3.0.14でいくつかの異なるアプローチをテストしました。

TL; DR db.doc.find({ nums: { $gt: -Infinity }})が最も高速で信頼性が高い(少なくとも、私がテストしたMongoDBバージョンでは)。

編集:これはMongoDB v3.6では機能しません!可能な解決策については、この投稿の下のコメントを参照してください。

セットアップ

リストフィールドなしの1kのドキュメント、空のリストを含む1kのドキュメント、空でないリストを含む5つのドキュメントを挿入しました。

for (var i = 0; i < 1000; i++) { db.doc.insert({}); }
for (var i = 0; i < 1000; i++) { db.doc.insert({ nums: [] }); }
for (var i = 0; i < 5; i++) { db.doc.insert({ nums: [1, 2, 3] }); }
db.doc.createIndex({ nums: 1 });

これは、以下のテストのようにパフォーマンスを真剣に考えるには十分な規模ではないことを認識していますが、さまざまなクエリの正確さと選択したクエリプランの動作を示すには十分です。

テスト

db.doc.find({'nums': {'$exists': true}}) 間違った結果を返します(私たちが達成しようとしていることに対して)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': {'$exists': true}}).count()
1005

-

db.doc.find({'nums.0': {'$exists': true}})正しい結果が返されますが、フルコレクションスキャンを使用すると遅くなります(COLLSCAN説明の通知段階)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).explain()
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.doc",
    "indexFilterSet": false,
    "parsedQuery": {
      "nums.0": {
        "$exists": true
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "nums.0": {
          "$exists": true
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": [ ]
  },
  "serverInfo": {
    "host": "MacBook-Pro",
    "port": 27017,
    "version": "3.0.14",
    "gitVersion": "08352afcca24bfc145240a0fac9d28b978ab77f3"
  },
  "ok": 1
}

-

db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}})間違った結果を返します。これは、無効なインデックススキャンがドキュメントを進めないためです。それはおそらく正確ですが、インデックスがないと遅くなります。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 2,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$gt": {
              "$size": 0
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "({ $size: 0.0 }, [])"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}})正しい結果が返されますが、パフォーマンスが低下します。技術的にはインデックススキャンを実行しますが、それでもすべてのドキュメントを進め、それらをフィルタリングする必要があります)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2016,
  "advanced": 5,
  "needTime": 2010,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$exists": true
          }
        },
        {
          "$not": {
            "nums": {
              "$size": 0
            }
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 2016,
    "advanced": 5,
    "needTime": 2010,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 2005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 2005,
      "executionTimeMillisEstimate": 0,
      "works": 2015,
      "advanced": 2005,
      "needTime": 10,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, MaxKey]"
        ]
      },
      "keysExamined": 2015,
      "dupsTested": 2015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $ne: [] }})は正しい結果を返し、少し高速ですが、パフォーマンスはまだ理想的ではありません。IXSCANを使用して、既存のリストフィールドでドキュメントを進めるだけですが、空のリストを1つずつ除外する必要があります。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 1018,
  "advanced": 5,
  "needTime": 1011,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "$not": {
            "nums": {
              "$eq": [ ]
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 1017,
    "advanced": 5,
    "needTime": 1011,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 1005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 1005,
      "executionTimeMillisEstimate": 0,
      "works": 1016,
      "advanced": 1005,
      "needTime": 11,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, undefined)",
          "(undefined, [])",
          "([], MaxKey]"
        ]
      },
      "keysExamined": 1016,
      "dupsTested": 1015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $gt: [] }})使用されているインデックスに依存しているため、危険です。予期しない結果が生じる可能性があります。これは、ドキュメントを進めない無効なインデックススキャンが原因です。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()
5

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 1,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "nums": {
        "$gt": [ ]
      }
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "([], BinData(0, ))"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums.0’: { $gt: -Infinity }}) 正しい結果を返しますが、パフォーマンスが低下します(フルコレクションスキャンを使用します)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "COLLSCAN",
  "filter": {
    "nums.0": {
      "$gt": -Infinity
    }
  },
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2007,
  "advanced": 5,
  "needTime": 2001,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "direction": "forward",
  "docsExamined": 2005
}

-

db.doc.find({'nums': { $gt: -Infinity }})驚くべきことに、これは非常にうまく機能します!正しい結果が得られ、高速で、インデックススキャンフェーズから5つのドキュメントを進めます。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "FETCH",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 16,
  "advanced": 5,
  "needTime": 10,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "docsExamined": 5,
  "alreadyHasObj": 0,
  "inputStage": {
    "stage": "IXSCAN",
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 15,
    "advanced": 5,
    "needTime": 10,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "keyPattern": {
      "nums": 1
    },
    "indexName": "nums_1",
    "isMultiKey": true,
    "direction": "forward",
    "indexBounds": {
      "nums": [
        "(-inf.0, inf.0]"
      ]
    },
    "keysExamined": 15,
    "dupsTested": 15,
    "dupsDropped": 10,
    "seenInvalidated": 0,
    "matchTested": 0
  }
}

あなたの非常に詳細な回答@wojcikstefanをありがとう。残念ながら、あなたの提案した解決策は私の場合にはうまくいかないようです。2mのドキュメントを含むMongoDB 3.6.4コレクションがあり、そのほとんどにseen_events文字列配列があり、これにもインデックスが付けられています。で検索すると{ $gt: -Infinity }、ドキュメントがすぐに見つかりません。{ $exists: true, $ne: [] }私を使用すると、FETCHステージで多くの時間が無駄になり、1,2mのドキュメントが得られます:gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
NCode

あなたは正しいようです@Ncode-これはMongoDB v3.6では機能しなくなりました:(私は数分間それをいじってみましたが、ここに私が見つけたものがあります:1 db.test_collection.find({"seen_events.0": {$exists: true}}).コレクションスキャンを使用しているので悪いです。2。db.test_collection.find({seen_events: {$exists: true, $ne: []}})は。悪いそのIXSCANは、すべての文書に一致した後、フィルタリングが遅いFETCH相で行われるため、同じ3.のために行くdb.test_collection.find({seen_events: {$exists: true, $not: {$size: 0}}})4.他のすべてのクエリが無効な結果を返す。。
wojcikstefan

1
@NCodeが解決策を見つけました!空でないものseen_eventsがすべて文字列を含むことが確実な場合は、これを使用できますdb.test_collection.find({seen_events: {$gt: ''}}).count()。パフォーマンスが良いことを確認するには、をチェックしてくださいdb.test_collection.find({seen_events: {$gt: ''}}).explain(true).executionStats。おそらく、スキーマ検証を介して、表示されたイベントが文字列であることを強制できます:docs.mongodb.com/manual/core/schema-validation
wojcikstefan

ありがとう!既存の値はすべて文字列なので、試してみましょう。MongoDBバグトラッカーにこの問題を説明するバグもあります。jira.mongodb.org/ browse
NCode

30

2.6リリース以降、これを行う別の方法は、フィールドを空の配列と比較することです。

ME.find({pictures: {$gt: []}})

シェルでテストする:

> db.ME.insert([
{pictures: [1,2,3]},
{pictures: []},
{pictures: ['']},
{pictures: [0]},
{pictures: 1},
{foobar: 1}
])

> db.ME.find({pictures: {$gt: []}})
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a7"), "pictures": [ 1, 2, 3 ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a9"), "pictures": [ "" ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4aa"), "pictures": [ 0 ] }

そのためpictures、少なくとも1つの配列要素を持つドキュメントが適切に含まれ、pictures空の配列であるか、配列ではないか、欠落しているドキュメントが除外されます。


7
インデックスを使用しようとすると、この答えは注意を促すかもしれません。実行するdb.ME.createIndex({ pictures: 1 })と、db.ME.find({pictures: {$gt: []}})少なくともMongoDB v3.0.14では結果が返されません
wojcikstefan

@wojcikstefan良いキャッチ。これを再検討する必要があります。
JohnnyHK 2017年

5

これを実現するには、次のいずれかを使用できます。
どちらも、要求されたキーが含まれていないオブジェクトの結果を返さないようにします。

db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})

4

「pictures」が配列で空ではないすべてのドキュメントのみを取得します

ME.find({pictures: {$type: 'array', $ne: []}})

3.2より前のバージョンのMongoDbを使用している場合は、の$type: 4代わりにを使用してください$type: 'array'。このソリューションは$ sizeも使用しないことに注意してくださいため、インデックスに問題はありません(「クエリはクエリの$ size部分にインデックスを使用できません」)。

これらを含む他のソリューション(受け入れられた回答):

ME.find({pictures:{$ exists:true、$ not:{$ size:0}}}); ME.find({pictures:{$ exists:true、$ ne:[]}})

ある間違った彼らは、例えば、「画像」は、場合でも、文書を返すのでnullundefined、0、など


2

$elemMatch演算子を使用する:ドキュメントに従って

$ elemMatch演算子は、指定されたすべてのクエリ条件に一致する少なくとも1つの要素を持つ配列フィールドを含むドキュメントに一致します。

$elemMatches値が配列であり、空でないことを確認します。したがって、クエリは次のようになります

ME.find({ pictures: { $elemMatch: {$exists: true }}})

PSこのコードの変種は、MongoDB UniversityのM121コースにあります。


0

Mongo演算子$ existsに対してヘルパーメソッドExistsを使用することもできます。

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });

0
{ $where: "this.pictures.length > 1" }

$ whereを使用し、配列フィールドのサイズを返すthis.field_name.lengthを渡し、数値と比較してチェックします。いずれかの配列の値が配列サイズよりも大きい場合、少なくとも1にする必要があります。したがって、すべての配列フィールドの長さが1より大きい場合、その配列にデータがあることを意味します


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.