mongoDB / mongoose: unik jika tidak null

103

Saya bertanya-tanya apakah ada cara untuk memaksa entri koleksi unik tetapi hanya jika entri tidak null . e Contoh skema:

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

'email' dalam hal ini tidak diperlukan tetapi jika 'email' disimpan, saya ingin memastikan bahwa entri ini unik (pada tingkat database).

Entri kosong sepertinya mendapatkan nilai 'null' sehingga setiap entri tanpa email crash dengan opsi 'unik' (jika ada pengguna berbeda tanpa email).

Saat ini saya sedang menyelesaikannya pada level aplikasi, tetapi ingin menyimpan kueri db itu.

Terima kasih

ezmilhouse.dll
sumber

Jawaban:

168

Mulai MongoDB v1.8 + Anda bisa mendapatkan perilaku yang diinginkan untuk memastikan nilai unik tetapi mengizinkan banyak dokumen tanpa bidang dengan menyetel sparseopsi ke true saat menentukan indeks. Seperti dalam:

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

Atau di dalam cangkang:

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

Perhatikan bahwa, indeks jarang unik masih tidak mengijinkan beberapa docs dengan emailbidang dengan nilai dari null, hanya beberapa docs tanpa suatu emailbidang.

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

JohnnyHK
sumber
18
Hebat! Pasti jawaban terbaik untuk pemula seperti saya setelah 1,8! CATATAN: Mongoose tidak akan memperbarui indeks unik Anda menjadi sparse jika Anda hanya menambahkan sparse: true ke skema Anda. Anda harus melepas dan menambahkan kembali indeks. Entah apakah itu yang diharapkan atau bug.
Adam A
8
"Catatan: jika indeks sudah ada di db, itu tidak akan diganti." - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat
Saya rasa ini tidak menjawab pertanyaan dengan benar, karena beberapa dokumen tanpa bidang tertentu tidak sama dengan beberapa dokumen dengan nilai nol pada bidang itu (yang tidak dapat diindeks secara unik).
kako-nawao
1
@ kako-nawao Benar, ini hanya berfungsi untuk dokumen tanpa emailbidang, bukan di tempat yang sebenarnya ada nilai null. Lihat jawaban yang diperbarui.
JohnnyHK
2
Tidak berfungsi dengan bidang yang hilang. Mungkin perilaku diubah di versi mongodb yang lebih baru. Jawaban harus diperbarui.
joniba
44

tl; dr

Ya, mungkin saja memiliki beberapa dokumen dengan bidang disetel ke nullatau tidak ditentukan, sambil menerapkan nilai "aktual" yang unik.

persyaratan :

  • MongoDB v3.2 +.
  • Mengetahui jenis nilai konkret Anda sebelumnya (misalnya, selalu a stringatau objectjika tidak null).

Jika Anda tidak tertarik dengan detailnya, silakan langsung ke implementationbagian ini.

versi yang lebih panjang

Untuk melengkapi jawaban @ Nolan, dimulai dengan MongoDB v3.2 Anda dapat menggunakan indeks unik parsial dengan ekspresi filter.

Ekspresi filter parsial memiliki batasan. Ini hanya dapat mencakup yang berikut:

  • persamaan persamaan (yaitu bidang: nilai atau menggunakan $eqoperator),
  • $exists: true ekspresi,
  • $gt, $gte , $lt, $lteEkspresi,
  • $type ekspresi,
  • $and operator di tingkat atas saja

Ini berarti ungkapan sepele {"yourField"{$ne: null}}tidak dapat digunakan.

Namun, dengan asumsi bahwa bidang Anda selalu menggunakan tipe yang sama , Anda dapat menggunakan $typeekspresi .

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

MongoDB v3.6 menambahkan dukungan untuk menentukan beberapa jenis yang mungkin, yang dapat diteruskan sebagai array:

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

yang berarti bahwa ini memungkinkan nilai menjadi salah satu dari sejumlah tipe ganda jika tidak null .

Oleh karena itu, jika kita ingin mengizinkan emailbidang pada contoh di bawah ini untuk menerima salah satu stringatau, katakanlah, binary datanilai, $typeekspresi yang sesuai adalah:

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

penerapan

luwak

Anda dapat menentukannya dalam skema luwak:

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

atau langsung tambahkan ke koleksi (yang menggunakan driver node.js asli):

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

sopir mongodb asli

menggunakan collection.createIndex

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

cangkang mongodb

menggunakan db.collection.createIndex:

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

Ini akan memungkinkan memasukkan beberapa catatan dengan nullemail, atau tanpa bidang email sama sekali, tetapi tidak dengan string email yang sama.

MasterAM
sumber
Jawaban yang luar biasa. Anda seorang penyelamat.
r3wt
Jawaban ini juga berlaku untuk saya.
Emmanuel NK
1
Sebagian besar jawaban yang diterima untuk pertanyaan ini melibatkan memastikan Anda tidak secara eksplisit menetapkan nilai null ke kunci yang diindeks. Bahwa mereka malah diteruskan tanpa ditentukan. Saya melakukan itu dan masih mendapatkan kesalahan (saat menggunakan uniquedan sparse). Saya memperbarui skema saya dengan jawaban ini, menghapus indeks saya yang ada, dan itu bekerja seperti pesona.
Phil
Dipilih untuk yang satu ini, karena memberikan pengetahuan dan kemungkinan jawaban berdasarkan skenario paling umum yang akan berakhir pada jawaban SO ini di tempat pertama. Terima kasih atas jawaban rinci! : +1:
anothercoder
6

Hanya pembaruan singkat untuk mereka yang meneliti topik ini.

Jawaban yang dipilih akan berfungsi, tetapi Anda mungkin ingin mempertimbangkan untuk menggunakan indeks parsial.

Berubah di versi 3.2: Mulai dari MongoDB 3.2, MongoDB menyediakan opsi untuk membuat indeks parsial. Indeks parsial menawarkan superset dari fungsionalitas indeks renggang. Jika Anda menggunakan MongoDB 3.2 atau yang lebih baru, indeks parsial harus lebih disukai daripada indeks renggang.

Dokumentasi lebih lanjut tentang indeks parsial: https://docs.mongodb.com/manual/core/index-p Partial /

Nolan Garrido
sumber
2

Sebenarnya, hanya dokumen pertama di mana "email" sebagai field tidak ada akan berhasil disimpan. Penyimpanan selanjutnya di mana "email" tidak ada akan gagal saat memberikan kesalahan (lihat potongan kode di bawah). Untuk alasannya, lihat dokumentasi resmi MongoDB sehubungan dengan Indeks Unik dan Kunci yang Hilang di sini di 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"});

Menurut definisi, indeks unik hanya dapat mengizinkan satu nilai untuk disimpan hanya sekali. Jika Anda menganggap null sebagai salah satu nilai tersebut, itu hanya dapat disisipkan sekali! Anda benar dalam pendekatan Anda dengan memastikan dan memvalidasinya di tingkat aplikasi. Begitulah cara melakukannya.

Anda mungkin juga ingin membaca ini http://www.mongodb.org/display/DOCS/Querying+and+nulls

Samyak Bhuta
sumber