Data Connect クエリを実装する

Firebase Data Connect を使用すると、Google Cloud SQL で管理されている PostgreSQL インスタンスのコネクタを作成できます。これらのコネクタは、スキーマからデータを使用するためのクエリとミューテーションの組み合わせです。

スタートガイドでは、PostgreSQL の映画レビュー アプリのスキーマを紹介しました。

このガイドでは、クエリなどのデプロイ可能な管理オペレーションとアドホック管理オペレーションの両方も紹介しました。

  • デプロイ可能なクエリは、クライアント アプリから呼び出すために実装するクエリで、定義した API エンドポイントを使用します。これらは、サーバーにデプロイされたコネクタにバンドルされます。Data Connect ツールは、API に基づいてクライアント SDK を生成します。デプロイされたクエリは IAM ポリシーで保護されないため、Data Connect @auth ディレクティブを使用して保護してください。
  • アドホック管理クエリは、特権環境から実行されてデータを読み取ります。これらは、Firebase コンソールで作成して実行することも、Data Connect VS Code 拡張機能を使用してローカル開発環境で作成して実行することもできます。

このガイドでは、デプロイ可能なクエリについて詳しく説明します。

Data Connect クエリの機能

Data Connect では、PostgreSQL データベースで想定されるすべての方法で基本的なクエリを実行できます。

しかし、Data Connect の GraphQL 拡張機能を使用すると、高速で効率的なアプリの高度なクエリを実装できます。

  • 多くのオペレーションから返されるキー スカラーを使用して、レコードに対する繰り返しオペレーションを簡素化する
  • マルチステップ ミューテーション オペレーションの過程でクエリを実行してデータをルックアップし、コード行とサーバーへのラウンド トリップを節約します。

生成されたフィールドを使用してクエリを作成する

Data Connect オペレーションは、スキーマの型と型の関係に基づいて自動的に生成された Data Connect のフィールドのセットを拡張します。これらのフィールドは、スキーマを編集するたびにローカル ツールによって生成されます。

生成されたフィールドを使用すると、単一のテーブルから個々のレコードまたは複数のレコードを取得するクエリから、関連するテーブルから複数のレコードを取得するクエリまで、ますます複雑なクエリを実装できます。

スキーマに Movie 型と関連する Actor 型が含まれているとします。Data Connect は、moviemoviesactors_on_movies フィールドなどを生成します。


movie フィールドを使用したクエリ

movie フィールドは、Movie テーブル内の 1 つのレコードを表します。

このフィールドを使用して、キーで 1 つの映画をクエリします。

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}


movies フィールドを使用したクエリ

movies フィールドは、Movie テーブルのレコードのリストを表します。

このフィールドを使用して、複数の映画をクエリします(例: 特定の年のすべての映画)。

query GetMovies($myYear: Int!) {
  movies(where: { year: { eq: $myYear } }) { title }
}


actors_on_movies フィールドを使用したクエリ

actors_on_movies フィールドは、Actor テーブルと Movie テーブルを接続するレコードのリストを表します。このフィールドを使用して、特定の映画に関連付けられているすべての俳優をクエリします。

このフィールドを使用して、特定の映画に関連付けられているすべての俳優をクエリします。

  query GetActorsOnMovie($myKey: Movie_Key!) {
    actors_on_movies(where: { movie: { key: { eq: $myKey } } }) {
      actor { name }
    }
  }

クエリの必須要素

Data Connect クエリは、Data Connect 拡張機能を含む GraphQL クエリです。通常の GraphQL クエリと同様に、オペレーション名と GraphQL 変数のリストを定義できます。

Data Connect は、@auth などのカスタマイズされたディレクティブを使用して GraphQL クエリを拡張します。

したがって、次のクエリには次のものが含まれます。

  • query 型の定義
  • ListMoviesByGenre オペレーション(クエリ)名
  • 1 つのクエリ引数(ここでは String 型の $genre 変数)
  • 単一のディレクティブ @auth
  • 単一のフィールド movies
query ListMoviesByGenre($genre: String!) @auth(level: PUBLIC) {
  movies(where: { genre: { eq: $genre } }) {
    id
    title
  }
}

すべてのクエリ引数には、型宣言、String などの組み込み型、Movie などのカスタムのスキーマ定義型が必要です。

このガイドでは、複雑さを増すクエリのシグネチャについて説明します。最後に、デプロイ可能なクエリの構築に使用できる、強力で簡潔な関係式を紹介します。

クエリ内のキー スカラー

まず、キー スカラーについて説明します。

Data Connect は、各テーブルの主キーを表す特別なキー スカラーを定義します。これは {TableType}_Key で識別されます。これは、主キー値の JSON オブジェクトです。

キー スカラーは、ほとんどの自動生成された読み取りフィールドから返されるレスポンスとして取得するか、スカラーキーの構築に必要なすべてのフィールドを取得したクエリから取得します。

実行例の movie などの単一の自動クエリは、キー スカラーを受け入れるキー引数をサポートします。

キー スカラーをリテラルとして渡すことができます。ただし、キー スカラーを入力として渡す変数を定義できます。

クエリ

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}
    

レスポンス

{
  "data": {
    "movie": {
      "title": "Example Movie Title"
    }
  }
}
    

これらは、次のようにリクエスト JSON で指定できます(他のシリアル化形式でも指定できます)。

{
  # 
  "variables": {
    "myKey": {"id": "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"}
  }
}

カスタム スカラー解析により、変数を含むオブジェクト構文を使用して Movie_Key を構築することもできます。これは、何らかの理由で個々のコンポーネントを異なる変数に分割する場合に特に便利です。

基本的なクエリを作成する

クエリの作成を開始して、データベースから個々のレコードを取得したり、テーブルからレコードを一覧表示したりできます。結果を制限して並べ替えることもできます。

個々のレコードを取得する

最も簡単なクエリは、ID で単一のレコードを取得します。クエリでは、自動生成された movie フィールドが使用されます。

クエリ

query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    imageUrl
    genre
  }
}
    

レスポンス

{
  "data": {
    "movie": {
      "id": "some-uuid",
      "title": "Example Movie Title",
      "imageUrl": "https://example.com/movie.jpg",
      "genre": "Action"
    }
  }
}
    

テーブル内のすべてのレコードを取得する

Movies テーブルの映画の完全なリストからフィールドのサブセットを取得するには、クエリで自動生成された movies フィールドを使用します。実装は次のようになります。

クエリ

query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    genre
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "imageUrl": "https://example.com/movie.jpg",
        "genre": "Action"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title",
        "imageUrl": "https://example.com/another-movie.jpg",
        "genre": "Comedy"
      }
    ]
  }
}
    

orderBylimitoffset 演算子を使用する

当然のことながら、テーブルからすべてのレコードを一覧表示しても、有用性は限られます。

結果の順序付けとページネーションを実行できます。これらの引数は受け入れられますが、結果では返されません。

ここで、クエリは評価の高い上位 10 件の映画のタイトルを取得します。

クエリ

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      { "title": "Top Movie 1" },
      { "title": "Top Movie 2" },
      { "title": "Top Movie 3" }
      // ... other 7 movies
    ]
  }
}
    

たとえば、評価順に並べた映画の 11 ~ 20 番目を取得するユースケースがあります。

クエリ

query Movies11to20 {
  movies(orderBy: [{ rating: DESC }], limit: 10, offset: 10) {
    # graphql: list the fields from the results to return
    title
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      { "title": "Movie 11" },
      { "title": "Movie 12" },
      { "title": "Movie 13" }
      // ... other 7 movies
    ]
  }
}
    

クエリでエイリアスを使用する

Data Connect は、クエリでの GraphQL エイリアシングをサポートしています。エイリアスを使用すると、クエリの結果で返されるデータの名前を変更できます。1 つの Data Connect クエリで、複数のフィルタや他のクエリ オペレーションを 1 つの効率的なリクエストでサーバーに適用できます。これにより、複数の「サブクエリ」を一度に発行できます。返されるデータセットで名前の競合を回避するには、エイリアスを使用してサブクエリを区別します。

次のクエリでは、式でエイリアス mostPopularleastPopular を使用しています。

クエリ

query ReviewPopularitySpread($genre: String) {
  mostPopular: review(
    first: {
      where: {genre: {eq: $genre}},
      orderBy: {popularity: DESC}
    }
  ),
  leastPopular: review(
    last: {
      where: {genre: {eq: $genre}},
      orderBy: {popularity: DESC}
    }
  )
}
    

レスポンス

{
  "data": {
    "mostPopular": [
      { "popularity": 9 }
    ],
    "leastPopular": [
      { "popularity": 1 }
    ]
  }
}
    

クエリフィルタを使用する

Data Connect クエリは、一般的な SQL フィルタと順序付けオペレーションにすべてマッピングされます。

orderBy 演算子を使用して where でフィルタする

テーブル(およびネストされた関連付け)から一致するすべての行を返します。フィルタに一致するレコードがない場合は、空の配列を返します。

クエリ

query MovieByTopRating($genre: String) {
  mostPopular: movies(
    where: { genre: { eq: $genre } },
    orderBy: { rating: DESC }
  ) {
    # graphql: list the fields from the results to return
    id
    title
    genre
    description
  }
}
    

レスポンス

{
  "data": {
    "mostPopular": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "genre": "Action",
        "description": "A great movie"
      }
    ]
  }
}
    

null 値をテストしてフィルタする

isNull 演算子を使用して、null 値をテストできます。

クエリ

query ListMoviesWithoutDescription {
  movies(where: { description: { isNull: true }}) {
    id
    title
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

その他の演算子については、入力オブジェクト型のリファレンス ガイドをご覧ください。

値の比較でフィルタする

lt(小なり)や ge(大なりイコール)などの演算子を使用して、クエリ内の値を比較できます。

クエリ

query ListMoviesByRating($minRating: Int!, $maxRating: Int!) {
  movies(where: { rating: { ge: $minRating, lt: $maxRating }}) {
    id
    title
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

配列フィールドの includes 演算子と excludes 演算子でフィルタする

配列フィールドに指定された項目が含まれていることをテストできます。

次の例は、includes 演算子を示しています。

Data Connect は、includesAllexcludesexcludesAll などをサポートしています。整数、文字列、日付、その他のデータ型の演算子については、リファレンス ドキュメントの _ListFilter 見出しをご覧ください。

クエリ

query ListMoviesByTag($tag: String!) {
  movies(where: { tags: { includes: $tag }}) {
    # graphql: list the fields from the results to return
    id
    title
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      }
    ]
  }
}
    

文字列オペレーションと正規表現でフィルタする

クエリでは、正規表現などの一般的な文字列検索と比較演算子を使用できます。効率化のため、ここでは複数のオペレーションをバンドルし、エイリアスで区別しています。

query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
  prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
  suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
  contained: movies(where: {title: {contains: $contained}}) {...}
}

_or_and_not 演算子のロジックでフィルタする

より複雑なロジックには _or を使用します。Data Connect は、_and 演算子と _not 演算子もサポートしています。

クエリ

query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
  movies(
    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
  ) {
    # graphql: list the fields from the results to return
    title
  }
}
    

レスポンス

{
  "data": {
    "movies": [
      { "title": "Movie Title 1" },
      { "title": "Movie Title 2" }
    ]
  }
}
    

リレーショナル クエリを作成する

Data Connect クエリは、テーブル間の関係に基づいてデータにアクセスできます。スキーマで定義されたオブジェクト(1 対 1)または配列(1 対多)のリレーションを使用して、ネストされたクエリを作成できます。つまり、1 つのタイプのデータを、ネストされたタイプまたは関連するタイプのデータとともに取得できます。

このようなクエリでは、生成された読み取りフィールドでマジック Data Connect _on__via 構文を使用します。

スキーマのサンプルを確認してください。

多対 1

次に、_on_ 構文を示すクエリを見てみましょう。

クエリ

query MyReviews @auth(level: USER) {
  user(key: {id_expr: "auth.uid"}) {
    reviews: reviews_on_user {
      movie { name }
      rating
    }
  }
}
    

レスポンス

{
  "data": {
    "user": {
      "reviews": [
        {
          "movie": { "name": "Movie Title" },
          "rating": 5
        }
      ]
    }
  }
}
    

1 対 1

_on_ 構文を使用して 1 対 1 のクエリを作成できます。

クエリ

query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}
    

レスポンス

{
  "data": {
    "movie": {
      "movieMetadatas_on_movie": {
        "director": "Some Director"
      }
    }
  }
}
    

多対多

多対多クエリでは _via_ 構文を使用します。多対多クエリでは、指定された映画の俳優を取得できます。

クエリ

query MoviesActors($id: UUID!) @auth(level: USER) {
  movie(id: $id) {
     actors: actors_via_MovieActors {
        name
     }
  }
}
    

レスポンス

{
  "data": {
    "movie": {
      "actors": [
        {
          "name": "Actor Name"
        }
      ]
    }
  }
}
    

ただし、エイリアスを使用してより複雑なクエリを作成し、role に基づいてフィルタリングして、mainActorssupportingActors の結果で俳優と関連する映画を取得できます。これは多対多であるため、_via_ 構文が使用されます。

クエリ

query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
  movie(id: $movieId) {
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      name
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      name
    }
  }
  actor(id: $actorId) {
    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      title
    }
    supportingRoles: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      title
    }
  }
}
    

レスポンス

{
  "data": {
    "movie": {
      "mainActors": [
        {
          "name": "Main Actor Name"
        }
      ],
      "supportingActors": [
        {
          "name": "Supporting Actor Name"
        }
      ]
    },
    "actor": {
      "mainRoles": [
        {
          "title": "Main Role Movie Title"
        }
      ],
      "supportingRoles": [
        {
          "title": "Supporting Role Movie Title"
        }
      ]
    }
  }
}
    

集約クエリ

集計とは何か、集計を使用する理由

集計フィールドを使用すると、結果のリストに対して計算を実行できます。集計フィールドを使用すると、次のようなことができます。

  • レビューの平均スコアを求める
  • ショッピング カート内の商品の合計金額を求める
  • 評価が最も高い商品または最も低い商品を見つける
  • ストア内の商品数を数える

集計はサーバーで行われます。クライアントサイドで計算するよりも、次のようなメリットがあります。

  • アプリのパフォーマンスが向上する(クライアントサイドの計算を回避できるため)
  • データの下り(外向き)費用が削減される(すべての入力ではなく、集計結果のみを送信するため)。
  • セキュリティの強化(クライアントにデータセット全体ではなく集計データへのアクセス権を付与できるため)

集計のスキーマの例

このセクションでは、ストアフロントのサンプル スキーマに切り替えます。これは、集計の使用方法を説明するのに適しています。

  type Product @table {
    name: String!
    manufacturer: String!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

単純な集計

すべてのフィールドの _count

最も単純な集計フィールドは _count です。これは、クエリに一致する行数を返します。型内の各フィールドに対して、Data Connect はフィールドの型に応じて対応する集計フィールドを生成します。

クエリ

query CountProducts {
  products {
    _count
  }
}

回答
one

たとえば、データベースに 5 つの商品がある場合、結果は次のようになります。

{
  "products": [
    {
    "_count": 5
    }
  ]
}

すべてのフィールドには <field>_count フィールドがあり、そのフィールドに null 以外の値がある行の数をカウントします。

クエリ

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

レスポンス
field_count

たとえば、有効期限付きの商品が 3 つある場合、結果は次のようになります。

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
数値フィールドの _min、_max、_sum、_avg

数値フィールド(int、float、int64)にも <field>_min<field>_max<field>_sum<field>_avg があります。

クエリ

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

レスポンス
_min _max _sum _avg

たとえば、次の商品があるとします。

  • 商品 A: quantityInStock: 10price: 2.99
  • プロダクト B: quantityInStock: 5price: 5.99
  • プロダクト C: quantityInStock: 20price: 1.99

結果は次のようになります。

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
日付とタイムスタンプの _min と _max

日付とタイムスタンプのフィールドには <field>_min<field>_max があります。

クエリ

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

レスポンス
_min _maxdatetime

たとえば、次のような有効期限があるとします。

  • 商品 A: 2024-01-01
  • 商品 B: 2024-03-01
  • 商品 C: 2024-02-01

結果は次のようになります。

{
  "products": [
    {
    "expirationDate_max": "2024-03-01",
    "expirationDate_min": "2024-01-01"
    }
  ]
}

Distinct

distinct 引数を使用すると、フィールド(またはフィールドの組み合わせ)の一意の値をすべて取得できます。例:

クエリ

query ListDistinctManufacturers {
  products(distinct: true) {
    manufacturer
  }
}

レスポンス
distinct

たとえば、次のメーカーがあるとします。

  • 商品 A: manufacturer: "Acme"
  • 商品 B: manufacturer: "Beta"
  • 商品 C: manufacturer: "Acme"

結果は次のようになります。

{
  "products": [
    { "manufacturer": "Acme" },
    { "manufacturer": "Beta" }
  ]
}

集計フィールドで distinct 引数を使用して、個別の値を集計することもできます。例:

クエリ

query CountDistinctManufacturers {
  products {
    manufacturer_count(distinct: true)
  }
}

レスポンス
distinctonaggregate

たとえば、次のメーカーがあるとします。

  • 商品 A: manufacturer: "Acme"
  • 商品 B: manufacturer: "Beta"
  • 商品 C: manufacturer: "Acme"

結果は次のようになります。

{
  "products": [
    {
    "manufacturer_count": 2
    }
  ]
}

グループ化された集計

グループ化された集計を実行するには、型で集計フィールドと非集計フィールドを組み合わせて選択します。これにより、集計以外のフィールドの値が同じ一致する行がすべてグループ化され、そのグループの集計フィールドが計算されます。例:

クエリ

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

レスポンス
groupedaggregates

たとえば、次の商品があるとします。

  • 商品 A: manufacturer: "Acme"price: 2.99
  • プロダクト B: manufacturer: "Beta"price: 5.99
  • プロダクト C: manufacturer: "Acme"price: 1.99

結果は次のようになります。

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
グループ化された集計を含む havingwhere

having 引数と where 引数を使用して、指定した条件を満たすグループのみを返すこともできます。

  • having を使用すると、集計フィールドでグループをフィルタできます
  • where を使用すると、集計以外のフィールドに基づいて行をフィルタリングできます。

クエリ

query FilteredMostExpensiveProductByManufacturer {
  products(having: {price_max: {ge: 2.99}}) {
  manufacturer
  price_max
  }
}

レスポンス
havingwhere

たとえば、次の商品があるとします。

  • 商品 A: manufacturer: "Acme"price: 2.99
  • プロダクト B: manufacturer: "Beta"price: 5.99
  • プロダクト C: manufacturer: "Acme"price: 1.99

結果は次のようになります。

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}

テーブル間の集計

集計フィールドは、生成された一対多のリレーションシップ フィールドと組み合わせて使用することで、データに関する複雑な質問に回答できます。次に、例で使用できる個別のテーブル Manufacturer を含む変更されたスキーマを示します。

  type Product @table {
    name: String!
    manufacturer: Manufacturer!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

  type Manufacturer @table {
    name: String!
    headquartersCountry: String!
  }

集計フィールドを使用して、メーカーが製造している製品の数を調べるなどの操作を行うことができます。

クエリ

query GetProductCount($id: UUID) {
  manufacturers {
    name
    products_on_manufacturer {
      _count
    }
  }
}

回答
aggregatesacrosstables

たとえば、次のメーカーがあるとします。

  • メーカー A: name: "Acme"products_on_manufacturer: 2
  • メーカー B: name: "Beta"products_on_manufacturer: 1

結果は次のようになります。

{
  "manufacturers": [
    { "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
    { "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
  ]
}

高度なクエリを作成する: query フィールドを使用して、複数ステップのオペレーションでデータを読み取る

たとえば、挿入や更新を行う前に、ミューテーションの実行中にデータベースを読み取って既存のデータを検索し、検証したい状況はたくさんあります。これらのオプションを使用すると、ラウンド トリップ オペレーションが節約され、コストが削減されます。

Data Connect はこの機能をサポートしています。複数ステップのオペレーションをご覧ください。

次のステップ

関連情報: