web-dev-qa-db-ja.com

mongoDB / mongoose:nullでない場合は一意

一意のコレクションエントリを強制する方法があるかどうか疑問に思いましたただし、エントリがnullでない場合のみ。 eサンプルスキーマ:

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

この場合の「電子メール」は必須ではありませんが、「電子メール」が保存されている場合は、このエントリが一意であることを確認する必要があります(データベースレベル)。

空のエントリは値「null」を取得するように見えるため、「unique」オプションでメールがクラッシュしないすべてのエントリ(メールを持たない別のユーザーがいる場合)。

今、私はアプリケーションレベルでそれを解決していますが、そのdbクエリを保存したいです。

tHX

84
ezmilhouse

MongoDB v1.8以降では、インデックスを定義するときにsparseオプションをtrueに設定することにより、一意の値を保証するがフィールドなしで複数のドキュメントを許可するという望ましい動作を得ることができます。次のように:

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

またはシェルで:

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

一意のスパースインデックスでは、emailvalueを持つnullフィールドを持つ複数のドキュメントは許可されません。 、複数のドキュメントのみなしemailフィールド。

http://docs.mongodb.org/manual/core/index-sparse/ を参照してください

139
JohnnyHK

tl; dr

はい、一意の「実際の」値を適用しながら、フィールドがnullに設定されているか定義されていない複数のドキュメントを作成することができます。

要件

  • MongoDB v3.2 +。
  • 事前に具体的な値のタイプを知っている(たとえば、stringでない場合は常にobjectまたはnull)。

詳細に興味がない場合は、implementationセクションに進んでください。

より長いバージョン

@Nolanの答えを補足するために、MongoDB v3.2以降では、フィルター式で部分的な一意のインデックスを使用できます。

部分フィルター式には制限があります。次のもののみを含めることができます。

  • 等式(つまり、フィールド:値または$eq演算子)、
  • $exists: true式、
  • $gt$gte$lt$lte式、
  • $type式、
  • $and演算子は最上位のみ

これは、簡単な式{"yourField"{$ne: null}}は使用できません。

ただし、フィールドが常に同じタイプを使用すると仮定すると、 $type expression

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

MongoDB v3.6では、配列として渡すことができる複数の可能なタイプを指定するサポートが追加されました。

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

つまり、null以外の場合、値は複数の型のいずれかになります。

したがって、以下の例のemailフィールドにstringまたは、たとえばbinary data値、適切な$type式は次のようになります。

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

実装

マングース

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

または、コレクションに直接追加します(ネイティブnode.jsドライバーを使用):

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

ネイティブmongodbドライバー

collection.createIndex

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

mongodbシェル

db.collection.createIndex

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

これにより、複数のレコードをnull電子メールで挿入したり、電子メールフィールドをまったく使用せずに同じ電子メール文字列を挿入したりすることができます。

29
MasterAM

このトピックを調査している人たちへの簡単な更新。

選択した回答は機能しますが、代わりに部分インデックスの使用を検討することをお勧めします。

バージョン3.2で変更:MongoDB 3.2以降、MongoDBには部分インデックスを作成するオプションがあります。部分インデックスは、疎インデックスの機能のスーパーセットを提供します。 MongoDB 3.2以降を使用している場合、スパースインデックスよりも部分インデックスを優先する必要があります。

部分インデックスに関するその他のドキュメント: https://docs.mongodb.com/manual/core/index-partial/

4
Nolan Garrido

実際、フィールドとして「email」が存在しない最初のドキュメントのみが正常に保存されます。 「電子メール」が存在しない場所での後続の保存は、エラーを発生させながら失敗します(以下のコードスニペットを参照)。理由については、ここで http://www.mongodb.org/display/DOCS/Indexes#Indexes-UniqueIndexes でユニークインデックスとキーの欠落に関するMongoDB公式ドキュメントをご覧ください。

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

定義上、一意のインデックスでは、1つの値のみを1回しか保存できません。 nullをそのような値の1つと見なす場合、挿入できるのは1回だけです!アプリケーションレベルで確実に検証することで、アプローチは正しいです。それはそれができる方法です。

これを読むこともできます http://www.mongodb.org/display/DOCS/Querying+and+nulls

2
Samyak Bhuta