实现 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 表中的单条记录。

使用此字段按键查询单个电影。

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 字段表示连接 ActorMovie 表的记录列表。使用此字段可查询与指定电影关联的所有演员。

使用此字段可查询与指定电影关联的所有演员。

  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 操作(查询)名称
  • 单个查询实参,此处为 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)支持接受键标量的 key 实参。

您可能会将键标量作为字面量传递。不过,您可以定义变量来传递关键标量作为输入。

查询

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 别名。借助别名,您可以重命名查询结果中返回的数据。单个 Data Connect 查询可以在一个高效的服务器请求中应用多个过滤条件或其他查询操作,从而一次性有效地发出多个“子查询”。为避免返回的数据集中出现名称冲突,您可以使用别名来区分子查询。

以下查询中的表达式使用了别名 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 过滤和排序操作。

使用 whereorderBy 运算符进行过滤

返回表(和嵌套关联)中的所有匹配行。如果没有记录与过滤条件匹配,则返回空数组。

查询

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"
      }
    ]
  }
}
    

使用 includesexcludes 运算符过滤数组字段

您可以测试数组字段是否包含指定项。

以下示例说明了 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 运算符逻辑进行过滤

对于更复杂的逻辑,请使用 _orData 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 查询可以根据表之间的关系访问数据。您可以使用架构中定义的对象(一对一)或数组(一对多)关系来执行嵌套查询,即提取一种类型的数据以及嵌套或相关类型的数据。

此类查询在生成的读取字段中使用神奇的 Data Connect _on__via 语法。

请务必查看示例架构

多对一

现在,我们来看一个查询,以说明 _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
        }
      ]
    }
  }
}
    

一对一

您可以使用 _on_ 语法编写一对一查询。

查询

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(含分组汇总)

您还可以使用 havingwhere 实参仅返回符合所提供条件的群组。

  • 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 支持此功能。 请参阅多步操作

后续步骤

您可能感兴趣的内容: