mongoDB / mongoose: duy nhất nếu không phải null


103

Tôi đã tự hỏi liệu có cách nào để buộc một mục nhập bộ sưu tập duy nhất nhưng chỉ khi mục nhập không rỗng . e Lược đồ mẫu:

var UsersSchema = new Schema({
    name  : {type: String, trim: true, index: true, required: true},
    email : {type: String, trim: true, index: true, unique: true}
});

'email' trong trường hợp này là không bắt buộc nhưng nếu 'email' được lưu, tôi muốn đảm bảo rằng mục nhập này là duy nhất (ở cấp độ cơ sở dữ liệu).

Các mục nhập trống dường như nhận giá trị 'null' vì vậy mọi mục nhập sẽ không có email nào bị lỗi với tùy chọn 'duy nhất' (nếu có một người dùng khác không có email).

Hiện tại, tôi đang giải quyết nó ở cấp ứng dụng, nhưng rất muốn lưu truy vấn db đó.

cám ơn

Câu trả lời:


168

Kể từ MongoDB v1.8 +, bạn có thể có được hành vi mong muốn là đảm bảo các giá trị duy nhất nhưng cho phép nhiều tài liệu không có trường bằng cách đặt sparsetùy chọn thành true khi xác định chỉ mục. Như trong:

email : {type: String, trim: true, index: true, unique: true, sparse: true}

Hoặc trong shell:

db.users.ensureIndex({email: 1}, {unique: true, sparse: true});

Lưu ý rằng một chỉ mục thưa thớt, duy nhất vẫn không cho phép nhiều tài liệu với một emailtrường có giá trịnull, chỉ nhiều tài liệu khôngemailtrường.

Xem http://docs.mongodb.org/manual/core/index-sparse/


18
Tuyệt vời! Chắc chắn là câu trả lời tốt nhất cho những người mới như tôi sau 1.8! LƯU Ý: Mongoose sẽ không cập nhật chỉ mục duy nhất của bạn để trở nên thưa thớt nếu bạn chỉ thêm một chỉ mục thưa thớt: true vào lược đồ của mình. Bạn phải bỏ và thêm lại chỉ mục. Dunno nếu đó là mong đợi hoặc một lỗi.
Adam A

8
"Lưu ý: nếu chỉ mục đã tồn tại trên db, nó sẽ không được thay thế." - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat

Tôi không nghĩ rằng điều này trả lời câu hỏi một cách chính xác, vì một số tài liệu không có trường cụ thể không giống với một số tài liệu có giá trị rỗng trên trường đó (không thể được lập chỉ mục duy nhất).
kako-nawao

1
@ kako-nawao Đúng vậy, nó chỉ hoạt động với những tài liệu không có emailtrường, không phải nơi mà nó thực sự có giá trị null. Xem câu trả lời cập nhật.
JohnnyHK

2
Không hoạt động với các trường bị thiếu. Có thể hành vi đã được thay đổi trong các phiên bản mongodb sau này. Câu trả lời cần được cập nhật.
joniba

44

tl; dr

Có, có thể có nhiều tài liệu với một trường được đặt thành nullhoặc không được xác định, trong khi thực thi các giá trị "thực tế" duy nhất.

yêu cầu :

  • MongoDB v3.2 +.
  • Biết trước (các) loại giá trị cụ thể của bạn (ví dụ: luôn luôn là một stringhoặc objectkhi không null).

Nếu bạn không quan tâm đến chi tiết, vui lòng bỏ qua implementationphần này.

phiên bản dài hơn

Để bổ sung câu trả lời của @ Nolan, bắt đầu với MongoDB v3.2, bạn có thể sử dụng chỉ mục duy nhất một phần với biểu thức bộ lọc.

Biểu thức bộ lọc từng phần có những hạn chế. Nó chỉ có thể bao gồm những điều sau:

  • biểu thức bình đẳng (tức là trường: giá trị hoặc sử dụng $eqtoán tử),
  • $exists: true biểu hiện,
  • $gt, $gte, $lt, $lteBiểu thức,
  • $type biểu thức,
  • $and chỉ ở cấp cao nhất

Điều này có nghĩa là {"yourField"{$ne: null}}không thể sử dụng biểu thức tầm thường .

Tuy nhiên, giả sử rằng trường của bạn luôn sử dụng cùng một kiểu , bạn có thể sử dụng một $typebiểu thức .

{ field: { $type: <BSON type number> | <String alias> } }

MongoDB v3.6 đã thêm hỗ trợ để chỉ định nhiều kiểu có thể có, có thể được chuyển dưới dạng một mảng:

{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }

có nghĩa là nó cho phép giá trị là bất kỳ trong số nhiều loại khi không null.

Do đó, nếu chúng ta muốn cho phép emailtrường trong ví dụ dưới đây chấp nhận một trong hai giá trị stringhoặc, giả sử binary data, một $typebiểu thức thích hợp sẽ là:

{email: {$type: ["string", "binData"]}}

thực hiện

cầy mangut

Bạn có thể chỉ định nó trong lược đồ mongoose:

const UsersSchema = new Schema({
  name: {type: String, trim: true, index: true, required: true},
  email: {
    type: String, trim: true, index: {
      unique: true,
      partialFilterExpression: {email: {$type: "string"}}
    }
  }
});

hoặc trực tiếp thêm nó vào bộ sưu tập (sử dụng trình điều khiển node.js gốc):

User.collection.createIndex("email", {
  unique: true,
  partialFilterExpression: {
    "email": {
      $type: "string"
    }
  }
});

trình điều khiển mongodb gốc

sử dụng collection.createIndex

db.collection('users').createIndex({
    "email": 1
  }, {
    unique: true,
    partialFilterExpression: {
      "email": {
        $type: "string"
      }
    }
  },
  function (err, results) {
    // ...
  }
);

vỏ mongodb

sử dụng db.collection.createIndex:

db.users.createIndex({
  "email": 1
}, {
  unique: true, 
  partialFilterExpression: {
    "email": {$type: "string"}
  }
})

Điều này sẽ cho phép chèn nhiều bản ghi với một nullemail hoặc hoàn toàn không có trường email, nhưng không phải với cùng một chuỗi email.


Câu trả lời tuyệt vời. Bạn là một vị cứu tinh.
r3wt

Câu trả lời này đã làm điều đó cho tôi.
Emmanuel NK

1
Hầu hết các câu trả lời được chấp nhận cho câu hỏi này liên quan đến việc đảm bảo rằng bạn không đặt giá trị null một cách rõ ràng cho các khóa được lập chỉ mục của mình. Thay vào đó chúng được chuyển qua không xác định. Tôi đã làm điều đó và vẫn gặp lỗi (trong khi sử dụng uniquesparse). Tôi đã cập nhật lược đồ của mình với câu trả lời này, bỏ chỉ mục hiện có của tôi và nó hoạt động như một sự quyến rũ.
Phil

Đã bỏ phiếu cho câu trả lời này, vì nó cung cấp kiến ​​thức và các câu trả lời có thể dựa trên các tình huống phổ biến nhất, người ta sẽ kết thúc câu trả lời SO này ngay từ đầu. Cảm ơn các câu trả lời chi tiết! : +1:
anothercoder

6

Chỉ là một bản cập nhật nhanh chóng cho những người nghiên cứu chủ đề này.

Câu trả lời đã chọn sẽ hoạt động, nhưng bạn có thể muốn xem xét sử dụng các chỉ mục từng phần.

Đã thay đổi trong phiên bản 3.2: Bắt đầu từ MongoDB 3.2, MongoDB cung cấp tùy chọn để tạo chỉ mục từng phần. Các chỉ mục một phần cung cấp một tập hợp siêu chức năng của các chỉ mục thưa thớt. Nếu bạn đang sử dụng MongoDB 3.2 trở lên, các chỉ mục một phần nên được ưu tiên hơn các chỉ mục thưa thớt.

Thêm doco về chỉ mục một phần: https://docs.mongodb.com/manual/core/index-partial/


2

Trên thực tế, chỉ tài liệu đầu tiên không tồn tại trường "email" sẽ được lưu thành công. Các lần lưu tiếp theo trong đó "email" không có mặt sẽ không thành công khi báo lỗi (xem đoạn mã bên dưới). Vì lý do, hãy xem tài liệu chính thức của MongoDB liên quan đến Chỉ mục duy nhất và Khóa bị thiếu ở đây tại http://www.mongodb.org/display/DOCS/Indexes#Indexes-UniqueIndexes .

  // NOTE: Code to executed in mongo console.

  db.things.ensureIndex({firstname: 1}, {unique: true});
  db.things.save({lastname: "Smith"});

  // Next operation will fail because of the unique index on firstname.
  db.things.save({lastname: "Jones"});

Theo định nghĩa, chỉ mục duy nhất chỉ có thể cho phép một giá trị được lưu trữ một lần duy nhất. Nếu bạn coi null là một trong những giá trị như vậy, nó chỉ có thể được chèn một lần! Bạn đã đúng trong cách tiếp cận của mình bằng cách đảm bảo và xác thực nó ở cấp ứng dụng. Đó là cách nó có thể được thực hiện.

Bạn cũng có thể muốn đọc http://www.mongodb.org/display/DOCS/Querying+and+nulls này

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.