MongoDB のクエリ - Swift SDK
項目一覧
- Overview
- ユースケース
- 前提条件
- サンプルデータ
- Async/Await クエリ MongoDB
- ドキュメントの作成
- 単一ドキュメントのインサート
- 複数のドキュメントの挿入
- ドキュメントを読む
- 単一ドキュメントの検索
- 複数ドキュメントの検索
- ドキュメントの検索とソート
- コレクション内のドキュメントをカウント
- Update Documents
- 単一ドキュメントの更新
- 複数のドキュメントの更新
- ドキュメントをアップサートする
- Delete Documents
- 単一ドキュメントの削除
- 複数のドキュメントの削除
- 変更の監視
- コレクションの変更の監視
- 特定の ID へのコレクション内の変更の監視
- フィルターでコレクションの変更を監視
- 非同期シーケンスとしてコレクションを監視
- ドキュメントの集計
- フィルター ドキュメント
- グループ ドキュメント
- プロジェクト ドキュメント フィールド
- ドキュメントへのフィールドの追加
- Unwind Array Values
Overview
Realm Swift SDK の MongoClientとQuery APIを使用して、クライアント アプリケーション コードから MongoDB に保存されているデータを直接クエリできます。 Atlas App Servicesは、ログインしたユーザーまたは各ドキュメントの内容に基づいて結果を安全に取得するためのコレクションにデータ アクセスルールを提供します。
Tip
以下も参照してください。
このページでは、MongoDB データソースを直接クエリする方法について説明します。 Realm から取得したデータをフィルタリングするには、「データのフィルタリング 」を参照してください。
ユースケース
MongoDB データソースをクエリする理由はさまざまあります。 Atlas Device Sync を介してクライアント内のデータを操作することは、必ずしも現実的ではないか、可能な限りありません。 次の場合には、MongoDB をクエリすることをお勧めします。
データセットが大きいか、クライアント デバイスにデータセット全体のロードに対する制約がある
Realm でモデル化されていないドキュメントを取得している
アプリは厳密なスキーマを持たないコレクションにアクセスする必要があります
非 Realm サービスは、アクセスしたいコレクションを生成します。
網羅的なものではありませんが、これらは MongoDB を直接クエリする一般的なユースケースです。
前提条件
クライアント アプリケーションから MongoDB をクエリする前に、App Services App で MongoDB Data Access を設定する必要があります。 バックエンド アプリを設定して Realm SDK クエリ Atlas を使用できるようにする方法については、App Services ドキュメントの「 MongoDB データアクセスの設定 」を参照してください。
サンプルデータ
これらの例は、フード ストアのチェーンで読み取りを説明する MongoDB コレクションで動作します。 ドキュメントは、次のプロパティを持つオブジェクトを表します。
class CoffeeDrink: Object { true) var _id: ObjectId (primaryKey: var name: String var beanRegion: String? var containsDairy: Bool var storeNumber: Int }
各例の完全なコードには、各操作を完了する前にログインと MongoDB コレクション ハンドルのインスタンス化が含まれています。 簡潔にするために、これらの例ではログインとコレクション処理コードが省略されています。 ただし、完全な各例は次のようになります。
appClient.login(credentials: Credentials.anonymous) { (result) in // Remember to dispatch back to the main thread in completion handlers // if you want to do anything on the UI. DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // mongodb-atlas is the cluster service name let mongoClient = user.mongoClient("mongodb-atlas") // Select the database let database = mongoClient.database(named: "ios") // Select the collection let collection = database.collection(withName: "CoffeeDrinks") // This document represents a CoffeeDrink object let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] // Insert the document into the collection collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): // Success returns the objectId for the inserted document print("Successfully inserted a document with id: \(objectId)") } } } } }
Async/Await クエリ MongoDB
バージョン 10.16.0 の新機能。
Realm Swift SDK は、MongoCollection メソッドの async/await バージョンを提供します。
このページのすべてのメソッドは async/await 構文と互換性があります。 この例では、 collection.insertOne()
メソッドのその構文を示します。 完了ハンドラーのバージョンは、「 単一ドキュメントの挿入 」で確認できます。
// This document represents a CoffeeDrink object let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] do { // Use the async collection method to insert the document let objectId = try await collection.insertOne(drink) print("Successfully inserted a document with id: \(objectId)") } catch { print("Call to MongoDB failed: \(error.localizedDescription)") }
Realm Swift SDK バージョン 10.15.0 および 10.16.0 以降では、Realm API の多くが Swift async/await 構文をサポートしています。 プロジェクトは、次の要件を満たしている必要があります。
Swift SDK バージョン | Swift バージョン要件 | サポートされている OS |
---|---|---|
10.25.0 | Swift 5.6 | iOS 13.x |
10.15.0 または 10.16.0 | Swift 5.5 | iOS 15.x |
アプリがasync/await
コンテキストで Realm にアクセスする場合は、スレッド関連のクラッシュを回避するためにコードを@MainActor
でマークします。
ドキュメントの作成
これらのコード スニペットは、モバイル アプリケーションから MongoDB コレクションに 1 つ以上のドキュメントを挿入する方法を示しています。 これらのメソッドは 1 つ以上のドキュメントを受け取り、結果を返します。 [] に成功すると、挿入されたドキュメントのObjectIdが返されます。または、複数のドキュメントを挿入している場合は、ObjectId の配列が順番に返されます。
単一ドキュメントのインサート
collection.insertOne() を使用して単一のドキュメントを挿入できます。
このスニペットは、「Collectionbian受け入れ」を説明する単一のドキュメントを、店舗のグループで販売する機能を説明するドキュメントのコレクションに挿入します。
// This document represents a CoffeeDrink object let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] // Insert the document into the collection collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): // Success returns the objectId for the inserted document print("Successfully inserted a document with id: \(objectId)") } }
Successfully inserted a document with id: objectId(64e50cab7765243942cd04ce)
複数のドキュメントの挿入
collection.insertMany() を使用して複数のドキュメントを挿入できます。
このスニペットは、グループ化された店舗で販売されるミルクバーを説明するドキュメントのコレクションに、コピー リンクを説明する 3 つのドキュメントを挿入します。
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 42] let drink2: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42] let drink3: Document = [ "name": "Bean of the Day", "beanRegion": "San Marcos, Guatemala", "containsDairy": false, "storeNumber": 47] // Insert the documents into the collection collection.insertMany([drink, drink2, drink3]) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectIds): print("Successfully inserted \(objectIds.count) new documents.") } }
Successfully inserted 3 new documents.
ドキュメントを読む
これらのコード スニペットは、モバイル アプリケーションから MongoDB コレクションに保存されているデータを読み取る方法を示しています。 読み取り操作では、標準のクエリ構文を使用して、データベースから返されるドキュメントを指定します。 読み取り操作では、一致した単一ドキュメント( findOneDocument()
の場合)、 long
数値( count()
の場合)、または一致したドキュメントの配列(次の場合)のいずれかに解決される結果が返されます。 find()
)。
単一ドキュメントの検索
collection.findOneDocument() を使用して単一のドキュメントを検索できます。
このスニペットは、グループ化された店舗で販売されているお茶を説明するドキュメントのコレクションから 1 つのドキュメントを検索します。ドキュメントのname
フィールドには string 値「Colombian Express」が含まれます。
let queryFilter: Document = ["name": "Colombian Blend"] collection.findOneDocument(filter: queryFilter) { result in switch result { case .failure(let error): print("Did not find matching documents: \(error.localizedDescription)") return case .success(let document): print("Found a matching document: \(String(describing: document))") } }
Found a matching document: Optional([ "name": Optional(RealmSwift.AnyBSON.string("Colombian Blend")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e5014f65796c813bc68274)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(43)) ])
複数ドキュメントの検索
collection.find() を使用して複数のドキュメントを見つけることができます。
このスニペットは、グループ化された店舗で販売されているお茶を説明するドキュメントのコレクション内のすべてのドキュメントを検索します。ドキュメントのname
フィールドには値「Americano」が含まれます。
let queryFilter: Document = ["name": "Americano"] collection.find(filter: queryFilter) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let bsonDocumentArray): print("Results: ") for bsonDocument in bsonDocumentArray { print("Coffee drink named: \(String(describing: bsonDocument["name"]))") } } }
Results: Coffee drink named: Optional(Optional(RealmSwift.AnyBSON.string("Americano"))) ... more matching documents ...
ドキュメントの検索とソート
コレクション内のドキュメントをソートするには、希望するソート オプションでFindOptionsのインスタンスを初期化します。 FindOptions
には、制限、プロジェクション、並べ替えの 3 つのパラメータがあります。 sorting
引数は、各キーがフィールドを表すキーと値のペアの配列にすることができます。 各キーで、値1
は降順でソートされ、 -1
は昇順でソートされます。
次に、クエリの実行時にFindOptions
インスタンスをcollection.find()
メソッドに渡します。
このスニペットは、グループ化された店舗で販売されているケーキを説明するドキュメントのコレクション内のすべてのドキュメントを検索します。ドキュメントのname
フィールドには値「Americano」が含まれ、 beanRegion
フィールドで降順にソートされます。
let queryFilter: Document = ["name": "Americano"] let findOptions = FindOptions(0, nil, [["beanRegion": 1]]) collection.find(filter: queryFilter, options: findOptions) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let documents): print("Results: ") for document in documents { print("Coffee drink: \(document)") } } }
Results: Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e521ed6b124fd047534345)), "beanRegion": Optional(RealmSwift.AnyBSON.string("San Marcos, Guatemala")), "name": Optional(RealmSwift.AnyBSON.string("Americano")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ] Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e521ed6b124fd047534344)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "name": Optional(RealmSwift.AnyBSON.string("Americano")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")) ] ... more matching documents, sorted by `beanRegion` ...
コレクション内のドキュメントをカウント
collection.count()を使用してコレクション内のドキュメントをカウントできます。 カウントするドキュメントを決定するために、オプションのクエリと制限を指定できます。 クエリを指定しない場合、アクションはコレクション内のすべてのドキュメントをカウントします。
このスニペットは、グループ化された店舗で販売されているお茶を説明するドキュメントのコレクション内のドキュメントの数をカウントします。ドキュメントのname
フィールドには「操作の数」が含まれています。
let queryFilter: Document = ["name": "Bean of the Day"] collection.count(filter: queryFilter) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let count): print("Found this many documents in the collection matching the filter: \(count)") } }
Found this many documents in the collection matching the filter: 24
Update Documents
これらのコード スニペットは、モバイル アプリケーションから MongoDB コレクションに保存されているデータをアップデートする方法を示しています。 更新操作では、クエリを使用して更新するドキュメントを指定し、更新演算子を使用してクエリに一致するドキュメントを変更する方法を記述します。 更新操作は、 UpdateResultまたはError
に解決される結果を返します。
単一ドキュメントの更新
collection.updateOneDocument() を使用して単一のドキュメントを更新できます。
このスニペットは、店舗のグループで販売されるケーキのミルクを説明するドキュメントのコレクション内の 1 つのドキュメントを更新します。 このアップデート操作は、 name
フィールドに値「east」が含まれているドキュメントをクエリし、 containsDairy
フィールドをtrue
に設定します。
let queryFilter: Document = ["name": "Bean of the Day", "storeNumber": 42] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): if updateResult.matchedCount == 1 && updateResult.modifiedCount == 1 { print("Successfully updated a matching document.") } else { print("Did not update a document") } }
Successfully updated a matching document.
複数のドキュメントの更新
コレクション.updateManyDocuments() を使用して複数のドキュメントを更新できます。
このスニペットは、店舗のグループで販売されるケーキのミルクを説明するドキュメントのコレクション内の複数のドキュメントを更新します。 このアップデート操作は、 name
フィールドに値「east」が含まれているドキュメントをクエリし、 containsDairy
フィールドをtrue
に変更します。
let queryFilter: Document = ["name": "Bean of the Day"] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateManyDocuments(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated \(updateResult.modifiedCount) documents.") } }
Successfully updated 24 documents.
ドキュメントをアップサートする
アップデート操作がコレクション内のどのドキュメントにも一致しない場合は、 upsert
オプションをtrue
に設定することで、アップデート クエリに一致する新しいドキュメントを 1 つ自動的に挿入できます。
次のスニペットは、店舗のグループで販売されるミルクサーバーについて説明するドキュメントのコレクション内の 1 つのドキュメントを更新します。 クエリに一致するドキュメントがない場合、ドキュメントがない場合は新しいドキュメントが挿入されます。 この操作は、 name
フィールドの値が「操作の数」で、かつstoreNumber
フィールドの値が55
であるドキュメントをクエリします。
このスニペットはupsert
オプションをtrue
に設定しているため、クエリに一致するドキュメントがない場合、MongoDB はクエリと指定された更新の両方を含む新しいドキュメントを作成します。
let queryFilter: Document = ["name": "Bean of the Day", "storeNumber": 55] let documentUpdate: Document = ["name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": false, "storeNumber": 55] collection.updateOneDocument(filter: queryFilter, update: documentUpdate, upsert: true) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): if let unwrappedDocumentId = updateResult.documentId { print("Successfully upserted a document with id: \(unwrappedDocumentId)") } else { print("Did not upsert a document") } } }
Successfully upserted a document with id: 64e523e37765243942eba44a
Delete Documents
これらのコード スニペットは、MongoDB コレクションに保存されているドキュメントをモバイル アプリケーションから削除する方法を示しています。 Delete operations use a query to specify which documents to delete and return results that resolve to an Int
count of deleted documents or Error
.
単一ドキュメントの削除
collection.deleteOneDocument() を使用して、コレクションから単一のドキュメントを削除できます。
このスニペットは、店舗のグループで販売されているお茶を説明するドキュメントのコレクション内の 1 つのドキュメントを削除します。 この操作は、 name
フィールドの値が「Mocha」で、かつstoreNumber
フィールドの値が17
であるドキュメントをクエリして削除します。
let queryFilter: Document = ["name": "Mocha", "storeNumber": 17] collection.deleteOneDocument(filter: queryFilter) { deletedResult in switch deletedResult { case .failure(let error): print("Failed to delete a document: \(error.localizedDescription)") return case .success(let deletedResult): print("Successfully deleted a document.") } }
Successfully deleted a document.
複数のドキュメントの削除
collection.deleteManyDocuments() を使用して、コレクションから複数の項目を削除できます。
このスニペットは、 name
フィールドに値「Capped」が含まれるドキュメントのクエリに一致する店舗のグループで販売されているお茶を説明するドキュメントのコレクション内のすべてのドキュメントを削除します。
let filter: Document = ["name": "Caramel Latte"] collection.deleteManyDocuments(filter: filter) { deletedResult in switch deletedResult { case .failure(let error): print("Failed to delete a document: \(error.localizedDescription)") return case .success(let deletedResult): print("Successfully deleted \(deletedResult) documents.") } }
Successfully deleted 3 documents.
変更の監視
コレクション内のドキュメントが作成、変更、または削除されるたびに MongoDB が発行する変更通知について、コレクションを監視できます。 各通知では、変更されたドキュメントとその変更方法、およびイベントの原因となった操作後の完全なドキュメントを指定します。
重要
サーバーレスの制限事項
データソースが Atlas サーバーレスインスタンスの場合、変更を監視することはできません。 MongoDB サーバーレスは現在、変更をリッスンするために監視対象コレクションで使用される 変更ストリーム をサポートしていません。
コレクションの変更の監視
collection.watch()を呼び出すと、コレクションに加えられた変更のストリームを開くことができます。 この関数は、MongoDB コレクションが変更されたときに AnyBSON 変更イベントを発行する発行者を作成します。
オプションで、 collection.watch(filterIds:) を使用してコレクション内の_ids
のフィルタリングされたリストを監視するか、 collection.watch(matchFilter:)を使用して受信変更イベントに$match
フィルターを適用できます。
.watch()
メソッドはChangeEventDelegateを受け取り、ストリームの変更をサブスクライブできます。 たとえば、このChangeEventDelegate
を使用すると次のようになります。
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") } }
このコードは、 CoffeeDrinks
コレクション内のドキュメントに対する変更を監視します。
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // Continue below } // Set up the client, database, and collection. let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Watch the collection. In this example, we use a queue and delegate, // both of which are optional arguments. let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let changeStream = collection.watch(delegate: delegate, queue: queue) // Adding a document triggers a change event. let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 42] collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): print("Successfully inserted a document with id: \(objectId)") } } // After you're done watching for events, close the change stream. changeStream.close() } }
Login as <RLMUser: 0x600002dfd1a0> succeeded! Change stream opened: <RLMChangeStream: 0x60000182ab80> Successfully inserted a document with id: objectId(64e525665fef1743dedb5aa6) Change event document received: [ "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:15:18 +0000)), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E525660000000B2B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E525665FEF1743DEDB5AA60004" )) ])), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e525665fef1743dedb5aa6)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")), "db": Optional(RealmSwift.AnyBSON.string("ios")) ])), "operationType": Optional(RealmSwift.AnyBSON.string("insert")), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e525665fef1743dedb5aa6)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)) ]))] Change stream closed
特定の ID へのコレクション内の変更の監視
_id
を渡すことで、特定のオブジェクト リストに対するコレクション内の変更を監視できます。 ObjectId の配列とともにcollection.watch(filterIds: )を呼び出し、それらのドキュメントに適用される変更イベントのみを受け取ります。
このChangeEventDeleteを使用する例を考えてみます。
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") } }
以下のコードでは、この削除を使用して、 CoffeeDrinks
コレクション内の特定のドキュメントに対する変更を監視します。
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // Continue below } // Set up the client, database, and collection. let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Watch the collection. In this example, we use a queue and delegate, // both of which are optional arguments. // `filterIds` is an array of specific document ObjectIds you want to watch. let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let changeStream = collection.watch(filterIds: [drinkObjectId], delegate: delegate, queue: queue) // An update to a relevant document triggers a change event. let queryFilter: Document = ["_id": AnyBSON(drinkObjectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated the document") } } // After you're done watching for events, close the change stream. changeStream.close() } }
Login as <RLMUser: 0x60000010eb00> succeeded! Successfully inserted a document with id: objectId(64e525ce7765243942ef0a58) Change stream opened: <RLMChangeStream: 0x6000034946c0> Change event document received: [ "fullDocument": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e525ce7765243942ef0a58)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:17:09 +0000)), "operationType": Optional(RealmSwift.AnyBSON.string("update")), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e525ce7765243942ef0a58)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "db": Optional(RealmSwift.AnyBSON.string("ios")), "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")) ])), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "removedFields": Optional(RealmSwift.AnyBSON.array([])), "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true))])) ])), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E525D5000000082B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E525CE7765243942EF0A580004" )) ]))] Change stream closed
フィルターでコレクションの変更を監視
collection.watch(matchFilter: )を呼び出すと、特定の条件を満たすコレクション内のドキュメントに加えられた変更のストリームを開くことができます。 このメソッドは、コレクションの監視中に発生する各Document
データベース イベント を処理するために $match 演算子 のクエリとして使用される パラメータを受け入れます。
このChangeEventDeleteを使用する例を考えてみます。
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") } }
以下のコードでは、この削除を使用して、 CoffeeDrink
コレクション内のドキュメントに対する変更を監視します。 これにより、ドキュメントのstoreNumber
値が42
となるイベントに対してのみ、指定されたコールバックがトリガーされます。
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // Continue below } // Set up the client, database, and collection. let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Watch the collection. In this example, we use a queue and delegate, // both of which are optional arguments. let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let matchFilter = [ "fullDocument.storeNumber": AnyBSON(42) ] let changeStream = collection.watch(matchFilter: matchFilter, delegate: delegate, queue: queue) // An update to a relevant document triggers a change event. let queryFilter: Document = ["_id": AnyBSON(drinkObjectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated the document") } } // After you're done watching for events, close the change stream. changeStream.close() } }
Login as <RLMUser: 0x6000026ee640> succeeded! Successfully inserted a document with id: objectId(64e5266731323150716faf13) Change stream opened: <RLMChangeStream: 0x6000013283c0> Change event document received: [ "operationType": Optional(RealmSwift.AnyBSON.string("update")), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "removedFields": Optional(RealmSwift.AnyBSON.array([])), "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)) ])) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:19:44 +0000)), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E526700000000E2B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E5266731323150716FAF130004" )) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "db": Optional(RealmSwift.AnyBSON.string("ios")), "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")) ])), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e5266731323150716faf13)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ])), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e5266731323150716faf13)) ]))] Successfully updated the document Change stream closed
非同期シーケンスとしてコレクションを監視
バージョン10.37.0の新機能。
非同期シーケンスを開いて、コレクションに対する変更を監視できます。 非同期コンテキストでは、コレクションに対してchangeEvents()を呼び出して変更ストリームを開きます。 これにより、MongoDB コレクションの各変更に関する情報を含む任意の BSON 値の非同期シーケンスが提供されます。
オプションで、監視ストリームがサーバー上で初期化されたときに呼び出されるchangeEvents(onOpen: )コールバックを提供できます。
changeEvents()
API では、上記の例と同様に、コレクション内のドキュメントのサブセットを監視するには、 filterIds
またはmatchFilter
を使用できます。
次のスニペットは、 CoffeeDrinks
コレクション内のドキュメントに対する変更を非同期シーケンスとして監視します。
let user = try await appClient.login(credentials: Credentials.anonymous) // Set up the client, database, and collection. let mongoClient = user.mongoClient("mongodb-atlas") let database = mongoClient.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Set up a task you'll later await to keep the change stream open, // and you can cancel it when you're done watching for events. let task = Task { // Open the change stream. let changeEvents = collection.changeEvents(onOpen: { print("Successfully opened change stream") }) // Await events in the change stream. for try await event in changeEvents { let doc = event.documentValue! print("Received event: \(event.documentValue!)") } } // Updating a document in the collection triggers a change event. let queryFilter: Document = ["_id": AnyBSON(objectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] let updateResult = try await collection.updateOneDocument(filter: queryFilter, update: documentUpdate) // Cancel the task when you're done watching the stream. task.cancel() _ = await task.result
Successfully opened change stream Received event: [ "operationType": Optional(RealmSwift.AnyBSON.string("update")), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e526d9850b15debe83ff46)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")), "db": Optional(RealmSwift.AnyBSON.string("ios")) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:21:30 +0000)), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(43)), "_id": Optional(RealmSwift.AnyBSON.objectId(64e526d9850b15debe83ff46)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")) ])), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E526DA000000092B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E526D9850B15DEBE83FF460004" ) ) ])), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)) ])), "removedFields": Optional(RealmSwift.AnyBSON.array([])) ]))]
ドキュメントの集計
集計操作は、集計パイプラインと呼ばれる一連のデータ集計ステージを通じてコレクション内のすべてのドキュメントを実行します。 集計を使用すると、ドキュメントのフィルタリングと変換、関連するドキュメントのグループに関するサマリー データの収集、その他の複雑なデータ操作が可能になります。
collection.aggregate() を使用して、コレクションに対して集計操作を構成して実行できます。
集計操作は、集計ステージのリストを入力として受け入れ、パイプラインによって処理されたドキュメントのコレクション、またはError
に解決される結果を返します。
フィルター ドキュメント
$matchステージは、標準の MongoDBクエリ構文を使用してドキュメントをフィルタリングできます。
この$match
ステージでは、ドキュメントをフィルタリングして、 storeNumber
フィールドの値が42
と等しいドキュメントのみを含めます。
let pipeline: [Document] = [["$match": ["storeNumber": ["$eq": 42]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let documents): print("Successfully ran the aggregation:") for document in documents { print("Coffee drink: \(document)") } } }
Successfully ran the aggregation: Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e53171313231507183e7c2)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))] Coffee drink: [ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e53171313231507183e7c3)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))] (...more results...)
グループ ドキュメント
$groupステージを使用して、1 つ以上のドキュメントのサマリーデータを集計できます。 MongoDB は、 $group
ステージの_id
フィールドに定義された式に基づいてドキュメントをグループ化します。 フィールド名の前に$
を付けることで、特定のドキュメント フィールドを参照できます。
この$group
ステージでは、ドキュメントをstoreNumber
フィールドの値で配置します。 次に、 フィールドの値にその店舗番号を含むドキュメントの数を計算します。 つまり、店舗番号ごとのケーキの数を計算しています。
let pipeline: [Document] = [["$group": ["_id": "$storeNumber", "numItems": ["$sum": 1]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } } }
Successfully ran the aggregation. ["numItems": Optional(RealmSwift.AnyBSON.int64(27)), "_id": Optional(RealmSwift.AnyBSON.int64(42))] ["numItems": Optional(RealmSwift.AnyBSON.int64(44)), "_id": Optional(RealmSwift.AnyBSON.int64(47))] (...more results...)
プロジェクト ドキュメント フィールド
$projectステージを使用して、ドキュメントから特定のフィールドを含めたり省略したり、集計演算子を使用して新しいフィールドを計算したりできます。 プロジェクションは、次の 2 つの方法で機能します。
1
を使用して含めるフィールドを指定します。 これには、指定されていないすべてのフィールドを暗黙的に除外するという副作用があります。0
を使用して除外するフィールドを指定します。 これには、指定されていないすべてのフィールドが暗黙的に含まれるという副作用があります。
これらの 2 つのプロジェクション方法は、相互に排他的です。 含めるフィールドを指定した場合は、除外するフィールドも指定できず、その逆も同様です。
注意
_id
フィールドは特別なケースで、明示的に指定されない限り、すべてのクエリに常に含まれます。 このため、 0
値を持つ_id
フィールドを除外しながら、同時にstoreNumber
などの他のフィールドを1
とともに含めることができます。 _id
フィールドを除外するという特別なケースのみ、1 つの$project
ステージで除外と包含の両方が許可されます。
この例では、 CoffeeDrink
ドキュメントに、次のドキュメントと同様に「Store 42」などの単語を含む string 値であるstore
フィールドがあるとします。
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "store": "Store 42"] let drink2: Document = [ "name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "store": "Store 47"]
次の$project
ステージでは、 _id
フィールドが省略され、 name
フィールドが含まれ、 storeNumber
という名前の新しいフィールドが作成されます。 storeNumber
は 2 つの集計演算子を使用して生成されます。
$split
は、store
string 表現を、スペース文字を囲む 2 つの string セグメントに分割します。 たとえば、この方法で分割された値「Store 42」は、「store」と「42」の 2 つの要素を含む配列を返します。$arrayElemAt
は、2 番目の引数に基づいて配列から特定の要素を選択します。 この場合、値1
は、0
の配列インデックス以降、$split
演算子によって生成された配列から 2 番目の要素を選択します。 たとえば、この操作に渡される値 ["store", "42"] は "42" の値を返します。
let pipeline: [Document] = [["$project": ["_id": 0, "name": 1, "storeNumber": ["$arrayElemAt": [["$split": ["$store", " "]], 1]]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } } }
Successfully ran the aggregation. ["name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.string("42"))] ["storeNumber": Optional(RealmSwift.AnyBSON.string("47")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day"))] (...more results...)
ドキュメントへのフィールドの追加
$addFieldsステージを使用して、集計演算子を使用して計算された値を持つ新しいフィールドを追加できます。
注意
$addFields
は$projectに似ていますが、フィールドを含めたり省略したりすることはできません。
この例では、 CoffeeDrink
ドキュメントに、次のドキュメントと同様に「Store 42」などの単語を含む string 値であるstore
フィールドがあるとします。
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "store": "Store 42"] let drink2: Document = [ "name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "store": "Store 47"]
次の$addFields
ステージでは、 storeNumber
という名前の新しいフィールドが作成されます。値はstore
フィールドの値を変換する 2 つの集計演算子の出力です。
let pipeline: [Document] = [["$addFields": ["storeNumber": ["$arrayElemAt": [["$split": ["$store", " "]], 1]]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } } }
Successfully ran the aggregation. [ "storeNumber": Optional(RealmSwift.AnyBSON.string("42")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e588ff5fef1743de3559aa)), "store": Optional(RealmSwift.AnyBSON.string("Store 42")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia"))] [ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.string("47")), "store": Optional(RealmSwift.AnyBSON.string("Store 47")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e588ff5fef1743de3559ab))]
Unwind Array Values
$unwindステージを使用して、配列を含む単一のドキュメントを、その配列の個々の値を含む複数のドキュメントに変換できます。 配列フィールドを展開すると、MongoDB は配列フィールドの要素ごとに各ドキュメントを 1 回コピーしますが、コピーごとに配列値を配列要素に置き換えます。
featuredInPromotions
配列を含む次のドキュメントを考えてみましょう。
let drink: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42, "featuredInPromotions": [ "Spring into Spring", "Tastes of Fall", "Winter Delights" ] ]
次の$unwind
ステージでは、各ドキュメントのitems
配列の要素ごとに新しいドキュメントが作成されます。 また、元の配列内の要素の位置インデックスを指定するitemIndex
というフィールドも新しいドキュメントに追加されます。
let pipeline: [Document] = [["$unwind": ["path": "$featuredInPromotions", "includeArrayIndex": "itemIndex"]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print("Coffee drink: \(result)") } } }
Successfully ran the aggregation. Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Spring into Spring")), "itemIndex": Optional(RealmSwift.AnyBSON.int64(0)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true))] Coffee drink: [ "itemIndex": Optional(RealmSwift.AnyBSON.int64(1)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Tastes of Fall")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia"))] Coffee drink: [ "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "itemIndex": Optional(RealmSwift.AnyBSON.int64(2)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Winter Delights"))]
次に、グループ ドキュメントの例のように、各昇格のキー量をfeaturedInPromotions
と$sum
の値で$group
するか、データに基づいて他の計算や変換を実行することができます。