Trong mongoDb, làm cách nào để xóa một phần tử mảng theo chỉ mục của nó?


97

Trong ví dụ sau, giả sử tài liệu nằm trong bộ sưu tập db.people .

Làm cách nào để xóa phần tử thứ 3 của mảng sở thích bằng chỉ mục của nó ?

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

Đây là giải pháp hiện tại của tôi:

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

Có cách nào trực tiếp hơn không?


Câu trả lời:


138

Không có cách kéo / loại bỏ thẳng bằng chỉ mục mảng. Trên thực tế, đây là một vấn đề mở http://jira.mongodb.org/browse/SERVER-1014 , bạn có thể bỏ phiếu cho nó.

Cách giải quyết là sử dụng $ unset và sau đó là $ pull:

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

Cập nhật: như đã đề cập trong một số nhận xét, cách tiếp cận này không phải là nguyên tử và có thể gây ra một số điều kiện chạy đua nếu các máy khách khác đọc và / hoặc ghi giữa hai hoạt động. Nếu chúng ta cần hoạt động trở thành nguyên tử, chúng ta có thể:

  • Đọc tài liệu từ cơ sở dữ liệu
  • Cập nhật tài liệu và xóa mục trong mảng
  • Thay thế tài liệu trong cơ sở dữ liệu. Để đảm bảo tài liệu không thay đổi kể từ khi chúng tôi đọc nó, chúng tôi có thể sử dụng bản cập nhật nếu mẫu hiện tại được mô tả trong tài liệu mongo

33
Đã một năm rưỡi? Đây có còn là trạng thái của mọi thứ với mongodb không?
Abe

Câu trả lời tiếp theo là trực tiếp hơn và phù hợp với tôi. Mặc dù nó không loại bỏ theo chỉ mục, mà là loại bỏ theo giá trị.
vish

1
@Javier - giải pháp này có khiến bạn không gặp vấn đề nếu db thay đổi giữa thời gian bạn đếm vị trí trong chỉ mục và thời gian bạn chưa đặt?
UpTheCreek

Đây không phải là nguyên tử, vì vậy nó có khả năng gây ra các điều kiện chủng tộc khó hiểu nếu bạn có bất kỳ mã nào không được chuẩn bị để đối phó với mục nhập null trong mảng.
Glenn Maynard

3
8 năm và vé này vẫn chưa được thực hiện ...
Olly John

19

Bạn có thể sử dụng công cụ $pullsửa đổi updatehoạt động để xóa một phần tử cụ thể trong một mảng. Trong trường hợp bạn cung cấp, một truy vấn sẽ giống như sau:

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

Ngoài ra, bạn có thể xem xét sử dụng $pullAllđể loại bỏ tất cả các lần xuất hiện. Tìm hiểu thêm về điều này trên trang tài liệu chính thức - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

Điều này không sử dụng chỉ mục làm tiêu chí để xóa một phần tử, nhưng vẫn có thể hữu ích trong các trường hợp tương tự như trường hợp của bạn. IMO, việc sử dụng các chỉ mục để định địa chỉ các phần tử bên trong một mảng không đáng tin cậy lắm vì mongodb không nhất quán về thứ tự các phần tử như tôi biết.


6
Phần tử của anh ta là một mảng trong câu hỏi. Mongo nhất quán theo thứ tự trong trường hợp này. Trên thực tế, tất cả các tài liệu thực sự là bộ sưu tập được đặt hàng nhưng nhiều trình điều khiển không xử lý chúng theo cách này. Vấn đề phức tạp hơn nữa, nếu một tài liệu phải được di chuyển sau một thao tác cập nhật (do sự phát triển vượt quá yếu tố đệm), thứ tự của các khóa đột nhiên trở thành thứ tự bảng chữ cái (dưới bìa, tôi nghĩ rằng chúng được sắp xếp theo giá trị nhị phân của chúng, điều này nghi ngờ là dựa trên kiến ​​thức của tôi rằng mảng là khá nhiều tài liệu chuẩn với các khóa là các số nguyên liên tiếp thay vì các chuỗi).
Marr75,

Điều này giúp tránh các vấn đề về unset + pull, nhưng yêu cầu từ điển của bạn phải được sắp xếp nghiêm ngặt. Từ điển ở hầu hết các ngôn ngữ không có thứ tự, vì vậy có thể là một điều khó khăn khi thực hiện điều này.
Glenn Maynard

@ marr75: Bạn có tham khảo không? Các tài liệu Mongo luôn phải giữ trật tự, và nếu nó từng được sắp xếp lại các tài liệu phụ thì rất nhiều thứ sẽ bị hỏng.
Glenn Maynard

Tôi không có tài liệu tham khảo. Tôi biết điều này từ kinh nghiệm cá nhân với 2.2, có các lỗi đã sửa được giới thiệu bởi các tài liệu được cập nhật và di chuyển. Tôi đã không thực hiện nhiều công việc mongo trong vài tháng qua nên tôi không thể nói với các phiên bản hiện tại khác.
marr75

Về tính nhất quán của phần tử, trường hợp sử dụng mong muốn của tôi là. Một máy khách yêu cầu json từ mongo và sau đó nếu máy khách chọn nói, 'xóa' một mục khỏi mảng json, nó sẽ chuyển chỉ mục mảng tới máy chủ để xóa. nếu vị trí mục mảng di chuyển, đó sẽ là kỳ lạ, nhưng có thể giải thích lý do tại sao họ không thể thực hiện các tính năng
meffect

4

Thay vì sử dụng unset (như trong câu trả lời được chấp nhận), tôi giải quyết điều này bằng cách đặt trường thành một giá trị duy nhất (tức là không phải NULL) và sau đó ngay lập tức kéo giá trị đó. An toàn hơn một chút từ quan điểm không đồng bộ. Đây là mã:

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

Hy vọng mongo sẽ giải quyết vấn đề này, có lẽ bằng cách mở rộng công cụ sửa đổi $ position để hỗ trợ $ pull cũng như $ push.


3

Tôi khuyên bạn nên sử dụng trường GUID (Tôi có xu hướng sử dụng ObjectID) hoặc trường tự động tăng dần cho mỗi tài liệu con trong mảng.

Với GUID này, thật dễ dàng để đưa ra một lần kéo $ và chắc chắn rằng cái chính xác sẽ được kéo. Tương tự với các phép toán mảng khác.


2

Bắt đầu từ đầu Mongo 4.4, $functiontoán tử tổng hợp cho phép áp dụng một hàm javascript tùy chỉnh để triển khai hành vi không được Ngôn ngữ truy vấn MongoDB hỗ trợ.

Ví dụ: để cập nhật một mảng bằng cách xóa một phần tử tại một chỉ mục nhất định:

// { "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 có 3 tham số:

  • body, là hàm để áp dụng, có tham số là mảng để sửa đổi. Chức năng ở đây chỉ đơn giản là sử dụng splice để loại bỏ 1 phần tử ở chỉ mục 2.
  • args, chứa các trường từ bản ghi mà bodyhàm nhận làm tham số. Trong trường hợp của chúng tôi "$interests".
  • lang, là ngôn ngữ mà bodyhàm được viết. Chỉ jslà hiện nay.

2

trong Mongodb 4.2, bạn có thể làm điều này:

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

P là chỉ số của phần tử bạn muốn xóa khỏi mảng.

Nếu bạn muốn xóa khỏi P cho đến cuối:

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

0

Đối với những người đang tìm kiếm câu trả lời bằng cách sử dụng mongoose với nodejs. Đây là cách tôi làm điều đó.

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 }); });
 }

Tôi có thể viết lại câu trả lời này nếu nó không hiểu rõ lắm, nhưng tôi nghĩ là ổn.

Hy vọng điều này sẽ giúp bạn, tôi đã mất rất nhiều thời gian đối mặt với vấn đề này.


-3

Thay vì sử dụng $ pull, chúng ta có thể sử dụng $ pop để xóa các phần tử trong mảng theo chỉ mục của nó. Nhưng bạn nên trừ đi 1 từ vị trí chỉ mục để xóa dựa trên chỉ mục.

Ví dụ: nếu bạn muốn xóa phần tử trong chỉ mục 0, bạn nên sử dụng -1, đối với chỉ mục 1, bạn nên sử dụng 0, v.v.

Truy vấn để loại bỏ yếu tố thứ 3 (tiện ích):

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

để tham khảo: https://docs.mongodb.com/manual/reference/operator/update/pop/


3
Theo tài liệu được liên kết, $popchỉ có thể xóa phần tử đầu tiên hoặc cuối cùng khỏi mảng. Truy vấn trong câu trả lời này không loại bỏ phần tử thứ ba.
Travis G.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.