mongoDbでは、配列要素をそのインデックスでどのように削除しますか?


97

次の例では、ドキュメントがdb.peopleコレクションにあると想定しています。

第三要素削除する方法の利益のそれのことで、配列をインデックス

{
  "_id" : ObjectId("4d1cb5de451600000000497a"),           
  "name" : "dannie",  
  "interests" : [  
    "guitar",  
    "programming",           
    "gadgets",  
    "reading"  
  ]   
}

これは私の現在の解決策です:

var interests = db.people.findOne({"name":"dannie"}).interests;  
interests.splice(2,1)  
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});

より直接的な方法はありますか?


回答:


138

配列のインデックスによってプル/削除するストレートな方法はありません。実際、これは未解決の問題です。http://jira.mongodb.org/browse/SERVER-1014、投票することができます。

回避策は、$ unsetを使用してから$ pullを使用することです。

db.lists.update({}, {$unset : {"interests.3" : 1 }}) 
db.lists.update({}, {$pull : {"interests" : null}})

更新:一部のコメントで述べたように、このアプローチはアトミックではなく、他のクライアントが2つの操作間で読み取りや書き込みを行うと、競合状態が発生する可能性があります。操作をアトミックにする必要がある場合は、次のことができます。

  • データベースからドキュメントを読み取ります
  • ドキュメントを更新し、配列内のアイテムを削除します
  • データベース内のドキュメントを置き換えます。ドキュメントを読んでから変更されていないことを確認するために、mongo docsで説明されている現在のパターンの更新を使用できます。

33
1年半?これはまだmongodbの状態ですか?
阿部

次の答えはより直接的で、私にとってはうまくいきました。インデックスによる削除ではなく、値による削除です。
2013年

1
@Javier-このソリューションでは、インデックス内の位置をカウントした時刻と設定を解除した時刻の間にデータベースが変更された場合、問題が発生しませんか?
UpTheCreek 2013年

これはアトミックではないため、配列内のnullエントリに対処する準備ができていないコードがある場合は、あいまいな競合状態を引き起こす可能性があります。
Glenn Maynard

3
8年間、このチケットはまだ実装されていません...
Olly John

19

操作の$pull修飾子を使用updateして、配列内の特定の要素を削除できます。クエリを指定した場合、次のようになります。

db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})

また、$pullAllすべてのオカレンスを削除するためにを使用することを検討できます。詳細については、公式ドキュメントページ-http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

これは、要素を削除するための基準としてインデックスを使用しませんが、同様の場合に役立つ場合があります。IMO、配列内の要素のアドレス指定にインデックスを使用することは、mongodbが私の知っているfasのように要素の順序で一貫していないため、あまり信頼できません。


6
彼の要素は問題の配列です。この場合、Mongoは注文に一貫性があります。実際、すべてのドキュメントは実際には順序付けられたコレクションですが、多くのドライバはこの方法でそれらを処理しません。さらに問題を複雑にします。更新操作後にドキュメントを移動する必要がある場合(パディングファクターを超えた増加により)、キーの順序が突然アルファベット順になります(裏では、バイナリ値でソートされていると思いますが、この疑いは配列は文字列ではなく連続した整数であるキーを持つ標準的なドキュメントであるという私の知識に基づいています。
marr75

これにより、unset + pullの問題が回避されますが、辞書を厳密に順序付けする必要があります。ほとんどの言語の辞書は順不同なので、これを実現するのは面倒な場合があります。
Glenn Maynard

@ marr75:リファレンスはありますか?Mongoドキュメントは常に順序を保持することになっています。サブドキュメントを並べ替えると、多くのことが壊れます。
Glenn Maynard

参照はありません。私はこれを2.2の個人的な経験から知っています。更新されたドキュメントと移動されたドキュメントによって導入されたバグを修正しました。過去数か月間、モンゴでの作業をあまり行っていないため、現在のバージョンについて話すことはできません。
marr75 2013

要素の一貫性に関する限り、私の望ましいユースケースはそうです。クライアントはmongoにjsonを要求し、クライアントがjson配列からアイテムを「削除」することを選択した場合、削除するサーバーに配列インデックスを渡します。配列項目の位置が移動した場合、それは奇妙ですが、機能を実装できない理由を説明します
meffect

4

(受け入れられた回答のように)unsetを使用するのではなく、フィールドを一意の値(つまり、NULLではない)に設定してすぐにその値を取得することで、これを解決します。非同期の観点からは少し安全です。これがコードです:

    var update = {};
    var key = "ToBePulled_"+ new Date().toString();
    update['feedback.'+index] = key;
    Venues.update(venueId, {$set: update});
    return Venues.update(venueId, {$pull: {feedback: key}});

うまくいけば、mongoは、おそらく$ pullだけでなく$ pushもサポートするように$ position修飾子を拡張することによって、これに対処します。


3

配列内の各サブドキュメントには、GUID(ObjectIDを使用する傾向があります)フィールド、または自動インクリメントフィールドを使用することをお勧めします。

このGUIDを使用すると、$ pullを発行し、正しいものがプルされることを確認するのは簡単です。他の配列操作についても同様です。


2

以降Mongo 4.4$function集約演算子を使用すると、カスタムJavaScript関数を適用して、MongoDBクエリ言語でサポートされていない動作を実装できます。

たとえば、特定のインデックスにある要素を削除して配列を更新するには、次のようにします。

// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
  { "name": "dannie" },
  [{ $set:
    { "interests":
      { $function: {
          body: function(interests) { interests.splice(2, 1); return interests; },
          args: ["$interests"],
          lang: "js"
      }}
    }
  }]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }

$function 3つのパラメータを取ります:

  • body、これは適用する関数で、そのパラメーターは変更する配列です。この関数は、スプライスを使用してインデックス2の1つの要素を削除するだけです。
  • argsbody関数がパラメータとして受け取るレコードのフィールドを含みます。私たちの場合"$interests"
  • langは、body関数が記述されている言語です。js現在のみ利用可能です。

2

Mongodb 4.2ではこれを行うことができます:

db.example.update({}, [
     {$set: {field: {
           $concatArrays: [ 
                  {$slice: ["$field", P]}, 
                  {$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
           ]
     }}}
]);

Pは、配列から削除する要素のインデックスです。

Pから最後まで削除する場合:

db.example.update({}, [
     {$set: {field: {$slice: ["$field", P]}}
]);

0

nodejsでmongooseを使用して回答を検索している人向け。これが私のやり方です。

exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my  other field in my db

Test.findOneAndUpdate({ tCodigo: codTest },
    {
        '$unset': {
            [inPregunta]: 1, // put the field with [] 
            [inOpciones]: 1,
            [inTipo]: 1
        }
    }).then(()=>{ 
    Test.findOneAndUpdate({ tCodigo: codTest }, {
        '$pull': {
            'tPreguntas.0.pregunta': null,
            'tPreguntas.0.opciones': null,
            'tPreguntas.0.tipo': null
        }
    }).then(testModificado => {
        if (!testModificado) {
            res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
        } else {
            res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
        }
    })}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
 }

よくわからなければこの回答は書き直すことができますが大丈夫だと思います。

これがお役に立てば幸いです。この問題に直面して多くの時間を失いました。


-3

$ pullを使用する代わりに、$ popを使用して、配列の要素をインデックスで削除できます。ただし、インデックスに基づいて削除するには、インデックス位置から1を引く必要があります。

たとえば、インデックス0の要素を削除する場合は-1を使用し、インデックス1の場合は0を使用する必要があります。

3番目の要素(ガジェット)を削除するクエリ:

db.people.update({"name":"dannie"}, {'$pop': {"interests": 1}})

参照用:https : //docs.mongodb.com/manual/reference/operator/update/pop/


3
リンクされたドキュメントによると、$pop配列から最初または最後の要素のみを削除できます。この回答のクエリでは、3番目の要素は削除されません。
Travis G.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.