如何在TypeORM的whereIn语句中使用多列查询?PostgreSQL环境
如何在TypeORM中实现PostgreSQL的复合字段IN查询?
问题背景
我定义了如下TypeORM实体:
@Entity({ name: 'test_table' }) export class SomeEntity { @PrimaryGeneratedColumn({ type: 'int4', name: 'id' }) id: number; @Column({ type: 'int2', name: 'some_column_1' }) someColumn1: number; @Column({ type: 'text', name: 'some_column_2' }) someColumn2: string; }
想要执行这样的PostgreSQL查询:
SELECT id, some_column_1, some_column_2 FROM test_table WHERE (some_column_1, some_column_2) IN ((1, 'test1'), (2, 'test2'))
尝试了以下代码却报错:
// 省略部分代码 .where("(some_column_1, some_column_2) IN (:...arr)", { arr: [ [1, 'test1'], [2, 'test2'] ] }) .getMany();
错误信息:Error: input of anonymous composite types is not implemented
解决方案
方法1:原生VALUES子句+参数绑定
这是最贴近你目标SQL的实现方式,通过构造VALUES子句来规避匿名复合类型的问题:
const targetPairs = [[1, 'test1'], [2, 'test2']]; // 生成参数占位符,比如 ($1, $2), ($3, $4) const placeholders = targetPairs.map((_, idx) => `($${idx*2 + 1}, $${idx*2 + 2})`).join(', '); // 把二维数组扁平化,方便参数绑定 const flatParams = targetPairs.flat(); const result = await getRepository(SomeEntity) .createQueryBuilder('t') .where(`(t.some_column_1, t.some_column_2) IN (${placeholders})`) .setParameters(flatParams) .getMany();
这种方式既保留了你想要的IN子句结构,又通过参数绑定避免了SQL注入风险,同时解决了PostgreSQL的复合类型识别问题。
方法2:OR条件组合(适合少量匹配项)
如果你的匹配对数量不多,用TypeORM的QueryBuilder来组合多个AND+OR条件也是个不错的选择:
const matchConditions = [ { someColumn1: 1, someColumn2: 'test1' }, { someColumn1: 2, someColumn2: 'test2' } ]; const queryBuilder = getRepository(SomeEntity).createQueryBuilder('t'); matchConditions.forEach((cond, index) => { const paramPrefix = `pair${index}`; const condition = `t.some_column_1 = :${paramPrefix}_col1 AND t.some_column_2 = :${paramPrefix}_col2`; if (index === 0) { queryBuilder.where(condition, { [`${paramPrefix}_col1`]: cond.someColumn1, [`${paramPrefix}_col2`]: cond.someColumn2 }); } else { queryBuilder.orWhere(condition, { [`${paramPrefix}_col1`]: cond.someColumn1, [`${paramPrefix}_col2`]: cond.someColumn2 }); } }); const result = await queryBuilder.getMany();
这种写法更符合TypeORM的原生风格,不需要手写大量原生SQL,只是当匹配对很多时,生成的SQL会比较冗长。
方法3:利用PostgreSQL的unnest函数转换数组
通过将二维数组拆分成两个一维数组,再用unnest转换为行数据来匹配:
const targetPairs = [[1, 'test1'], [2, 'test2']]; const result = await getRepository(SomeEntity) .createQueryBuilder('t') .where(`(t.some_column_1, t.some_column_2) IN (SELECT unnest(:col1Vals), unnest(:col2Vals))`, { col1Vals: targetPairs.map(pair => pair[0]), col2Vals: targetPairs.map(pair => pair[1]) }) .getMany();
这种方式也能解决问题,不过需要注意unnest的顺序要严格对应,确保两个数组的元素位置匹配。
原方法报错原因
你之前的写法之所以出错,是因为TypeORM会把传入的二维数组参数转换成PostgreSQL的数组类型,而(col1, col2) IN (...)需要的是行的集合,不是数组类型。PostgreSQL无法直接识别这种匿名复合类型的输入,所以抛出了那个错误。
内容的提问来源于stack exchange,提问作者Roman
相关产品推荐
相关产品推荐

