宇野  和也

宇野 和也

1638714180

Rust言語の集約パイプライン

MongoDBの集約パイプライン は、その最も強力な機能の1つです。これらを使用すると、MongoDBデータベース内のデータの集計、変換、結合などの操作を実行する一連のステージに分割された式を記述できます。これにより、MongoDBデータベース内のドキュメントとコレクション全体で計算と分析を行うことができます。

前提条件

このクイックスタートは、一連のRust投稿の2番目です。私の最初の投稿であるRustでの基本的なMongoDB操作から始めることを強くお勧め します。ここでは、ここで使用するサンプルデータを含む無料のMongoDBAtlasデータベースクラスターを正しくセットアップする方法を説明 します。それを読んで戻ってきてください。待ちます。これがないと、このクイックスタートガイドのコードを実行するためにデータベースを正しく設定できません。

要約すると、次のものが必要です。

Rustの最新バージョン。1.49を使用しましたが、最近のバージョンであれば問題なく動作するはずです。

選択したコードエディタ。私はお勧めVSコード錆アナライザ拡張

sample_mflixデータセットを含むMongoDBクラスター。これを設定する手順 は、このシリーズの最初のブログ投稿にあります。

入門

MongoDBの集約パイプラインは非常に強力であるため、最初は少し圧倒されるように思われるかもしれません。このため、ゆっくりと始めます。まず、私はあなたがすでにのMongoDBので達成できることを行動複製パイプラインアップ構築する方法を紹介しますfind()方法を、代わりに集約パイプラインを使用して$match$sort$limit段階。次に、で実行できる範囲を超えるクエリを作成する方法を示し、別のコレクションからの関連ドキュメントを含めるためにfind使用$lookupする方法を示します。最後に、$groupドキュメントをグループ化して新しいドキュメントの概要を作成する方法を示して、「アグリゲーション」を「アグリゲーションパイプライン」に入れます。

このクイックスタートシリーズのすべてのサンプルコードは、GitHubにあります。行き詰まったらチェックすることをお勧めしますが、そうでない場合は、チュートリアルに従って自分でコードを書く価値があります。

この投稿のすべてのパイプラインは、sample_mflix データベースのmoviesコレクションに対して実行され ます。これには、次のようなドキュメントが含まれています(同等のRust構造体よりも少し読みやすいため、Pythonでどのように表示されるかを示しています)。

{
   '_id': ObjectId('573a1392f29313caabcdb497'),
   'awards': {'nominations': 7,
               'text': 'Won 1 Oscar. Another 2 wins & 7 nominations.',
               'wins': 3},
   'cast': ['Janet Gaynor', 'Fredric March', 'Adolphe Menjou', 'May Robson'],
   'countries': ['USA'],
   'directors': ['William A. Wellman', 'Jack Conway'],
   'fullplot': 'Esther Blodgett is just another starry-eyed farm kid trying to '
               'break into the movies. Waitressing at a Hollywood party, she '
               'catches the eye of alcoholic star Norman Maine, is given a test, '
               'and is caught up in the Hollywood glamor machine (ruthlessly '
               'satirized). She and her idol Norman marry; but his career '
               'abruptly dwindles to nothing',
   'genres': ['Drama'],
   'imdb': {'id': 29606, 'rating': 7.7, 'votes': 5005},
   'languages': ['English'],
   'lastupdated': '2015-09-01 00:55:54.333000000',
   'plot': 'A young woman comes to Hollywood with dreams of stardom, but '
            'achieves them only with the help of an alcoholic leading man whose '
            'best days are behind him.',
   'poster': 'https://m.media-amazon.com/images/M/MV5BMmE5ODI0NzMtYjc5Yy00MzMzLTk5OTQtN2Q3MzgwOTllMTY3XkEyXkFqcGdeQXVyNjc0MzMzNjA@._V1_SY1000_SX677_AL_.jpg',
   'rated': 'NOT RATED',
   'released': datetime.datetime(1937, 4, 27, 0, 0),
   'runtime': 111,
   'title': 'A Star Is Born',
   'tomatoes': {'critic': {'meter': 100, 'numReviews': 11, 'rating': 7.4},
               'dvd': datetime.datetime(2004, 11, 16, 0, 0),
               'fresh': 11,
               'lastUpdated': datetime.datetime(2015, 8, 26, 18, 58, 34),
               'production': 'Image Entertainment Inc.',
               'rotten': 0,
               'viewer': {'meter': 79, 'numReviews': 2526, 'rating': 3.6},
               'website': 'http://www.vcientertainment.com/Film-Categories?product_id=73'},
   'type': 'movie',
   'writers': ['Dorothy Parker (screen play)',
               'Alan Campbell (screen play)',
               'Robert Carson (screen play)',
               'William A. Wellman (from a story by)',
               'Robert Carson (from a story by)'],
   'year': 1937}

そこ多くのデータがありますが、私は上の中心にすることがあります_idtitleyear、およびcastフィールド。

最初の集約パイプライン

集約パイプラインは、コレクションのaggregate() メソッドを使用してmongodbモジュールによって実行されます 。

の最初の引数aggregate()は、実行される一連のパイプラインステージです。クエリと同様に、集計パイプラインの各ステージは BSONドキュメントです。これらdoc!は、前の投稿で紹介したマクロを使用して作成することがよくあります。

集約パイプラインは、コレクション内のすべてのデータを処理します。パイプラインの各ステージは通過するドキュメントに適用され、あるステージから放出されたドキュメントはすべて、残りのステージがなくなるまで次のステージへの入力として渡されます。この時点で、パイプラインの最後のステージから発行されたドキュメントは、への呼び出しと同様の方法で、カーソルとしてクライアントプログラムに返されますfind()

などの個々のステージは、$match特定の基準に一致するドキュメントのみを通過させるためのフィルターとして機能できます。他のステージのようなタイプの、$project$addFields、とは$lookup彼らがパイプラインを通過すると、個々の文書の内容を変更します。最後に、などの特定のステージタイプは、$group渡されたドキュメント全体に基づいて、まったく新しいドキュメントのセットを作成します。これらのステージはいずれも、MongoDB自体に格納されているデータを変更しません。プログラムに戻す前にデータを変更するだけです。$ outのように、パイプラインの結果をMongoDBに保存できるステージがあります、このクイックスタートでは取り上げません。

前回の投稿で使用したのと同じ環境で作業していると想定します。そのため、ファイルに依存関係としてmongodbクレートが既に構成されているCargo.toml必要があり.envMONGODB_URI環境変数を含むファイルが必要です。

検索と並べ替え

まず、以下をRustコードに貼り付けます。

// Load the MongoDB connection string from an environment variable:
let client_uri =
   env::var("MONGODB_URI").expect("You must set the MONGODB_URI environment var!");

// An extra line of code to work around a DNS issue on Windows:
let options =
   ClientOptions::parse_with_resolver_config(&client_uri, ResolverConfig::cloudflare())
      .await?;
let client = mongodb::Client::with_options(options)?;

// Get the 'movies' collection from the 'sample_mflix' database:
let movies = client.database("sample_mflix").collection("movies");

上記のコードは、データベース内のコレクションを指す、とCollection呼ばれるインスタンスを提供します。movie_collectionmovies

パイプラインを作成し、それをで実行してaggregateから、ループして結果の各映画の詳細を出力するコードを次に示します。プログラムに貼り付けます。

// Usually implemented outside your main function:
#[derive(Deserialize)]
struct MovieSummary {
   title: String,
   cast: Vec<String>,
   year: i32,
}

impl fmt::Display for MovieSummary {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
      write!(
         f,
         "{}, {}, {}",
         self.title,
         self.cast.get(0).unwrap_or(&"- no cast -".to_owned()),
         self.year
      )
   }
}

// Inside main():
let pipeline = vec![
   doc! {
      // filter on movie title:
      "$match": {
         "title": "A Star Is Born"
      }
   },
   doc! {
      // sort by year, ascending:
      "$sort": {
         "year": 1
      }
   },
];

// Look up "A Star is Born" in ascending year order:
let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the results, convert each of them to a MovieSummary, and then print out.
while let Some(result) = results.next().await {
   // Use serde to deserialize into the MovieSummary struct:
   let doc: MovieSummary = bson::from_document(result?)?;
   println!("* {}", doc);
}

このパイプラインには2つの段階があります。1つ目は$ matchステージで、これはfind()。を使用してコレクションをクエリするのと似ています。読み取り操作クエリに基づいて、ステージを通過するドキュメントをフィルタリングします。これはパイプラインの最初のステージであるため、その入力はmovieコレクション内のすべてのドキュメントです。$matchステージのクエリtitleは入力ドキュメントのフィールドでフィルタリングされるため、このステージから出力されるドキュメントは「スター誕生」というタイトルのみになります。

2番目のステージは$ sort ステージです。映画「アリー/スター誕生」のドキュメントのみがこのステージに渡されるため、結果として「アリー/スター誕生」と呼ばれるすべての映画が年のフィールドで並べ替えられ、最も古い映画が最初になります。

Aggregate()を呼び出すと、結果のドキュメントを指すカーソルが返されます。カーソルはストリームトレイトを実装します。メソッドを提供するStreamExtをインポートしている限り、カーソルは他のストリームと同じようにループできますnext()。上記のコードは、返されたすべてのドキュメントをループし、タイトル、cast配列の最初の俳優、映画が制作された年で構成される短い要約を出力します。

上記のコードを実行すると、次のようになります。

* A Star Is Born, Janet Gaynor, 1937
* A Star Is Born, Judy Garland, 1954
* A Star Is Born, Barbra Streisand, 1976

コードのリファクタリング

上記の例のように、集約パイプライン全体を単一のデータ構造として構築することは可能ですが、必ずしも良い考えではありません。パイプラインは長く複雑になる可能性があります。このため、パイプラインの各ステージを個別の変数として構築し、最後に次のようにステージをパイプラインに結合することをお勧めします。

// Match title = "A Star Is Born":
let stage_match_title = doc! {
   "$match": {
      "title": "A Star Is Born"
   }
};

// Sort by year, ascending:
let stage_sort_year_ascending = doc! {
   "$sort": { "year": 1 }
};

// Now the pipeline is easier to read:
let pipeline = vec![stage_match_title, stage_sort_year_ascending];

結果の数を制限する

映画コレクションから「アリー/スター誕生」の最新作を入手したいと想像してみてください。

これは、次の順序で実行される3つの段階と考えることができます。

「アリー/スター誕生」の映画ドキュメントを入手してください。

年の降順で並べ替えます。

最初のドキュメントを除くすべてを破棄します。

最初の段階はすでに上記と同じstage_match_titleです。2番目のステージはと同じstage_sort_year_ascendingですが、値がに1変更されてい-1ます。3番目のステージは$ limitステージです。

変更され、新しい、このようなコードになります。

// Sort by year, descending:
let stage_sort_year_descending = doc! {
   "$sort": {
      "year": -1
   }
};

// Limit to 1 document:
let stage_limit_1 = doc! { "$limit": 1 };

let pipeline = vec![stage_match_title, stage_sort_year_descending, stage_limit_1];

上記の変更を行ってコードを実行すると、次の行が表示されます。

* A Star Is Born, Barbra Streisand, 1976

ちょっと待って!レディー・ガガとブラッドリー・クーパーとの素晴らしい作品のドキュメントがないのはなぜですか?

今学んだスキルを使って、コレクションの最新の日付を見つけてみませんか?それはあなたにあなたの答えを与えるでしょう!

これで、集計パイプラインを使用してコレクションのコンテンツをフィルタリング、並べ替え、制限する方法がわかりました。しかし、これらはあなたがすでに行うことができる単なる操作ですfind()!なぜこれらの複雑で新しい集約パイプラインを使用したいのですか?

読んでください、私の友人、そして私はあなたにMongoDBアグリゲーションパイプラインの真の力をお見せします。

他のコレクションで関連データを検索する

sample_mflixデータベースに隠れている汚い秘密があります。moviesコレクションだけでなく、と呼ばれるコレクションもありますcommentscommentsコレクション内のドキュメントは次のようになります。

{
   '_id': ObjectId('5a9427648b0beebeb69579d3'),
   'movie_id': ObjectId('573a1390f29313caabcd4217'),
   'date': datetime.datetime(1983, 4, 27, 20, 39, 15),
   'email': 'cameron_duran@fakegmail.com',
   'name': 'Cameron Duran',
   'text': 'Quasi dicta culpa asperiores quaerat perferendis neque. Est animi '
           'pariatur impedit itaque exercitationem.'}

映画へのコメントです。なぜ人々がこれらの映画にラテン語のコメントを書いているのかわかりませんが、それで行きましょう。2番目のフィールドは、コレクション内のドキュメントmovie_id,_id値に対応しますmovies

だから、映画に関連したコメントです!

MongoDBを使用すると、映画をクエリしたり、リレーショナルデータベースのJOINなどの関連コメントを埋め込んだりできますか? はい、そうです$ lookupステージで。

別のコレクションから関連ドキュメントを取得し、それらをプライマリコレクションのドキュメントに埋め込む方法を説明します。

まず、MovieSummary構造体の定義を変更してcommentsrelated_commentsBSONフィールドからロードされたフィールドを持つようにします。ドキュメントにComment含まれるデータのサブセットを含む構造体を定義しcommentsます。

#[derive(Deserialize)]
struct MovieSummary {
   title: String,
   cast: Vec<String>,
   year: i32,
   #[serde(default, rename = "related_comments")]
   comments: Vec<Comment>,
}

#[derive(Debug, Deserialize)]
struct Comment {
   email: String,
   name: String,
   text: String,
}

次に、新しいパイプラインを最初から作成し、次の手順から始めます。

// Look up related documents in the 'comments' collection:
let stage_lookup_comments = doc! {
   "$lookup": {
      "from": "comments",
      "localField": "_id",
      "foreignField": "movie_id",
      "as": "related_comments",
   }
};

// Limit to the first 5 documents:
let stage_limit_5 = doc! { "$limit": 5 };

let pipeline = vec![
   stage_lookup_comments,
   stage_limit_5,
];

let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the results and print a summary and the comments:
while let Some(result) = results.next().await {
   let doc: MovieSummary = bson::from_document(result?)?;
   println!("* {}, comments={:?}", doc, doc.comments);
}

私が呼んstage_lookup_comments$lookupステージはステージです。この$lookupステージcommentsでは、同じムービーIDを持つコレクションからドキュメントを検索します。一致するコメントはrelated_comments、という名前のBSONフィールドに配列としてリストされ、配列値には、この映画の_id値がmovie_id。であるすべてのコメントが含まれます。

$limit圧倒されることなく適度な量の出力があることを確認するために、ステージを追加しました。

次に、コードを実行します。

上記のパイプラインの実行がかなり遅いことに気付くかもしれません。これには2つの理由があります。

23.5kの映画ドキュメントと50kのコメントがあります。

commentsコレクションに欠落しているインデックスがあります。インデックスについて教えるために、意図的に欠落しています。

インデックスの問題を修正する方法については、ここでは説明しません。これについては、このシリーズの後半の投稿で、インデックスに焦点を当てて説明します。代わりに、開発中に低速の集約パイプラインを操作するための秘訣を紹介します。

パイプラインを作成してテストしている間、遅いパイプラインでの作業は苦痛です。 ただし、パイプライン$limit開始時に一時的なステージを配置すると、クエリが高速になります(ただし、データセット全体を実行していないため、結果が異なる場合があります)。

このパイプラインを書いていたとき、私はの最初の段階を持っていました{ "$limit": 1000 }

パイプラインの作成が終了したら、最初のステージをコメントアウトして、パイプラインがコレクション全体で実行されるようにすることができます。 最初のステージを削除することを忘れないでください。そうしないと、間違った結果が得られます。

上記の集約パイプラインは、5つの映画ドキュメントの要約を印刷します。私はあなたの映画の要約のほとんどまたはすべてがこれで終わることを期待しています:comments=[]

配列の長さのマッチング

良ければ、配列にいくつかのドキュメントがあるかもしれませんが、ほとんどの映画にはコメントがないため、それはありそうにありません。次に、コメントが3つ以上ある映画のみに一致するようにいくつかのステージを追加する方法を示します。

理想的に$matchは、related_commentsフィールドの長さを取得し、それを式と照合する単一のステージを追加できるはず{ "$gt": 2 }です。この場合、実際には2つのステップです。

フィールドcomment_countの長さを含むフィールド(これを呼びます)を追加しrelated_commentsます。

の値がcomment_count2より大きい場合に一致します。

2つのステージのコードは次のとおりです。

// Calculate the number of comments for each movie:
let stage_add_comment_count = doc! {
   "$addFields": {
      "comment_count": {
         "$size": "$related_comments"
      }
   }
};

// Match movie documents with more than 2 comments:
let stage_match_with_comments = doc! {
   "$match": {
      "comment_count": {
         "$gt": 2
      }
   }
};

2つのステージは、$lookupステージの後と$limit5つのステージの前にあります。

let pipeline = vec![
   stage_lookup_comments,
   stage_add_comment_count,
   stage_match_with_comments,
   limit_5,
]

ここにいる間、このコードの出力をクリーンアップして、コメントを少し適切にフォーマットします。

let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the results and print a summary and the comments:
while let Some(result) = results.next().await {
   let doc: MovieSummary = bson::from_document(result?)?;
   println!("* {}", doc);
   if doc.comments.len() > 0 {
      // Print a max of 5 comments per movie:
      for comment in doc.comments.iter().take(5) {
         println!(
            "  - {} <{}>: {}",
            comment.name,
            comment.email,
            comment.text.chars().take(60).collect::<String>(),
         );
      }
   } else {
      println!("  - No comments");
   }
}

今、あなたがこのコードを実行するとき、あなたはもっとこのようなものが表示されます。

* Midnight, Claudette Colbert, 1939
  - Sansa Stark <sansa_stark@fakegmail.com>: Error ex culpa dignissimos assumenda voluptates vel. Qui inventore
  - Theon Greyjoy <theon_greyjoy@fakegmail.com>: Animi dolor minima culpa sequi voluptate. Possimus necessitatibu
  - Donna Smith <donna_smith@fakegmail.com>: Et esse nulla ducimus tempore aliquid. Suscipit iste dignissimos v

ゲーム・オブ・スローンズのサンサ・スタークが彼女のラテン語を本当に知っているのを見るのは良いことですよね?

これで、パイプラインでルックアップを操作する方法を示しました。$groupステージを使用して実際の集計を行う方法を示します。

ドキュメントをグループ化する $group

もう一度新しいパイプラインから始めます。

$group私はゆっくりと、これを打破ますので、ステージでは、理解することがより困難な段階の一つです。

次のコードから始めます。

// Define a struct to hold grouped data by year:
#[derive(Debug, Deserialize)]
struct YearSummary {
   _id: i32,
   #[serde(default)]
   movie_count: i64,
   #[serde(default)]
   movie_titles: Vec<String>,
}

// Some movies have "year" values ending with 'è'.
// This stage will filter them out:
let stage_filter_valid_years = doc! {
   "$match": {
      "year": {
         "$type": "number",
      }
   }
};

/*
* Group movies by year, producing 'year-summary' documents that look like:
* {
*     '_id': 1917,
* }
*/
let stage_group_year = doc! {
   "$group": {
      "_id": "$year",
   }
};

let pipeline = vec![stage_filter_valid_years, stage_group_year];

// Loop through the 'year-summary' documents:
let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the yearly summaries and print their debug representation:
while let Some(result) = results.next().await {
   let doc: YearSummary = bson::from_document(result?)?;
   println!("* {:?}", doc);
}

ではmovies、コレクション、年間のいくつかは、「E」の文字が含まれています。このデータベースには、いくつかの厄介な値が含まれています。この場合、ドキュメントはほんの一握りであり、それらを削除するだけでよいと思うので、数値ではない$matchドキュメントを除外するステージを追加しましたyear

このコードを実行すると、次のように表示されます。

* YearSummary { _id: 1959, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1980, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1977, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1933, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1998, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1922, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1948, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1965, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1950, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1968, movie_count: 0, movie_titles: [] }
...

各行は、集約パイプラインから発行されたドキュメントです。しかし、あなたはもう映画のドキュメントを見ていません。$group指定によるステージ基入力ドキュメント_id発現および出力それぞれ一意のための1つのドキュメント_id値。この場合、式はです$year。これは、yearフィールドの一意の値ごとに1つのドキュメントが発行されることを意味します。放出される各ドキュメントには、グループ化されたドキュメントのデータを集約して生成された値も含まれる可能性があります(通常は含まれます)。現在、YearSummary文書は、デフォルト値を使用しているmovie_countmovie_titles。それを修正しましょう。

ステージ定義を次のように変更します。

let stage_group_year = doc! {
   "$group": {
      "_id": "$year",
      // Count the number of movies in the group:
      "movie_count": { "$sum": 1 },
   }
};

これmovie_countにより1、グループ内のすべてのドキュメントに追加した結果を含むフィールドが追加されます。つまり、グループ内の映画ドキュメントの数をカウントします。ここでコードを実行すると、次のように表示されます。

* YearSummary { _id: 2005, movie_count: 758, movie_titles: [] }
* YearSummary { _id: 1999, movie_count: 542, movie_titles: [] }
* YearSummary { _id: 1943, movie_count: 36, movie_titles: [] }
* YearSummary { _id: 1926, movie_count: 9, movie_titles: [] }
* YearSummary { _id: 1935, movie_count: 40, movie_titles: [] }
* YearSummary { _id: 1966, movie_count: 116, movie_titles: [] }
* YearSummary { _id: 1971, movie_count: 116, movie_titles: [] }
* YearSummary { _id: 1952, movie_count: 58, movie_titles: [] }
* YearSummary { _id: 2013, movie_count: 1221, movie_titles: [] }
* YearSummary { _id: 1912, movie_count: 2, movie_titles: [] }
...

のように、グループからのデータを要約できるアキュムレータ演算子がいくつかあり $sumます。放出されたドキュメント内のすべての映画タイトルの配列を作成したい場合は"movie_titles": { "$push": "$title" },$groupステージに追加できます。その場合、次のYearSummaryようなインスタンスが得られます。

* YearSummary { _id: 1986, movie_count: 206, movie_titles: ["Defense of the Realm", "F/X", "Mala Noche", "Witch from Nepal", ... ]}

次のステージを追加して、結果を並べ替えます。

let stage_sort_year_ascending = doc! {
   "$sort": {"_id": 1}
};

let pipeline = vec! [
   stage_filter_valid_years,  // Match numeric years
   stage_group_year,
   stage_sort_year_ascending, // Sort by year (which is the unique _id field)
]

$matchステージはパイプラインの開始に$sort追加され、は終了に追加されることに注意してください。一般的なルールとして、パイプラインの早い段階でドキュメントを除外して、後の段階で処理するドキュメントが少なくなるようにする必要があります。また、パイプラインがコレクションに割り当てられた適切なインデックスを利用できる可能性が高くなります。

このクイックスタートシリーズのすべてのサンプルコードは、GitHubにあります

を使用した集計$groupは、データに関する興味深いことを発見するための優れた方法です。この例では、毎年作られる映画の数を示していますが、各国の映画に関する情報を見たり、さまざまな俳優が作った映画を見たりすることも興味深いでしょう。

何を学んだの?

ドキュメントをフィルタリング、グループ化、および他のコレクションと結合するための集約パイプラインを構築する方法を学習しました。$limitパイプラインの開始時にステージを配置すると、開発をスピードアップするのに役立つ可能性があることを学んだことを願っています(ただし、本番環境に移行する前に削除する必要があります)。また、フィルタリング式をパイプラインの最後ではなく最初に配置するなど、いくつかの基本的な最適化のヒントも学びました。

あなたを介して行ってきたように、あなたはおそらくがあることに気づいただろう トン異なるの ステージタイプ演算子、および アキュムレータ事業者は。アグリゲーションパイプラインのさまざまなコンポーネントの使用方法を学ぶことは、開発者としてMongoDBを効果的に使用することを学ぶ上で重要です。

アグリゲーションパイプラインを操作するのが大好きです。パイプラインで何ができるかにいつも驚いています。

リンク: https://www.mongodb.com/developer/quickstart/rust-quickstart-aggregation/

#rust #mongodb 

What is GEEK

Buddha Community

Rust言語の集約パイプライン
宇野  和也

宇野 和也

1638714180

Rust言語の集約パイプライン

MongoDBの集約パイプライン は、その最も強力な機能の1つです。これらを使用すると、MongoDBデータベース内のデータの集計、変換、結合などの操作を実行する一連のステージに分割された式を記述できます。これにより、MongoDBデータベース内のドキュメントとコレクション全体で計算と分析を行うことができます。

前提条件

このクイックスタートは、一連のRust投稿の2番目です。私の最初の投稿であるRustでの基本的なMongoDB操作から始めることを強くお勧め します。ここでは、ここで使用するサンプルデータを含む無料のMongoDBAtlasデータベースクラスターを正しくセットアップする方法を説明 します。それを読んで戻ってきてください。待ちます。これがないと、このクイックスタートガイドのコードを実行するためにデータベースを正しく設定できません。

要約すると、次のものが必要です。

Rustの最新バージョン。1.49を使用しましたが、最近のバージョンであれば問題なく動作するはずです。

選択したコードエディタ。私はお勧めVSコード錆アナライザ拡張

sample_mflixデータセットを含むMongoDBクラスター。これを設定する手順 は、このシリーズの最初のブログ投稿にあります。

入門

MongoDBの集約パイプラインは非常に強力であるため、最初は少し圧倒されるように思われるかもしれません。このため、ゆっくりと始めます。まず、私はあなたがすでにのMongoDBので達成できることを行動複製パイプラインアップ構築する方法を紹介しますfind()方法を、代わりに集約パイプラインを使用して$match$sort$limit段階。次に、で実行できる範囲を超えるクエリを作成する方法を示し、別のコレクションからの関連ドキュメントを含めるためにfind使用$lookupする方法を示します。最後に、$groupドキュメントをグループ化して新しいドキュメントの概要を作成する方法を示して、「アグリゲーション」を「アグリゲーションパイプライン」に入れます。

このクイックスタートシリーズのすべてのサンプルコードは、GitHubにあります。行き詰まったらチェックすることをお勧めしますが、そうでない場合は、チュートリアルに従って自分でコードを書く価値があります。

この投稿のすべてのパイプラインは、sample_mflix データベースのmoviesコレクションに対して実行され ます。これには、次のようなドキュメントが含まれています(同等のRust構造体よりも少し読みやすいため、Pythonでどのように表示されるかを示しています)。

{
   '_id': ObjectId('573a1392f29313caabcdb497'),
   'awards': {'nominations': 7,
               'text': 'Won 1 Oscar. Another 2 wins & 7 nominations.',
               'wins': 3},
   'cast': ['Janet Gaynor', 'Fredric March', 'Adolphe Menjou', 'May Robson'],
   'countries': ['USA'],
   'directors': ['William A. Wellman', 'Jack Conway'],
   'fullplot': 'Esther Blodgett is just another starry-eyed farm kid trying to '
               'break into the movies. Waitressing at a Hollywood party, she '
               'catches the eye of alcoholic star Norman Maine, is given a test, '
               'and is caught up in the Hollywood glamor machine (ruthlessly '
               'satirized). She and her idol Norman marry; but his career '
               'abruptly dwindles to nothing',
   'genres': ['Drama'],
   'imdb': {'id': 29606, 'rating': 7.7, 'votes': 5005},
   'languages': ['English'],
   'lastupdated': '2015-09-01 00:55:54.333000000',
   'plot': 'A young woman comes to Hollywood with dreams of stardom, but '
            'achieves them only with the help of an alcoholic leading man whose '
            'best days are behind him.',
   'poster': 'https://m.media-amazon.com/images/M/MV5BMmE5ODI0NzMtYjc5Yy00MzMzLTk5OTQtN2Q3MzgwOTllMTY3XkEyXkFqcGdeQXVyNjc0MzMzNjA@._V1_SY1000_SX677_AL_.jpg',
   'rated': 'NOT RATED',
   'released': datetime.datetime(1937, 4, 27, 0, 0),
   'runtime': 111,
   'title': 'A Star Is Born',
   'tomatoes': {'critic': {'meter': 100, 'numReviews': 11, 'rating': 7.4},
               'dvd': datetime.datetime(2004, 11, 16, 0, 0),
               'fresh': 11,
               'lastUpdated': datetime.datetime(2015, 8, 26, 18, 58, 34),
               'production': 'Image Entertainment Inc.',
               'rotten': 0,
               'viewer': {'meter': 79, 'numReviews': 2526, 'rating': 3.6},
               'website': 'http://www.vcientertainment.com/Film-Categories?product_id=73'},
   'type': 'movie',
   'writers': ['Dorothy Parker (screen play)',
               'Alan Campbell (screen play)',
               'Robert Carson (screen play)',
               'William A. Wellman (from a story by)',
               'Robert Carson (from a story by)'],
   'year': 1937}

そこ多くのデータがありますが、私は上の中心にすることがあります_idtitleyear、およびcastフィールド。

最初の集約パイプライン

集約パイプラインは、コレクションのaggregate() メソッドを使用してmongodbモジュールによって実行されます 。

の最初の引数aggregate()は、実行される一連のパイプラインステージです。クエリと同様に、集計パイプラインの各ステージは BSONドキュメントです。これらdoc!は、前の投稿で紹介したマクロを使用して作成することがよくあります。

集約パイプラインは、コレクション内のすべてのデータを処理します。パイプラインの各ステージは通過するドキュメントに適用され、あるステージから放出されたドキュメントはすべて、残りのステージがなくなるまで次のステージへの入力として渡されます。この時点で、パイプラインの最後のステージから発行されたドキュメントは、への呼び出しと同様の方法で、カーソルとしてクライアントプログラムに返されますfind()

などの個々のステージは、$match特定の基準に一致するドキュメントのみを通過させるためのフィルターとして機能できます。他のステージのようなタイプの、$project$addFields、とは$lookup彼らがパイプラインを通過すると、個々の文書の内容を変更します。最後に、などの特定のステージタイプは、$group渡されたドキュメント全体に基づいて、まったく新しいドキュメントのセットを作成します。これらのステージはいずれも、MongoDB自体に格納されているデータを変更しません。プログラムに戻す前にデータを変更するだけです。$ outのように、パイプラインの結果をMongoDBに保存できるステージがあります、このクイックスタートでは取り上げません。

前回の投稿で使用したのと同じ環境で作業していると想定します。そのため、ファイルに依存関係としてmongodbクレートが既に構成されているCargo.toml必要があり.envMONGODB_URI環境変数を含むファイルが必要です。

検索と並べ替え

まず、以下をRustコードに貼り付けます。

// Load the MongoDB connection string from an environment variable:
let client_uri =
   env::var("MONGODB_URI").expect("You must set the MONGODB_URI environment var!");

// An extra line of code to work around a DNS issue on Windows:
let options =
   ClientOptions::parse_with_resolver_config(&client_uri, ResolverConfig::cloudflare())
      .await?;
let client = mongodb::Client::with_options(options)?;

// Get the 'movies' collection from the 'sample_mflix' database:
let movies = client.database("sample_mflix").collection("movies");

上記のコードは、データベース内のコレクションを指す、とCollection呼ばれるインスタンスを提供します。movie_collectionmovies

パイプラインを作成し、それをで実行してaggregateから、ループして結果の各映画の詳細を出力するコードを次に示します。プログラムに貼り付けます。

// Usually implemented outside your main function:
#[derive(Deserialize)]
struct MovieSummary {
   title: String,
   cast: Vec<String>,
   year: i32,
}

impl fmt::Display for MovieSummary {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
      write!(
         f,
         "{}, {}, {}",
         self.title,
         self.cast.get(0).unwrap_or(&"- no cast -".to_owned()),
         self.year
      )
   }
}

// Inside main():
let pipeline = vec![
   doc! {
      // filter on movie title:
      "$match": {
         "title": "A Star Is Born"
      }
   },
   doc! {
      // sort by year, ascending:
      "$sort": {
         "year": 1
      }
   },
];

// Look up "A Star is Born" in ascending year order:
let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the results, convert each of them to a MovieSummary, and then print out.
while let Some(result) = results.next().await {
   // Use serde to deserialize into the MovieSummary struct:
   let doc: MovieSummary = bson::from_document(result?)?;
   println!("* {}", doc);
}

このパイプラインには2つの段階があります。1つ目は$ matchステージで、これはfind()。を使用してコレクションをクエリするのと似ています。読み取り操作クエリに基づいて、ステージを通過するドキュメントをフィルタリングします。これはパイプラインの最初のステージであるため、その入力はmovieコレクション内のすべてのドキュメントです。$matchステージのクエリtitleは入力ドキュメントのフィールドでフィルタリングされるため、このステージから出力されるドキュメントは「スター誕生」というタイトルのみになります。

2番目のステージは$ sort ステージです。映画「アリー/スター誕生」のドキュメントのみがこのステージに渡されるため、結果として「アリー/スター誕生」と呼ばれるすべての映画が年のフィールドで並べ替えられ、最も古い映画が最初になります。

Aggregate()を呼び出すと、結果のドキュメントを指すカーソルが返されます。カーソルはストリームトレイトを実装します。メソッドを提供するStreamExtをインポートしている限り、カーソルは他のストリームと同じようにループできますnext()。上記のコードは、返されたすべてのドキュメントをループし、タイトル、cast配列の最初の俳優、映画が制作された年で構成される短い要約を出力します。

上記のコードを実行すると、次のようになります。

* A Star Is Born, Janet Gaynor, 1937
* A Star Is Born, Judy Garland, 1954
* A Star Is Born, Barbra Streisand, 1976

コードのリファクタリング

上記の例のように、集約パイプライン全体を単一のデータ構造として構築することは可能ですが、必ずしも良い考えではありません。パイプラインは長く複雑になる可能性があります。このため、パイプラインの各ステージを個別の変数として構築し、最後に次のようにステージをパイプラインに結合することをお勧めします。

// Match title = "A Star Is Born":
let stage_match_title = doc! {
   "$match": {
      "title": "A Star Is Born"
   }
};

// Sort by year, ascending:
let stage_sort_year_ascending = doc! {
   "$sort": { "year": 1 }
};

// Now the pipeline is easier to read:
let pipeline = vec![stage_match_title, stage_sort_year_ascending];

結果の数を制限する

映画コレクションから「アリー/スター誕生」の最新作を入手したいと想像してみてください。

これは、次の順序で実行される3つの段階と考えることができます。

「アリー/スター誕生」の映画ドキュメントを入手してください。

年の降順で並べ替えます。

最初のドキュメントを除くすべてを破棄します。

最初の段階はすでに上記と同じstage_match_titleです。2番目のステージはと同じstage_sort_year_ascendingですが、値がに1変更されてい-1ます。3番目のステージは$ limitステージです。

変更され、新しい、このようなコードになります。

// Sort by year, descending:
let stage_sort_year_descending = doc! {
   "$sort": {
      "year": -1
   }
};

// Limit to 1 document:
let stage_limit_1 = doc! { "$limit": 1 };

let pipeline = vec![stage_match_title, stage_sort_year_descending, stage_limit_1];

上記の変更を行ってコードを実行すると、次の行が表示されます。

* A Star Is Born, Barbra Streisand, 1976

ちょっと待って!レディー・ガガとブラッドリー・クーパーとの素晴らしい作品のドキュメントがないのはなぜですか?

今学んだスキルを使って、コレクションの最新の日付を見つけてみませんか?それはあなたにあなたの答えを与えるでしょう!

これで、集計パイプラインを使用してコレクションのコンテンツをフィルタリング、並べ替え、制限する方法がわかりました。しかし、これらはあなたがすでに行うことができる単なる操作ですfind()!なぜこれらの複雑で新しい集約パイプラインを使用したいのですか?

読んでください、私の友人、そして私はあなたにMongoDBアグリゲーションパイプラインの真の力をお見せします。

他のコレクションで関連データを検索する

sample_mflixデータベースに隠れている汚い秘密があります。moviesコレクションだけでなく、と呼ばれるコレクションもありますcommentscommentsコレクション内のドキュメントは次のようになります。

{
   '_id': ObjectId('5a9427648b0beebeb69579d3'),
   'movie_id': ObjectId('573a1390f29313caabcd4217'),
   'date': datetime.datetime(1983, 4, 27, 20, 39, 15),
   'email': 'cameron_duran@fakegmail.com',
   'name': 'Cameron Duran',
   'text': 'Quasi dicta culpa asperiores quaerat perferendis neque. Est animi '
           'pariatur impedit itaque exercitationem.'}

映画へのコメントです。なぜ人々がこれらの映画にラテン語のコメントを書いているのかわかりませんが、それで行きましょう。2番目のフィールドは、コレクション内のドキュメントmovie_id,_id値に対応しますmovies

だから、映画に関連したコメントです!

MongoDBを使用すると、映画をクエリしたり、リレーショナルデータベースのJOINなどの関連コメントを埋め込んだりできますか? はい、そうです$ lookupステージで。

別のコレクションから関連ドキュメントを取得し、それらをプライマリコレクションのドキュメントに埋め込む方法を説明します。

まず、MovieSummary構造体の定義を変更してcommentsrelated_commentsBSONフィールドからロードされたフィールドを持つようにします。ドキュメントにComment含まれるデータのサブセットを含む構造体を定義しcommentsます。

#[derive(Deserialize)]
struct MovieSummary {
   title: String,
   cast: Vec<String>,
   year: i32,
   #[serde(default, rename = "related_comments")]
   comments: Vec<Comment>,
}

#[derive(Debug, Deserialize)]
struct Comment {
   email: String,
   name: String,
   text: String,
}

次に、新しいパイプラインを最初から作成し、次の手順から始めます。

// Look up related documents in the 'comments' collection:
let stage_lookup_comments = doc! {
   "$lookup": {
      "from": "comments",
      "localField": "_id",
      "foreignField": "movie_id",
      "as": "related_comments",
   }
};

// Limit to the first 5 documents:
let stage_limit_5 = doc! { "$limit": 5 };

let pipeline = vec![
   stage_lookup_comments,
   stage_limit_5,
];

let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the results and print a summary and the comments:
while let Some(result) = results.next().await {
   let doc: MovieSummary = bson::from_document(result?)?;
   println!("* {}, comments={:?}", doc, doc.comments);
}

私が呼んstage_lookup_comments$lookupステージはステージです。この$lookupステージcommentsでは、同じムービーIDを持つコレクションからドキュメントを検索します。一致するコメントはrelated_comments、という名前のBSONフィールドに配列としてリストされ、配列値には、この映画の_id値がmovie_id。であるすべてのコメントが含まれます。

$limit圧倒されることなく適度な量の出力があることを確認するために、ステージを追加しました。

次に、コードを実行します。

上記のパイプラインの実行がかなり遅いことに気付くかもしれません。これには2つの理由があります。

23.5kの映画ドキュメントと50kのコメントがあります。

commentsコレクションに欠落しているインデックスがあります。インデックスについて教えるために、意図的に欠落しています。

インデックスの問題を修正する方法については、ここでは説明しません。これについては、このシリーズの後半の投稿で、インデックスに焦点を当てて説明します。代わりに、開発中に低速の集約パイプラインを操作するための秘訣を紹介します。

パイプラインを作成してテストしている間、遅いパイプラインでの作業は苦痛です。 ただし、パイプライン$limit開始時に一時的なステージを配置すると、クエリが高速になります(ただし、データセット全体を実行していないため、結果が異なる場合があります)。

このパイプラインを書いていたとき、私はの最初の段階を持っていました{ "$limit": 1000 }

パイプラインの作成が終了したら、最初のステージをコメントアウトして、パイプラインがコレクション全体で実行されるようにすることができます。 最初のステージを削除することを忘れないでください。そうしないと、間違った結果が得られます。

上記の集約パイプラインは、5つの映画ドキュメントの要約を印刷します。私はあなたの映画の要約のほとんどまたはすべてがこれで終わることを期待しています:comments=[]

配列の長さのマッチング

良ければ、配列にいくつかのドキュメントがあるかもしれませんが、ほとんどの映画にはコメントがないため、それはありそうにありません。次に、コメントが3つ以上ある映画のみに一致するようにいくつかのステージを追加する方法を示します。

理想的に$matchは、related_commentsフィールドの長さを取得し、それを式と照合する単一のステージを追加できるはず{ "$gt": 2 }です。この場合、実際には2つのステップです。

フィールドcomment_countの長さを含むフィールド(これを呼びます)を追加しrelated_commentsます。

の値がcomment_count2より大きい場合に一致します。

2つのステージのコードは次のとおりです。

// Calculate the number of comments for each movie:
let stage_add_comment_count = doc! {
   "$addFields": {
      "comment_count": {
         "$size": "$related_comments"
      }
   }
};

// Match movie documents with more than 2 comments:
let stage_match_with_comments = doc! {
   "$match": {
      "comment_count": {
         "$gt": 2
      }
   }
};

2つのステージは、$lookupステージの後と$limit5つのステージの前にあります。

let pipeline = vec![
   stage_lookup_comments,
   stage_add_comment_count,
   stage_match_with_comments,
   limit_5,
]

ここにいる間、このコードの出力をクリーンアップして、コメントを少し適切にフォーマットします。

let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the results and print a summary and the comments:
while let Some(result) = results.next().await {
   let doc: MovieSummary = bson::from_document(result?)?;
   println!("* {}", doc);
   if doc.comments.len() > 0 {
      // Print a max of 5 comments per movie:
      for comment in doc.comments.iter().take(5) {
         println!(
            "  - {} <{}>: {}",
            comment.name,
            comment.email,
            comment.text.chars().take(60).collect::<String>(),
         );
      }
   } else {
      println!("  - No comments");
   }
}

今、あなたがこのコードを実行するとき、あなたはもっとこのようなものが表示されます。

* Midnight, Claudette Colbert, 1939
  - Sansa Stark <sansa_stark@fakegmail.com>: Error ex culpa dignissimos assumenda voluptates vel. Qui inventore
  - Theon Greyjoy <theon_greyjoy@fakegmail.com>: Animi dolor minima culpa sequi voluptate. Possimus necessitatibu
  - Donna Smith <donna_smith@fakegmail.com>: Et esse nulla ducimus tempore aliquid. Suscipit iste dignissimos v

ゲーム・オブ・スローンズのサンサ・スタークが彼女のラテン語を本当に知っているのを見るのは良いことですよね?

これで、パイプラインでルックアップを操作する方法を示しました。$groupステージを使用して実際の集計を行う方法を示します。

ドキュメントをグループ化する $group

もう一度新しいパイプラインから始めます。

$group私はゆっくりと、これを打破ますので、ステージでは、理解することがより困難な段階の一つです。

次のコードから始めます。

// Define a struct to hold grouped data by year:
#[derive(Debug, Deserialize)]
struct YearSummary {
   _id: i32,
   #[serde(default)]
   movie_count: i64,
   #[serde(default)]
   movie_titles: Vec<String>,
}

// Some movies have "year" values ending with 'è'.
// This stage will filter them out:
let stage_filter_valid_years = doc! {
   "$match": {
      "year": {
         "$type": "number",
      }
   }
};

/*
* Group movies by year, producing 'year-summary' documents that look like:
* {
*     '_id': 1917,
* }
*/
let stage_group_year = doc! {
   "$group": {
      "_id": "$year",
   }
};

let pipeline = vec![stage_filter_valid_years, stage_group_year];

// Loop through the 'year-summary' documents:
let mut results = movies.aggregate(pipeline, None).await?;
// Loop through the yearly summaries and print their debug representation:
while let Some(result) = results.next().await {
   let doc: YearSummary = bson::from_document(result?)?;
   println!("* {:?}", doc);
}

ではmovies、コレクション、年間のいくつかは、「E」の文字が含まれています。このデータベースには、いくつかの厄介な値が含まれています。この場合、ドキュメントはほんの一握りであり、それらを削除するだけでよいと思うので、数値ではない$matchドキュメントを除外するステージを追加しましたyear

このコードを実行すると、次のように表示されます。

* YearSummary { _id: 1959, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1980, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1977, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1933, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1998, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1922, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1948, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1965, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1950, movie_count: 0, movie_titles: [] }
* YearSummary { _id: 1968, movie_count: 0, movie_titles: [] }
...

各行は、集約パイプラインから発行されたドキュメントです。しかし、あなたはもう映画のドキュメントを見ていません。$group指定によるステージ基入力ドキュメント_id発現および出力それぞれ一意のための1つのドキュメント_id値。この場合、式はです$year。これは、yearフィールドの一意の値ごとに1つのドキュメントが発行されることを意味します。放出される各ドキュメントには、グループ化されたドキュメントのデータを集約して生成された値も含まれる可能性があります(通常は含まれます)。現在、YearSummary文書は、デフォルト値を使用しているmovie_countmovie_titles。それを修正しましょう。

ステージ定義を次のように変更します。

let stage_group_year = doc! {
   "$group": {
      "_id": "$year",
      // Count the number of movies in the group:
      "movie_count": { "$sum": 1 },
   }
};

これmovie_countにより1、グループ内のすべてのドキュメントに追加した結果を含むフィールドが追加されます。つまり、グループ内の映画ドキュメントの数をカウントします。ここでコードを実行すると、次のように表示されます。

* YearSummary { _id: 2005, movie_count: 758, movie_titles: [] }
* YearSummary { _id: 1999, movie_count: 542, movie_titles: [] }
* YearSummary { _id: 1943, movie_count: 36, movie_titles: [] }
* YearSummary { _id: 1926, movie_count: 9, movie_titles: [] }
* YearSummary { _id: 1935, movie_count: 40, movie_titles: [] }
* YearSummary { _id: 1966, movie_count: 116, movie_titles: [] }
* YearSummary { _id: 1971, movie_count: 116, movie_titles: [] }
* YearSummary { _id: 1952, movie_count: 58, movie_titles: [] }
* YearSummary { _id: 2013, movie_count: 1221, movie_titles: [] }
* YearSummary { _id: 1912, movie_count: 2, movie_titles: [] }
...

のように、グループからのデータを要約できるアキュムレータ演算子がいくつかあり $sumます。放出されたドキュメント内のすべての映画タイトルの配列を作成したい場合は"movie_titles": { "$push": "$title" },$groupステージに追加できます。その場合、次のYearSummaryようなインスタンスが得られます。

* YearSummary { _id: 1986, movie_count: 206, movie_titles: ["Defense of the Realm", "F/X", "Mala Noche", "Witch from Nepal", ... ]}

次のステージを追加して、結果を並べ替えます。

let stage_sort_year_ascending = doc! {
   "$sort": {"_id": 1}
};

let pipeline = vec! [
   stage_filter_valid_years,  // Match numeric years
   stage_group_year,
   stage_sort_year_ascending, // Sort by year (which is the unique _id field)
]

$matchステージはパイプラインの開始に$sort追加され、は終了に追加されることに注意してください。一般的なルールとして、パイプラインの早い段階でドキュメントを除外して、後の段階で処理するドキュメントが少なくなるようにする必要があります。また、パイプラインがコレクションに割り当てられた適切なインデックスを利用できる可能性が高くなります。

このクイックスタートシリーズのすべてのサンプルコードは、GitHubにあります

を使用した集計$groupは、データに関する興味深いことを発見するための優れた方法です。この例では、毎年作られる映画の数を示していますが、各国の映画に関する情報を見たり、さまざまな俳優が作った映画を見たりすることも興味深いでしょう。

何を学んだの?

ドキュメントをフィルタリング、グループ化、および他のコレクションと結合するための集約パイプラインを構築する方法を学習しました。$limitパイプラインの開始時にステージを配置すると、開発をスピードアップするのに役立つ可能性があることを学んだことを願っています(ただし、本番環境に移行する前に削除する必要があります)。また、フィルタリング式をパイプラインの最後ではなく最初に配置するなど、いくつかの基本的な最適化のヒントも学びました。

あなたを介して行ってきたように、あなたはおそらくがあることに気づいただろう トン異なるの ステージタイプ演算子、および アキュムレータ事業者は。アグリゲーションパイプラインのさまざまなコンポーネントの使用方法を学ぶことは、開発者としてMongoDBを効果的に使用することを学ぶ上で重要です。

アグリゲーションパイプラインを操作するのが大好きです。パイプラインで何ができるかにいつも驚いています。

リンク: https://www.mongodb.com/developer/quickstart/rust-quickstart-aggregation/

#rust #mongodb