オントロジーファンクションオブジェクトの関数API:オブジェクトセット

注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。

API:オブジェクトセット

オブジェクトセットは、単一のタイプのオブジェクトの順不同のコレクションを表します。Functions APIを使用してオブジェクトセットをフィルター処理する、定義されたリンクタイプに基づいて他のオブジェクトタイプに対してサーチアラウンドを実行する、または集計値を計算したり具体的なオブジェクトを取得したりすることができます。Functionへの入力として個々のオブジェクトを渡すだけでなく、オブジェクト検索APIを使用していつでもオブジェクトセットを検索することができます。

フィルター処理、並べ替え、集計は、オントロジーアプリで Searchable レンダーヒントが有効になっているプロパティに対してのみ動作します。これらのプロパティは検索のためにインデックス化されています。Searchable レンダーヒントを有効にする方法を学びます。

オブジェクト検索

Objects.search() インターフェースを使用すると、プロジェクトにインポートされたオブジェクトタイプの任意のものを検索するための検索を開始することができます。この例では、Functionは与えられた airportCode を使用して、その空港から出発したすべてのフライトを見つけます。次に、それらのフライトのすべての異なる目的地を見つけて返します。

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // TypeScriptのコード import { Function } from "@foundry/functions-api"; import { Objects } from "@foundry/ontology-api"; // FlightFunctionsクラスの定義 export class FlightFunctions { // 現在のフライトの目的地を取得する関数 @Function() public currentFlightDestinations(airportCode: string): Set<string> { // 出発空港コードが指定されたコードと一致するフライトを検索 const flightsFromAirport = Objects.search() .flights() .filter(flight => flight.departureAirportCode.exactMatch(airportCode)) .all(); // 到着空港コードのリストを作成 const destinations = flightsFromAirport.map(flight => flight.arrivalAirportCode!); // 重複をなくした目的地のセットを返す return new Set(destinations); } }

オブジェクトセットは、オブジェクトのリスト、オブジェクトリソースIDのリスト、またはオブジェクトセットリソースIDを検索対象のオブジェクトタイプに引数として渡すことで作成することもできます。例: Objects.search().flights([flight])

特定のタイプのオブジェクトセットを持っている場合、以下で説明されるように、そのセットに対してさまざまな操作を実行できます。

フィルタリング

オブジェクトセットの .filter() メソッドを使用すると、オブジェクトの検索可能プロパティに基づいてオブジェクトセットをフィルター処理できます。フィルターメソッドは、フィルタリング対象のプロパティのタイプに基づいたフィルター定義を取ります。

  • すべてのプロパティタイプは、.exactMatch() フィルターをサポートしており、そのプロパティ値で正確に一致するオブジェクトにフィルタリングします。これは、文字列の正確な一致をフィルタリングするために役立ちます (上記の例のように)、またはオブジェクトのプライマリキーにフィルタリングするために役立ちます (例:.filter(object => object.primaryKey.exactMatch(PrimaryKey)))。

    • プロパティが null または undefined であるかどうかを確認するには、hasProperty() メソッドを使用します。
  • 文字列プロパティはいくつかのキーワードフィルターをサポートしています。各メソッドの Code Assist のドキュメントを参照してください。

    • .phrase() は、指定されたフレーズを含む値に基づいてフィルタリングします。ただし、ハイフン、アンダースコア、またはピリオドで区切られた連結された単語の文字列値は、1つのフレーズとして扱われます。たとえば、"banana" を検索する場合、プロパティ値が "banana_pudding" のオブジェクトは返されません。
    • .phrasePrefix() は、指定されたフレーズとプレフィックスで始まる値に基づいてフィルタリングします。たとえば、banana.phrasePrefix() 検索は、プロパティ値が banana_pudding のオブジェクトを返します。pudding.phrasePrefix() 検索は、プロパティ値 banana_pudding と一致しません。
    • .prefixOnLastToken() は、指定されたフレーズを含む値に基づいてフィルタリングし、最後のトークンがプレフィックスとして扱われます。
    • .matchAnyToken().fuzzyMatchAnyToken() は、検索クエリをトークン (通常は個々の単語) に分割し、指定されたトークンのいずれかを含む値に基づいてフィルタリングします。fuzzy バージョンは、近似値を一致させることができます。検索クエリまたはプロパティ値にハイフン、アンダースコア、またはピリオドが含まれている場合、連結された単語であっても、1つのトークンまたはフレーズとして扱われます。
    • .matchAllTokens().fuzzyMatchAllTokens() は、検索クエリをトークン (通常は個々の単語) に分割し、指定されたトークンのすべてを含む値に基づいてフィルタリングします。fuzzy バージョンは、近似値を一致させることができます。
      • Fuzzy フィルターは、オプションの Fuzziness パラメーターを取ることができます。これは @foundry/functions-api からインポートされます。
      • 利用可能な Fuzziness オプションの説明は、ElasticSearch のドキュメントで見つけることができます。詳細については、以下を参照してください。
  • 数値、日付、およびタイムスタンププロパティは、.range() フィルターをサポートしています。

    • Range フィルターには、.lt().lte().gt()、および gte() メソッドのセットがあり、それぞれ、より少ない / より少ないか等しい / より大きい / より大きいか等しい比較を実行します。
  • ブール型プロパティは、.isTrue() および .isFalse() フィルターをサポートしています。

  • Geohash プロパティは、.withinDistanceOf().withinPolygon()、および .withinBoundingBox() フィルターをサポートしています。

  • GeoShape プロパティは、.withinBoundingBox().intersectsBoundingBox().doesNotIntersectBoundingBox().withinPolygon().intersectsPolygon()、および doesNotIntersectPolygon() フィルターをサポートしています。

  • リンクフィルターは、.isPresent() メソッドを使用して、特定のタイプのリンクされたオブジェクトがあるかどうかをフィルタリングできます。

  • 配列プロパティは、.contains() フィルターをサポートしており、与えられた値のいずれかを含む配列プロパティの値を持つオブジェクトにフィルタリングします。

フィルタの組み合わせ

@foundry/functions-api からエクスポートされた Filters API を使用して、フィルターを組み合わせることができます。利用可能なメソッドは次のとおりです。

  • and() は、すべての指定されたフィルターに合格するオブジェクトにオブジェクトセットをフィルタリングします。
  • or() は、いずれかの指定されたフィルターに合格するオブジェクトにオブジェクトセットをフィルタリングします。
  • not() は、指定されたフィルターを否定します。

以下の例では、and() を使用して、フライトオブジェクトセットをフライト目的地でフィルタリングできます:

Copied!
1 2 3 4 5 6 7 8 9 10 11 import { Filters } from "@foundry/functions-api"; // オブジェクトを検索 Objects.search() .flights() // 航空便を検索 .filter(flight => Filters.or( // 条件1: 目的地が "SFO" で乗客数が 100 人以上 Filters.and(flight.destination.exactMatch("SFO"), flight.passengerCount.gt(100)), // 条件2: 目的地が "LAX" で乗客数が 300 人以上 Filters.and(flight.destination.exactMatch("LAX"), flight.passengerCount.gt(300)), ))

上記のコードは、SFOに100人以上の乗客を乗せて到着したフライト、またはLAXに300人以上の乗客を乗せて到着したフライトにフィルター処理する。

警告

オブジェクトセットの .filter() メソッドでは、演算子 &&|| は使用されません。複数のフィルターを適用するには、上記の Filters のメソッドのいずれかを使用する(または and 条件を達成するために .filter() を複数回呼び出す)必要があります。

ファジー検索で文字列プロパティにフィルター処理する

オプショナルな fuzziness パラメーターを指定することで、ファジー一致の挙動をより微調整できます。fuzzinessを指定しない場合、検索対象のトークンの長さに基づいて自動的に編集距離が許可されます。編集距離を指定するためには、@foundry/functions-api から Fuzziness をインポートする必要があります。

任意のトークンにファジーマッチング

// オブジェクトを検索し、従業員をフィルタリングします。従業員の名前が "Michael" にあいまい検索で一致するものを選択します(編集距離が2以内のもの)
Objects.search().employee().filter(employee => employee.firstName.fuzzyMatchAnyToken("Michael", { fuzziness: Fuzziness.LEVENSHTEIN_TWO })).all();

上記のコードは、提供された検索語とのレーベンシュタイン距離が2以内のファーストネームを持つ従業員を返します。この例では、MichaelMichealMikhaelMichelMikhailMihail(ただし、Miguel などは含まれません)。検索語の正確さに自信がある場合は、より小さい編集距離(異なるレーベンシュタイン距離)で検索し、検索結果をさらに絞り込むことができます。

すべてのトークンをあいまいマッチさせる

Copied!
1 2 3 4 5 // Objects.search()メソッドを使用して、従業員を検索します。 // filter()メソッドを使用して、従業員のフルネームが"Michael Smith"とファジーマッチする従業員をフィルタリングします。 // ここで、ファジーネスはLEVENSHTEIN_ONE(レーベンシュタイン距離1)を使用します。これは、名前が完全一致でなくても、一文字以内の誤差を許容することを意味します。 // 最後に、all()メソッドを使用して、条件に一致するすべての従業員を取得します。 Objects.search().employee().filter(employee => employee.fullName.fuzzyMatchAllTokens("Michael Smith", { fuzziness: Fuzziness.LEVENSHTEIN_ONE })).all();

複数のトークンフレーズに対しても、ファジーフィルターを使用することができます。上記のコードは、従業員のフルネームに MichaelSmith の両方が含まれていて、それぞれのトークンで最大1つのエディットがある場合にマッチします。たとえば、 Mikhael Smitt (つまり、それぞれのレーベンシュタイン距離が1)という場合です。 fuzzyMatchAllTokens または fuzzyMatchesAllTokens フィルターでは、トークンの順序は考慮されません。

文字列配列プロパティのファジーマッチ

配列ベースのプロパティに対するすべてのフィルターは、その基礎となるタイプで利用可能なメソッドを使用できます。たとえば、文字列配列プロパティは、文字列プロパティで利用可能な任意のメソッドに基づいてフィルター処理されることができますが、メソッドの命名はわずかに異なる場合があります。配列プロパティのフィルタリングでは、配列要素の中から1つがマッチすれば、そのオブジェクトが返されます。

周辺検索

周辺検索の制限

メモリにロードされたオブジェクトセット .all() または .allAsync() は、最大3回の周辺検索が許可されています。3回以上の周辺検索を使用すると、エラーがスローされます。

ユーザーのオブジェクトセットのオブジェクトタイプに基づいて、周辺検索 メソッドが生成され、ユーザーのオブジェクトセットのオブジェクトタイプに基づいてリンクを移動できるようになります。以下の例では、出発コードに基づいてフライトのオブジェクトセットをフィルターし、それらのフライトの乗客に周辺検索を行います。これにより、さらにフィルタリングまたは周辺検索が可能な乗客のオブジェクトセットが生成されます。

Copied!
1 2 3 4 5 // 空港から出発する乗客を取得するコード const passengersDepartingFromAirport = Objects.search() .flights() // 航空便を検索 .filter(flight => flight.departureAirportCode.exactMatch(airportCode)) // 出発空港コードが一致する便をフィルタリング .searchAroundPassengers(); // その便の周りの乗客を検索

Search Around メソッドは、プロジェクトにインポートされたリンクタイプに対してのみ生成されます。リンクタイプのインポート方法については、チュートリアル を参照してください。

パフォーマンス上の理由から、現在、1回の検索で実行できる Search Around 操作の数は、3 つに制限されています。Search Around の深さが3つ以上の検索を実行しようとすると、実行時に検索が失敗します。

K-最近傍法(KNN)

KNNの制限事項

KNN は、OSv2 にインデックスされたオブジェクトタイプでのみサポートされています。k 値は 0 < K <= 100 の範囲に制限されています。また、検索ベクトルはインデックスに使用されたものと同じサイズでなければならず、2048 次元の制限があります。これらの制限を超えるとエラーがスローされます。

埋め込みプロパティを持つオブジェクトタイプは、KNN 検索で利用できるようになります。これらの検索では、提供された埋め込みパラメーターに最も近い埋め込みプロパティを持つ k 値のオブジェクトが返されます。以下の例では、提供された映画の脚本に最も類似した映画を返します。クエリ時に埋め込みを生成するには、モデリングのライブデプロイメント の設定が必要です。

functions.json にて、enableVectorPropertiestrue に設定していることを確認してください。

Copied!
1 2 3 4 5 6 7 8 9 10 11 import { Objects } from "@foundry/ontology-api"; const kValue: number = 2; // VectorはFML Liveから生成できるか、既存のオブジェクトから取得できる const vector: Double[] = [0.7, 0.1, 0.3]; const movies: Movies[] = Objects.search() .movies() // obj.vectorProperty.near(vector, { kValue }) を使用して、最も近いk個の隣接点を検索 .nearestNeighbors(obj => obj.vectorProperty.near(vector, { kValue })) .orderByRelevance() // 関連性の高い順に並べ替え .take(kValue); // kValueの数だけ取得

完全なセマンティック検索ワークフローの例として、セマンティック検索ワークフローガイドを参照してください。

セット操作

同じオブジェクトタイプのオブジェクトセットは、セット操作を使用して様々な方法で組み合わせることができます:

  • .union() は、与えられたオブジェクトセットのいずれかに存在するオブジェクトで構成される新しいオブジェクトセットを作成します。
  • .intersect() は、与えられたオブジェクトセットのすべてに存在するオブジェクトで構成される新しいオブジェクトセットを作成します。
  • .subtract() は、与えられたオブジェクトセットに存在する任意のオブジェクトを削除します。

全オブジェクトの取得

.all() および .allAsync() メソッドは、オブジェクトセット内のすべてのオブジェクトを取得します。一度に多すぎるオブジェクトをロードしようとすると、ユーザーの関数の実行に失敗する可能性があります。現在、一度にロードできるオブジェクトの最大数は 100,000 です。しかし、10,000 オブジェクト以上をロードすると、ユーザーの関数の実行がタイムアウトする可能性もあります。関数の時間とスペースの制限について詳しくはこちら。

.allAsync() メソッドを使用すると、オブジェクトセット内のすべてのオブジェクトに解決する Promise を取得できます。これは、複数のオブジェクトセットからのデータを並行してロードする際に便利です。

ソートと制限

全オブジェクトを取得する代わりに、オブジェクトセットにソート条件を適用し、ロードするオブジェクトの数を特定の数に制限することで、制限した数のオブジェクトをロードできます。これを行うために、以下のメソッドを使用できます:

  • .orderBy() は、検索可能なプロパティを指定してソートし、ソート方向を指定することができます。このメソッドで選択可能なプロパティは、ソート可能なタイプ(数字、日付、文字列)のもののみです。複数のプロパティでソートするには、.orderBy() を複数回呼び出すことができます。
  • .orderByRelevance() は、オブジェクトが提供されたフィルターにどれだけ一致するかの順序でオブジェクトを返すことを指定します。最も関連性の高いものから最初にリストされます。クエリ用語がプロパティ値に出現する頻度、すべてのオブジェクトでの用語の出現頻度などを考慮に入れた複雑な判断で、特定のオブジェクトのプロパティ値に対するクエリ用語の関連性を決定します。.exactMatch() フィルターのみを行ったり、非文字列プロパティにフィルター処理を行った場合、関連性はあまり適切ではありません。.orderBy().orderByRelevance() のどちらか一方のみが1回の検索で使用できることに注意してください。
  • .take().takeAsync() は、セットから指定された数のオブジェクトを取得することを可能にします。これらのメソッドは、ソートを指定した後でのみ利用可能です。

例えば、以下のコードは、最も早い開始日を持つ 10 人の従業員を取得します:

Copied!
1 2 3 4 5 6 7 8 // Objects.search() メソッドを呼び出す Objects.search() // 従業員(employees)に対して検索を行う .employees() // 開始日(startDate)で昇順に並び替える .orderBy(e => e.startDate.asc()) // 最初の10人の従業員を取得する .take(10)

もう一つの例として、保険会社の事故の申請文書を含むオブジェクトタイプ claims があるとしましょう。赤い車と鹿の事故に関する特定の申請を見つけたいとします。.orderByRelevance() 行がない場合、redcarcollisionwith、または deer のいずれかの単語を含む結果が上位 10 件に表示されることがあります。.orderByRelevance() 行があると、最初の 10 件の結果は、最も検索用語を含む申請が上位に表示されるため、最も関連性の高い申請が最初に表示されます。

// Objects.search() はオブジェクトの検索を行います。
const results = Objects.search()
    // .claims() はクレーム(主張や要求)を取得します。
    .claims()
    // .filter() は指定した条件に一致するドキュメントをフィルタリングします。
    // ここでは "red car collision with deer" の任意の単語に一致するテキストを持つドキュメントを検索します。
    .filter(doc => doc.text.matchAnyToken("red car collision with deer"))
    // .orderByRelevance() は検索結果を関連性の高い順に並べ替えます。
    .orderByRelevance()
    // .take(10) は上位10件の検索結果を取得します。
    .take(10)

集計の計算

集計の制限

Objects APIから返される集計は、合計で10,000個のバケットに制限されています。この制限を超えるとエラーが発生します。

.topValues()を使用してバケット化する場合、データが1,000個以上の異なる値を持っていると、結果はおおよそのものになります。その場合、トップ値のリストは正確でない可能性があります。

プロパティによるオブジェクトのグループ化

多くの場合、オブジェクトセット内のすべてのオブジェクトをロードする必要はありません。代わりに、バケット化した集計値をロードしてさらなる分析を行うことができます。

集計の計算を始めるには、オブジェクトセットに.groupBy()メソッドを呼び出します。これにより、オブジェクトセット内のオブジェクトタイプの検索可能プロパティの1つに基づいてバケット化を指定することができます。たとえば、次のコードは、社員を開始日でグループ化します:

Copied!
1 2 3 4 5 6 // オブジェクトを検索 Objects.search() // 従業員を検索 .employees() // 開始日でグループ化(日単位) .groupBy(e => e.startDate.byDays())

プロパティをバケットに分けるための指定を行う際には、プロパティのタイプに応じてバケット分けの方法について追加情報を提供する必要があります:

  • boolean プロパティについては、唯一のオプションは .topValues() です。これにより、truefalse の2つのバケットが返されます。
  • 文字列プロパティについては、2つのオプションがあります:
    • .topValues():応答時間を短縮し、カーディナリティが小さいプロパティに対して使用します。文字列プロパティの上位1,000個の値に基づいてバケット分けします。この制限は、返される集約が過度に大きくならないようにするためのものです。
    • .exactValues():より正確な集約と、高カーディナリティプロパティに対して10,000個を超えるバケットを考慮する可能性を提供します。考慮されるバケットの数は .exactValues({"maxBuckets": numBuckets}) を通じて指定できます。ここで numBuckets は0から10,000の間の整数値でなければなりません。この方法では、考慮する結果が多くなるため、応答時間が長くなる可能性があります。
  • 数値プロパティ(例:IntegerLongFloatDouble)については、バケット分けのオプションが2つあります:
    • .byRanges() は、使用するべき正確な範囲を指定できます。例えば、.byRanges({ min: 0, max: 50 }, { min: 50, max: 100 }) を使用して、ここで指定する[0, 50]と[50, 100]の2つの範囲にオブジェクトをバケットに分けることができます。範囲の min は含まれ、max は除外されます。min または max のいずれかを省略して、-∞から max または min から∞の値を含むバケットを表現することもできます。
    • .byFixedWidth() は、各バケットの幅を指定します。例えば、.byFixedWidth(50) を使用して、各々が幅50を持つ範囲にオブジェクトをバケットに分けることができます。
  • LocalDate プロパティについては、簡単にバケット分けできるように、様々な便利なメソッドが提供されています:
    • .byYear()
    • .byQuarter()
    • .byMonth()
    • .byWeek()
    • .byDays() は、値を日単位でバケットに分けます。バケットの幅として使用する日数を指定することができます。
  • Timestamp プロパティについては、LocalDate と同じバケット分けのオプションが適用され、以下の追加オプションも使用できます:
    • .byHours() は、値を時間単位でバケットに分けます。バケットの幅として使用する時間数を指定することができます。
    • .byMinutes() は、値を分単位でバケットに分けます。バケットの幅として使用する分数を指定することができます。
    • .bySeconds() は、値を秒単位でバケットに分けます。バケットの幅として使用する秒数を指定することができます。
  • Array プロパティについては、バケット分けのオプションは配列内の要素のタイプによって決定されます。特に、Array<PropertyType> に対しては PropertyType と同じバケット分け方法が得られます(例えば、Array<boolean>boolean と同じバケット分け方法を得ます)。
    • 例えば、AliceとBobがそれぞれ ["US", "UK"]["US"] で働いていたという Array<string> という employeeSet がある場合、employeeSet.groupBy(e => e.pastCountries.exactValue()).count(){ "US": 2, "UK": 1 } を返します。

1つのプロパティによるグループ化の後、オプションで .segmentBy() メソッドを呼び出してさらなるバケット分けを行うことができます。これにより、2つの検索可能なプロパティによってバケット分けされた三次元の集約を計算することができます。例えば、従業員を開始日と役割でグループ化することができます。以下にその方法を示します:

Copied!
1 2 3 4 5 6 Objects.search() .employees() // 従業員の開始日を日単位でグループ化 .groupBy(e => e.startDate.byDays()) // 従業員の役割ごとにセグメント化 .segmentBy(e => e.role.topValues())

集約メトリクスの選択

オブジェクトセットをグループ化した後、各バケットに対して集約メトリクスを計算するためのさまざまな集約メソッドを呼び出すことができます。プロパティが必要なメソッドは、searchable とマークされたプロパティのみを受け入れます。可能な集約メソッドは次のとおりです:

  • .count() は各バケット内のオブジェクト数を単純に返します
  • .average() は指定された数値、タイムスタンプ、日付プロパティの平均数を返します
  • .max() は指定された数値、タイムスタンプ、日付プロパティの最大値を返します
  • .min() は指定された数値、タイムスタンプ、日付プロパティの最小値を返します
  • .sum() は指定された数値プロパティの値の合計を返します
  • .cardinality() は指定されたプロパティの異なる値の近似数を返します

これらのメソッドのいずれかを呼び出すと、TwoDimensionalAggregation または ThreeDimensionalAggregation のいずれかが返されます。最終集約メソッドのいずれかを呼び出す前に .segmentBy() を呼び出した場合、ThreeDimensionalAggregation が返されます。

これらの集約タイプの構造、含めて 有効なバケットタイプ についての詳細を学びましょう。

集約の結果は Promise でラップされていることに注意してください。集約を計算するにはリモートサービスからデータをロードする必要があるためです。Promise 結果をアンラップするには、async/await 構文を使用できます。

以下は、集約をロードし、結果として返す完全な例です。

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // "@foundry/functions-api" から "Function"と "ThreeDimensionalAggregation"をインポートする import { Function, ThreeDimensionalAggregation } from "@foundry/functions-api"; // "@foundry/ontology-api" から "Objects"をインポートする import { Objects } from "@foundry/ontology-api"; // "AggregationFunctions"というクラスをエクスポートする export class AggregationFunctions { // "employeesByRoleAndOffice"という関数を定義する。この関数は非同期であり、文字列の3次元集計を返すPromiseを返す @Function() public async employeesByRoleAndOffice(): Promise<ThreeDimensionalAggregation<string, string>> { // Objects.searchメソッドを使用して検索を開始し、employeeメソッドを使用して従業員をフィルタリングします。 // groupByメソッドを使って役職(タイトル)ごとにグループ化し、 // segmentByメソッドを使ってオフィスごとにセグメント化します。 // 最後に、countメソッドを使って各グループの数をカウントします。 return Objects.search() .employee() .groupBy(e => e.title.topValues()) .segmentBy(e => e.office.topValues()) .count(); } }

以下は、groupBy ステートメントを使用せずに集約する完全な例です:

Copied!
1 2 3 4 5 6 7 8 9 10 import { Function } from "@foundry/functions-api"; import { Objects } from "@foundry/ontology-api"; export class AggregationFunctions { @Function() public async employeesStats(): Promise<Double> { // count()がundefinedを返す場合はデフォルトでゼロにするすべての従業員の数を数えます return Objects.search().employee().count() ?? 0; } }

上記のコード例の適切な行を置き換えることで、groupByを使用せずに他の集約も実行できます。例えば:

  • 全従業員の数:Objects.search().employee().count(); (上記の例で見られるように)
  • 従業員の平均在籍年数:Objects.search().employee().average(e => e.tenure);
  • 従業員の最大在籍年数:Objects.search().employee().max(e => e.tenure);
  • 従業員の最小在籍年数:Objects.search().employee().min(e => e.tenure);
  • 全従業員の給与の合計:Objects.search().employee().sum(e => e.salary);
  • オフィスの数:Objects.search().employee().cardinality(e => e.office);

メモリ内で集約結果を操作する例として、カスタム集約の作成のガイドを試してみてください。