You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从PostgreSQL查询数据?基于NestJS、GraphQL、Flutter的需求

实现按Type分组的GraphQL查询方案

一、后端(NestJS + GraphQL + PostgreSQL)实现

1. 定义GraphQL返回类型

先定义匹配目标数据结构的GraphQL类型,支持两种实现方式:

SDL优先方式

创建GraphQL schema文件:

# src/graphql/collection.graphql
# 单个详情项
type CollectionDetail {
  name: String!
}

# 分组后的集合结构
type GroupedCollection {
  type: String!
  details: [CollectionDetail!]!
}

# 查询入口
type Query {
  getGroupedCollections: [GroupedCollection!]!
}

代码优先方式

用TypeScript装饰器定义实体:

// src/collection/entities/collection.entity.ts
import { ObjectType, Field } from '@nestjs/graphql';

@ObjectType()
export class CollectionDetail {
  @Field()
  name: string;
}

@ObjectType()
export class GroupedCollection {
  @Field()
  type: string;

  @Field(() => [CollectionDetail])
  details: CollectionDetail[];
}

2. 实现分组查询逻辑

推荐直接利用PostgreSQL的聚合函数在数据库层面完成分组,效率更高:

编写Service层逻辑

假设数据库表为collection_items,包含type和name字段:

// src/collection/collection.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CollectionItem } from './entities/collection-item.entity'; // 你的基础实体类
import { GroupedCollection } from './entities/collection.entity';

@Injectable()
export class CollectionService {
  constructor(
    @InjectRepository(CollectionItem)
    private readonly collectionItemRepo: Repository<CollectionItem>,
  ) {}

  async getGroupedCollections(): Promise<GroupedCollection[]> {
    // 直接执行SQL聚合查询
    const result = await this.collectionItemRepo.query(`
      SELECT 
        type,
        JSON_AGG(JSON_BUILD_OBJECT('name', name)) AS details
      FROM collection_items
      GROUP BY type
      ORDER BY type;
    `);
    return result;
  }
}

若偏好TypeORM QueryBuilder风格:

async getGroupedCollections(): Promise<GroupedCollection[]> {
  return this.collectionItemRepo
    .createQueryBuilder('item')
    .select('item.type', 'type')
    .addSelect('JSON_AGG(JSON_BUILD_OBJECT(\'name\', item.name))', 'details')
    .groupBy('item.type')
    .orderBy('item.type')
    .getRawMany();
}

编写Resolver层

将查询暴露给GraphQL接口:

// src/collection/collection.resolver.ts
import { Resolver, Query } from '@nestjs/graphql';
import { CollectionService } from './collection.service';
import { GroupedCollection } from './entities/collection.entity';

@Resolver()
export class CollectionResolver {
  constructor(private readonly collectionService: CollectionService) {}

  @Query(() => [GroupedCollection])
  getGroupedCollections() {
    return this.collectionService.getGroupedCollections();
  }
}

二、前端(Flutter + graphql_flutter)实现

1. 定义GraphQL查询文档

创建查询文件:

// lib/graphql/queries.dart
import 'package:graphql_flutter/graphql_flutter.dart';

const getGroupedCollectionsQuery = gql('''
  query GetGroupedCollections {
    getGroupedCollections {
      type
      details {
        name
      }
    }
  }
''');

2. 调用查询并渲染数据

使用Query组件实现数据获取和UI渲染:

// lib/screens/collection_screen.dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import '../graphql/queries.dart';

class CollectionScreen extends StatelessWidget {
  const CollectionScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('分组集合')),
      body: Query(
        options: QueryOptions(document: getGroupedCollectionsQuery),
        builder: (QueryResult result, {VoidCallback? refetch, FetchMore? fetchMore}) {
          // 处理加载状态
          if (result.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }
          // 处理异常
          if (result.hasException) {
            return Center(child: Text('加载失败:${result.exception.toString()}'));
          }
          // 解析数据
          final groupedCollections = result.data?['getGroupedCollections'] as List;
          // 渲染列表
          return ListView.builder(
            itemCount: groupedCollections.length,
            itemBuilder: (context, index) {
              final group = groupedCollections[index];
              return ExpansionTile(
                title: Text(group['type']),
                children: (group['details'] as List).map((detail) {
                  return ListTile(title: Text(detail['name']));
                }).toList(),
              );
            },
          );
        },
      ),
    );
  }
}

若使用Flutter Hooks,代码更简洁:

// lib/screens/collection_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import '../graphql/queries.dart';

class CollectionScreen extends HookWidget {
  const CollectionScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final queryResult = useQuery(QueryOptions(document: getGroupedCollectionsQuery));

    if (queryResult.isLoading) {
      return const Center(child: CircularProgressIndicator());
    }
    if (queryResult.hasException) {
      return Center(child: Text('加载失败:${queryResult.exception.toString()}'));
    }

    final groupedCollections = queryResult.data?['getGroupedCollections'] as List;

    return Scaffold(
      appBar: AppBar(title: const Text('分组集合')),
      body: ListView.builder(
        itemCount: groupedCollections.length,
        itemBuilder: (context, index) {
          final group = groupedCollections[index];
          return ExpansionTile(
            title: Text(group['type']),
            children: (group['details'] as List).map((detail) {
              return ListTile(title: Text(detail['name']));
            }).toList(),
          );
        },
      ),
    );
  }
}

三、可选扩展

  • 若需过滤特定类型,可在后端查询中添加参数,比如@Args('allowedTypes') allowedTypes: string[],并在SQL中加入WHERE type IN (:...allowedTypes)条件
  • 若需分页,可结合GraphQL分页规范调整查询和返回结构

内容的提问来源于stack exchange,提问作者Roeurb Navy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:01:06