配列サイズが1より大きいドキュメントのクエリ


664

次の形式のドキュメントを含むMongoDBコレクションがあります。

{
  "_id" : ObjectId("4e8ae86d08101908e1000001"),
  "name" : ["Name"],
  "zipcode" : ["2223"]
}
{
  "_id" : ObjectId("4e8ae86d08101908e1000002"),
  "name" : ["Another ", "Name"],
  "zipcode" : ["2224"]
}

現在、特定の配列サイズに一致するドキュメントを取得できます。

db.accommodations.find({ name : { $size : 2 }})

これにより、name配列に2つの要素があるドキュメントが正しく返されます。ただし、フィールドの配列サイズが2より大きい$gtすべてのドキュメントを返すコマンドを実行することはできませんname

db.accommodations.find({ name : { $size: { $gt : 1 } }})

name1より大きいサイズの配列を持つすべてのドキュメントを選択するにはどうすればよいですか(できれば現在のデータ構造を変更する必要がない)。


3
MongoDBの新しいバージョンには$ size演算子があります。@tobiaの回答をチェックしてください
AlbertEngelB 14年

4
実際の解決策:FooArray:{$ gt:{$ size: 'length'}}-> lenghtは任意の数にすることができます
Sergi Nadal

回答:


489

更新:

mongodbバージョン2.2以降の場合@ JohnnyHKが別の回答で説明しているこれを行うためのより効率的な方法。


1. $ whereを使用する

db.accommodations.find( { $where: "this.name.length > 1" } );

だが...

JavaScriptは、このページにリストされているネイティブオペレーターよりも実行速度は遅くなりますが、非常に柔軟性があります。詳細については、サーバー側の処理ページを参照してください。

2. 追加のフィールドを作成しNamesArrayLength、名前の配列の長さで更新してから、クエリで使用します。

db.accommodations.find({"NamesArrayLength": {$gt: 1} });

それはより良い解決策であり、はるかに速く動作します(それにインデックスを作成できます)。


4
ありがとうございます。名前のないドキュメントがいくつかあるので、クエリを次のように変更する必要がありました:db.accommodations.find({$ where: "if(this.name && this.name.length> 1){return this ;} "});
エムソン、

どういたしまして。はい、JavaScriptはで使用できます$where。非常に柔軟です。
Andrew Orsich、2011年

8
@emson {"name":{$ exists:1}、$ where: "this.name.lenght> 1"} ...のようなものを実行する方が速いと思います。遅いJavaScriptスクリプトの部分を最小化します。私はそれが機能し、$ existsの方が優先順位が高いと思います。
nairbv

1
JavaScriptをクエリに埋め込むことができるとは思いもしませんでした。jsonは扱いにくい場合があります。これらのクエリの多くは手動で1回のみ入力されるため、最適化は必要ありません。私はこのトリックを頻繁に使用します+1
pferrel 14年

3
配列から要素を追加/削除した後、「NamesArrayLength」の数を更新する必要があります。これは単一のクエリで実行できますか?または、2つのクエリが必要です。1つは配列の更新用で、もう1つはカウントの更新用です。
WarLord、2016

1329

MongoDB 2.2以降でこれを行うには、クエリオブジェクトキーで数値配列インデックスを使用できるので、より効率的な方法があります。

// Find all docs that have at least two name array elements.
db.accommodations.find({'name.1': {$exists: true}})

このクエリは、部分的なフィルター式を使用するインデックスでサポートできます(3.2以上が必要)。

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);

16
誰かがこれを索引付けする方法を説明してくれませんか?
ベン

26
これがどれほど効果的であるか、また、このソリューションを見つけるためにあなたが考えていた「箱から出してすぐに」ということに本当に感銘を受けました。これは2.6でも動作します。
earthmeLon 14

2
3.0でも動作します。これを見つけてくれてありがとう。
pikanezi

1
@Dims違いはありません、本当に:{'Name Field.1': {$exists: true}}
JohnnyHK 2016

9
@JoseRicardoBustosM。これにより、少なくとも 1つの要素をname含むドキュメントが見つかりますが、OPは1 より大きい要素を探していました
JohnnyHK

128

解釈された$where句を使用しないため、これはあなたの質問に答える最も速いクエリだと思います。

{$nor: [
    {name: {$exists: false}},
    {name: {$size: 0}},
    {name: {$size: 1}}
]}

これは、「名前のないもの(存在しないか空の配列)または名前が1つだけのドキュメントを除くすべてのドキュメント」を意味します。

テスト:

> db.test.save({})
> db.test.save({name: []})
> db.test.save({name: ['George']})
> db.test.save({name: ['George', 'Raymond']})
> db.test.save({name: ['George', 'Raymond', 'Richard']})
> db.test.save({name: ['George', 'Raymond', 'Richard', 'Martin']})
> db.test.find({$nor: [{name: {$exists: false}}, {name: {$size: 0}}, {name: {$size: 1}}]})
{ "_id" : ObjectId("511907e3fb13145a3d2e225b"), "name" : [ "George", "Raymond" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225c"), "name" : [ "George", "Raymond", "Richard" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225d"), "name" : [ "George", "Raymond", "Richard", "Martin" ] }
>

9
@virenわからない。これは確かにJavascriptソリューションよりも優れていましたが、新しいMongoDBではおそらく使用する必要があります{'name.1': {$exists: true}}
Tobia

@Tobia私の最初の使用は$ existsのみでしたが、実際にはテーブル全体のスキャンを使用しているため、非常に低速です。db.test.find({"name": "abc"、 "d.5":{$ exists:true}、 "d.6":{$ exists:true}}) "nReturned":46525、 "executionTimeMillis ":167289、" totalKeysExamined ":10990840、" totalDocsExamined ":10990840、" inputStage ":{" stage ":" IXSCAN "、" keyPattern ":{" name ":1、" d ":1}、" indexName " : "name_1_d_1"、 "direction": "forward"、 "indexBounds":{"name":["[\" abc \ "、\" abc \ "]"]、 "d":["[MinKey、MaxKey ] "]}}テーブル全体をスキャンした場合。

以下のような他の選択肢(推奨する答えを更新するためにいいだろう'name.1': {$exists: true}}、ともこのため、「1」のためにハードコードされたが、任意またはパラメトリック最小の配列の長さに比例しません。
ダンDascalescu

1
これは速いかもしれませんが、リスト> Nを探している場合、バラバラになります。Nは小さくありません。
Brandon Hill

62

集計も使用できます。

db.accommodations.aggregate(
[
     {$project: {_id:1, name:1, zipcode:1, 
                 size_of_name: {$size: "$name"}
                }
     },
     {$match: {"size_of_name": {$gt: 1}}}
])

//トランジットドキュメントに「size_of_name」を追加し、それを使用して名前のサイズをフィルタリングします


このソリューションは、@ JohnnyHKと共に最も一般的なソリューションであり、あらゆる配列サイズに使用できます。
2015

プロジェクション内で「size_of_name」を使用する場合、どうすればよいですか?実際、私はその値が$ sliceに等しいプロジェクション内で$ sliceを使用したいと思います:[0、 "size_of_name"-skip] ??
Sudhanshu Gaur

44

このようなことをしてみてください:

db.getCollection('collectionName').find({'ArrayName.1': {$exists: true}})

1は数値です。50より大きいレコードをフェッチする場合は、ArrayName.50を実行してください。ありがとうございます。


2
同じ答えが3年前に出されました
Dan Dascalescu

私は将来の出身です。これを高く評価していたでしょう。このソリューションは、要素が上記の位置に存在するかどうかをチェックすることで機能します。したがって、コレクションはその数以上でなければなりません。
MarAvFe

クエリ内に "ArrayName。<some_num>"のような動的な数値を入れることができますか?
Sahil Mahajan

はい、任意の数を使用できます。Nより大きいレコードをフェッチする場合は、nを渡します。
Aman Goel

36

上記のどれも私にとってはうまくいきませんでした。これは私がそれを共有しているのでそれをしました:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )

javascriptは、mongodbが提供するネイティブオペレーターよりも実行速度が遅くなりますが、非常に柔軟です。参照:stackoverflow.com/a/7811259/2893073、したがって、最終的なソリューションは次のとおりです:stackoverflow.com/a/15224544/2893073
Eddy

26

あなたは使用することができます$ exprの定期的なクエリで集計関数を使用する(3.6のmongoバージョン演算子を)。

比較query operatorsaggregation comparison operators

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})

$nameサブドキュメントである配列の代わりに、たとえば「person」レコードでどのように渡しpassport.stampsますか?私は、様々な引用の組み合わせを試してみましたが、私は得ます"The argument to $size must be an array, but was of type: string/missing"
Dan Dascalescu

3
@DanDascalescuスタンプがすべてのドキュメントに存在するわけではないようです。スタンプが存在しない場合、ifNullを使用して空の配列を出力できます。のようなものdb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram


22

MongoDB 3.6には$ expr https://docs.mongodb.com/manual/reference/operator/query/expr/が含まれてい ます

$ exprを使用して、$ match内の式を評価したり、検索したりできます。

{ $match: {
           $expr: {$gt: [{$size: "$yourArrayField"}, 0]}
         }
}

または見つける

collection.find({$expr: {$gte: [{$size: "$yourArrayField"}, 0]}});

1
正しいが、これは重複した答えです。@ user2683814によるstackoverflow.com/a/48410837/2424641を参照
SteveB '12

13

私はこの解決策を見つけました、特定の長さよりも長い配列フィールドを持つアイテムを見つけるために

db.allusers.aggregate([
  {$match:{username:{$exists:true}}},
  {$project: { count: { $size:"$locations.lat" }}},
  {$match:{count:{$gt:20}}}
])

最初の$ match集約は、すべてのドキュメントに対してtrueである引数を使用します。空白の場合、私は

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"

これは本質的に同じ答えがこの1、2年先に提供します。
Dan Dascalescu 2018

1

私は古い質問を知っていますが、検索で$ gteと$ sizeを使用してこれを試しています。find()の方が速いと思います。

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})

-5

上記はすべての作業に答えますが、最初に試みたのは正しい方法でしたが、構文が逆になっています( "$ size"と "$ gt"を切り替えます)。

正しい:

db.collection.find({items: {$gt: {$size: 1}}})

不正解:

db.collection.find({items: {$size: {$gt: 1}}})

1
なぜこれほど多くの反対投票が行われるのかわかりません。
ジェイクストークス

投票はしませんでしたが、機能しません(v4.2)。
Evgeni Nabokov

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