TypeORM:如何查询与字符串数组输入的多对多关系以查找所有字符串都应存在于相关实体列中的实体?

Posted

技术标签:

【中文标题】TypeORM:如何查询与字符串数组输入的多对多关系以查找所有字符串都应存在于相关实体列中的实体?【英文标题】:TypeORM: How to query ManyToMany relation with string array input to find entities where all strings should exist in the related entity's column? 【发布时间】:2022-01-13 11:21:10 【问题描述】:

我有一个 User 实体,它与 Profile 实体具有 OneToOne 关系,而 Profile 实体与 Category 实体具有 ManyToMany 关系。

// user.entity.ts

@Entity()
export class User 
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @OneToOne(() => Profile, 
    cascade: true,
    nullable: true,
  )
  @JoinColumn() // user owns the relationship (User table contains profileId). Use it only on one side of the relationship
  profile: Profile;

// profile.entity.ts

@Entity()
export class Profile 
  @PrimaryGeneratedColumn('uuid')
  id: number;

  @OneToOne(() => User, (user: User) => user.profile)
  user: User;

  @ManyToMany(() => Category, (category: Category) => category, 
    cascade: true,
    nullable: true,
  )
  @JoinTable()
  categories: Category[];

// category.entity.ts

@Entity()
export class Category 
  @PrimaryGeneratedColumn('uuid')
  id: number;

  @Column()
  name: string;

  @ManyToMany(() => Profile, (profile: Profile) => profile.categories, 
    nullable: true,
  )
  profiles: Profile[];

我的目标是获取所有用户实体,其中配置文件的类别名称都存在于字符串数组中作为输入,例如const categories = ['category1', 'category2']。到目前为止,将IN 与查询构建器一起使用使我接近了我的目标。

这是带有 IN 的查询:

const categories = ['category1', 'category2']

const users = await this.usersRepository
  .createQueryBuilder('user')
  .innerJoinAndSelect('user.profile', 'profile')
  .innerJoinAndSelect('profile.categories', 'categories')
  .where('categories.name IN (:...categories)', 
    categories,
  )
  .getMany();

我只想要category1category2 作为配置文件的多对多关系名称存在的用户。通过上面的查询,我还收到了只有这些值之一作为名称存在的用户。我目前的结构甚至可以做到这一点吗?

This 与我很接近,但 OP 有不相关的实体。

This 也很接近,但它只是一个用于过滤的字符串数组列。

我还想保留我当前的结构,因为可能想向类别实体添加一些其他列,例如订单。

更新:

我决定使用字符串数组而不是多对多关系,因为它满足我自己的要求。

// profile.entity.ts

@Column('text', 
  nullable: true,
  array: true,
)
categories?: string[];

更新后的查询:

const categories = ['category1', 'category2']

const users = await this.usersRepository
  .createQueryBuilder('user')
  .innerJoinAndSelect('user.profile', 'profile')
  .where('profile.categories::text[] @> (:categories)::text[]', 
    categories,
  )
  .getMany();

【问题讨论】:

【参考方案1】:

如果您使用的是 PostgreSQL,则可以使用 @> contains array operator。

const categories = ['category1', 'category2']

// untested code
const users = await this.usersRepository
  .createQueryBuilder('user')
  .innerJoinAndSelect('user.profile', 'profile')
  .innerJoin('profile.categories', 'categories')
  .groupBy('user.id')
  .addGroupBy('profile.id');
  .having('array_agg(categories.name::text) @> ARRAY[:...categories]', 
    categories,
  )
  .getMany();

它不是选择类别,而是将连接的类别聚合到一个数组中,并检查它是否是给定数组的超集。我无法使用 TypeORM 对此进行测试,所以我只是希望它可以处理数组构建语法,因为我在文档中的任何地方都找不到它。希望此解决方案对您有所帮助。

编辑:添加了缺失的 groupBy 和缺失的演员,如 cmets 中所述。

【讨论】:

感谢您的回答。它让我接近我想要的。我不得不在有条款中投出其中一个方面。 array_agg(categories.name)::text[]ARRAY[:...categories]::varchar[]。由于选择了配置文件,我还需要添加.addGroupBy('profile.id')。之后,我收到了预期的用户。但正如问题中所写,我还想选择类别,所以我需要一个.innerJoinAndSelect,但我还需要添加一个.addGroupBy('categories.id')。如果我现在有多个类别作为输入,那么我根本不会收到任何结果。对此有何提示? 通过将profile.id 添加到您的组并转换数组,您正在做正确的事情。但是,按类别选择和分组将不起作用,因为 array_agg 函数随后将产生每行一个类别的数组。您可以考虑再次加入categories,可以在您的选择中使用。但是,您可能需要为第二个连接使用不同的别名,以避免名称冲突。有很多方法可以通过一个连接和多个聚合函数来完成,但这需要一个带有原始结果的查询。如果你能让它工作,我会更新我的答案。 感谢您的提示,与此同时,我已经更新了我的代码,只在配置文件中使用字符串数组,而不是多对多关系,因为我已经在这个问题上花费了相当长的时间,并且这满足了我当前的应用程序要求。我也想完成一些事情。如果将来我在需要多对多关系的情况下再次遇到该问题,我将尝试用您的提示解决它。无论如何都会赞成你的答案,因为它让我非常接近解决方案

以上是关于TypeORM:如何查询与字符串数组输入的多对多关系以查找所有字符串都应存在于相关实体列中的实体?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建如何在 typeorm 中创建多对多关系,[NestJS]

TypeORM:使用自定义属性查询多对多

如何查询firebase的多对多关系?

如何在 django 中过滤查询集的多对多

typeORM 多对多关系不同情况的处理

如何使我的多对多查询更快?