MongoDB リファレンスの使用

MongoDBBeginner
オンラインで実践に進む

はじめに

この実験では、MongoDB のリファレンスを使用してデータの関係性をモデル化する方法を学びます。authors コレクションと books コレクションを持つシンプルな図書館管理システムを構築します。実践的なステップを通して、ドキュメントの作成、リファレンスを使用したそれらのリンク、コレクションをまたいだ関連データのクエリ、これらのリファレンスの更新、そしてインデックスを使用したクエリパフォーマンスの向上を学びます。この実験は、MongoDB におけるデータモデリングの実践的な基礎を提供します。

コレクションの作成とドキュメントの参照

このステップでは、データベースを設定し、authorsbooks の 2 つのコレクションを作成します。書籍を著者にリンクすることで、ドキュメントリファレンスの基本的な概念を学びます。

まず、MongoDB Shell を開きます。このインタラクティブなシェルで、すべてのデータベースコマンドを実行します。

mongosh

シェルに入ると、test> プロンプトが表示されます。library_db という新しいデータベースに切り替えます。データベースが存在しない場合、MongoDB は最初にデータを保存する際に作成します。

use library_db

次に、最初の著書を作成します。authors コレクションにドキュメントを挿入します。後で参照しやすくするために、この著書にはカスタム _id を指定します。

db.authors.insertOne({
    _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    name: "Jane Austen",
    nationality: "British",
    birthYear: 1775
})

次に、books コレクションにドキュメントを挿入します。author_id フィールドには、作成した著書の ObjectId が含まれています。これがリファレンスを作成する方法です。

db.books.insertOne({
    title: "Pride and Prejudice",
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1813,
    genre: "Classic Literature"
})

これで 1 対 1 の関係が作成されました。これを検証するために、作成したばかりのドキュメントを取得できます。

まず、著書を見つけます。

db.authors.findOne({ name: "Jane Austen" })

出力例:

{
  _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
  name: 'Jane Austen',
  nationality: 'British',
  birthYear: 1775
}

次に、書籍を見つけ、著書にリンクする author_id フィールドを確認します。

db.books.findOne({ title: "Pride and Prejudice" })

出力例:

{
  _id: ObjectId("..."),
  title: 'Pride and Prejudice',
  author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
  published: 1813,
  genre: 'Classic Literature'
}

次のステップのために mongosh シェルに留まることができます。

複数のドキュメントをリンクする

著者は通常、複数の本を書きます。このステップでは、単一の「親」ドキュメント(著者)に複数の「子」ドキュメント(書籍)をリンクする方法を学びます。これは 1 対多の関係を示しています。

mongosh シェルで作業を続けます。ジェーン・オースティンの本をさらに 2 冊追加しましょう。insertMany コマンドを使用して、複数のドキュメントを一度に挿入します。新しい 2 冊の本は、同じ author_id を参照します。

db.books.insertMany([
    {
        title: "Sense and Sensibility",
        author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
        published: 1811,
        genre: "Classic Literature"
    },
    {
        title: "Emma",
        author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
        published: 1815,
        genre: "Classic Literature"
    }
])

これでジェーン・オースティンの本がデータベースに 3 冊になりました。find() メソッドを使用して、author_id でフィルタリングし、それらすべてを取得します。

db.books.find({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1") })

出力例:

[
  {
    _id: ObjectId("..."),
    title: 'Pride and Prejudice',
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1813,
    genre: 'Classic Literature'
  },
  {
    _id: ObjectId("..."),
    title: 'Sense and Sensibility',
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1811,
    genre: 'Classic Literature'
  },
  {
    _id: ObjectId("..."),
    title: 'Emma',
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
    published: 1815,
    genre: 'Classic Literature'
  }
]

countDocuments を使用すると、特定の著書に関連付けられている本の数を素早く数えることもできます。

db.books.countDocuments({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1") })

出力例:

3

このシンプルなクエリは、リンクされたドキュメントの数を効率的に確認します。

$lookup を使用したコレクション横断クエリ

これまでは、既知の author_id を使用して書籍を取得してきました。より強力なアプローチは、単一のクエリで両方のコレクションのデータを結合することです。このステップでは、$lookup 集計ステージを使用して、books コレクションから authors コレクションへの左外部結合を実行します。

まず、クエリをより面白くするために、別の著者と書籍を追加します。

db.authors.insertOne({
    _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
    name: "Charles Dickens",
    nationality: "British",
    birthYear: 1812
})
db.books.insertOne({
    title: "Oliver Twist",
    author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
    published: 1837,
    genre: "Historical Fiction"
})

次に、集計パイプラインを構築します。このクエリは books コレクションから開始し、各書籍に対応する著者を「検索」します。

db.books.aggregate([
    {
        $lookup: {
            from: "authors",
            localField: "author_id",
            foreignField: "_id",
            as: "author_details"
        }
    }
])

$lookup ステージには次のフィールドがあります。

  • from: "authors": 結合するコレクションを指定します。
  • localField: "author_id": 入力ドキュメント(books から)のフィールドです。
  • foreignField: "_id": 「from」コレクション(authors から)のドキュメントのフィールドです。
  • as: "author_details": 入力ドキュメントに追加される新しい配列フィールドの名前です。

出力例(1 つのドキュメントの場合):

{
  _id: ObjectId("..."),
  title: 'Pride and Prejudice',
  author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
  published: 1813,
  genre: 'Classic Literature',
  author_details: [
    {
      _id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
      name: 'Jane Austen',
      nationality: 'British',
      birthYear: 1775
    }
  ]
}

ご覧のとおり、著者の情報は author_details フィールドの下に各書籍ドキュメント内に埋め込まれました。これにより、両方のコレクションのフィールドを同時にクエリできるようになります。

参照の更新と維持

データは常に静的であるとは限りません。エラーを修正したり、データを削除したりする必要がある場合があり、そのためにはドキュメントとその参照を更新または削除する必要があります。このステップでは、参照を更新する方法と、「孤児」ドキュメントを処理する方法を学びます。

「Emma」という本が誤ってジェーン・オースティンに帰属していたことが判明し、チャールズ・ディケンズに割り当てるべきだったとします。これは、updateOne コマンドと $set オペレーターを使用して修正できます。

db.books.updateOne(
    { title: "Emma" },
    { $set: { author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2") } }
)

再度書籍を検索し、その author_id を確認して変更を検証します。

db.books.findOne({ title: "Emma" })

出力例:

{
  _id: ObjectId("..."),
  title: 'Emma',
  author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
  published: 1815,
  genre: 'Classic Literature'
}

次に、親ドキュメントが削除された場合に何が起こるかを見てみましょう。著者を削除すると、その著者を参照している書籍はすべて「孤児」になります。チャールズ・ディケンズをデータベースから削除しましょう。

db.authors.deleteOne({ name: "Charles Dickens" })

著者ドキュメントはなくなりましたが、「Emma」と「Oliver Twist」という書籍は、削除された著者を指す author_id をまだ持っています。これはデータ整合性の問題を引き起こす可能性があります。実際のアプリケーションでは、孤児となった書籍を削除したり、再割り当てしたりするなど、この状況を処理するためのロジックを実装します。

この実験では、2 冊の孤児となった書籍を削除して手動でクリーンアップしましょう。

db.books.deleteMany({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2") })

このコマンドは、削除された著者を参照している books コレクションのすべてのドキュメントを削除し、データの整合性を維持します。

インデックスを使用したクエリパフォーマンスの向上

コレクションが大きくなると、特定のフィールドでフィルタリングするクエリは遅くなる可能性があります。これは、MongoDB が一致を見つけるためにすべてのドキュメントをスキャンする必要があるためです。これを最適化するために、頻繁にクエリするフィールドにインデックスを作成できます。この場合、books コレクションの author_id は最適な候補です。

このステップでは、著者の書籍の検索を高速化するために author_id フィールドにインデックスを作成します。

books コレクションで createIndex コマンドを使用します。引数 { author_id: 1 } は、MongoDB に author_id フィールドに昇順インデックスを作成するように指示します。

db.books.createIndex({ author_id: 1 })

MongoDB はこれをバックグラウンドで処理します。完了すると、インデックスが作成されたことを確認するメッセージが返されます。

出力例:

{
  "numIndexesBefore": 1,
  "numIndexesAfter": 2,
  "createdCollectionAutomatically": false,
  "ok": 1
}

インデックスが存在することを確認するには、getIndexes コマンドを使用できます。これにより、books コレクションのすべてのインデックスが一覧表示されます。

db.books.getIndexes()

2 つのインデックスが表示されるはずです。_id のデフォルトインデックスと、作成したばかりの新しい author_id_1 インデックスです。

出力例:

[
  { "v": 2, "key": { "_id": 1 }, "name": "_id_" },
  { "v": 2, "key": { "author_id": 1 }, "name": "author_id_1" }
]

このインデックスが設定されると、以前に使用した $lookup ステージを含む、author_id でフィルタリングまたはソートするクエリは、大規模なデータセットで大幅に高速になります。

最後に、MongoDB シェルを終了できます。

exit

まとめ

この実験では、MongoDB でドキュメント参照を使用する基本を学びました。コレクションを作成し、ObjectId 参照でドキュメントをリンクすることから始めました。次に、1 対多の関係を管理し、強力な $lookup 集計ステージを使用してコレクションを横断してクエリを実行し、参照を更新およびクリーンアップしてデータ整合性を維持する練習をしました。最後に、参照フィールドにインデックスを作成してクエリパフォーマンスを向上させました。これらのスキルは、MongoDB でスケーラブルで効率的なアプリケーションを構築するために不可欠です。