New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Common Table Expressions #1116
Comments
Hi, no, there is no, you probably need to execute raw sql query using |
Knex, for example, allows to build CTE thanks to |
Sorry but I can't imagine how this can be integrated into typeorm's query builder semantics. TypeORM's QueryBuilder purpose is to select some model and return it. Can you create a feature proposal on how this can be implemented in our QueryBuilder and how it will work. |
@sylvainlap did you have a chance to take a look on this issue deeply? |
Actually, it was more a question than a feature request. I can close the issue. |
I have a use case where a with x (id_list) as (
values (array[42,48,43])
)
select c.*
from comments c, x
where id = any (x.id_list)
order by array_position(x.id_list, c.id); However I'd ideally want to avoid using raw queries due to the lack of type-safety and the difficulty in mapping the results back to entities. What I'd like to do is to use an API similar to below. const comments = await getRepository(Comment)
.createQueryBuilder('comment')
.with('x(id_list)', 'values(:ids)')
.addFrom('x', 'x')
.where('comment.id = any(x.id_list)')
.orderBy('array_position(x.id_list, comment.id)')
.setParameter('ids', [42, 48, 43])
.getMany(); Understandably this is also possible with the following simpler query builder expression: const ids = [42, 48, 43];
const comments = await getRepository(Comment)
.createQueryBuilder('comment')
.whereInIds(ids)
.orderBy('array_position(:ids, comment.id)')
.setParameter({ids})
.getMany() However I'd like not to send the array of ids twice especially if there is a large number of them. A possible API for Knex knex.with('with_alias', knex.raw('select * from "books" where "author" = ?', 'Test')).select('*').from('with_alias') TypeORM const books = await getRepository(Book)
.createQueryBuilder()
.with('with_alias', 'select * from "books" where "author" = :author')
.select()
.from('with_alias')
.setParameter('author', 'Test')
.getMany(); Knex knex.with('with_alias', (qb) => {
qb.select('*').from('books').where('author', 'Test')
}).select('*').from('with_alias') TypeORM const books = await getRepository(Book)
.createQueryBuilder()
.with('with_alias', qb => qb.select().from('books').where('author', 'Test').getQuery())
.select()
.from('with_alias')
.getMany(); |
I know it's a bit hacky but here's a workaround: const originalQuery = qb.getQuery
const originalParams = qb.getParameters
qb.getQuery = () => {
const a = originalQuery.call(qb)
return `WITH lb AS (${a}) SELECT * FROM lb WHERE abs(rank - (${rankQuery})) <= 3`
}
qb.getParameters = () => {
const a = originalParams.call(qb)
a.user = user
return a
} |
@AlexMesser @pleerock What is the status of this issue and is there any assistance I can provide (implmentation) ? You could say I have a vested interest. |
@sloan-dog if you like the API I proposed, I can get it implemented this weekend. I've been digging around a lot through the |
@jtmthf Will the proposed API support |
@tvoloshyn I'm not sure if it's the best way, but I can implement it the same way as Knex's API as |
After further review, I don't think that syntax will work for recursive CTE's as it doesn't support parameters. That feature may need to be a separate discussion and PR. |
@jtmthf Do you have any work on this feature I can contribute? Your proposed api seems very useful and way better than dealing with raw queries. |
1 similar comment
@jtmthf Do you have any work on this feature I can contribute? Your proposed api seems very useful and way better than dealing with raw queries. |
Thanks a bunch for sharing the workaround! I managed to succesfully use your technique to implement the recursive tree querying described here: https://schinckel.net/2014/09/13/long-live-adjacency-lists/. |
I tried to overwrite a queryBuilders |
@Neo-Zhixing Could you please describe how can I use this snippet for WITH clause of Query Builder? |
Can we have a querybuilder that takes at a top level, any For that matter, scopes could be recursive, if necessary. So SelectQueryBuilder takes an array of scope indexes when it produces the actual SQL and parameters. This would be great, since it would mean that params in subqueries wouldn't have to be registered manually in the parent query. Just use nested scopes to produce the actual parameter names. Visitor pattern. |
Here's an implementation I have working. Subclassing SelectQueryBuilder works fine it seems.
|
@Ginden |
@timfrans No. |
@dustin-rcg thx so much for your approach. To make it work properly with native TypeORM's queryBuilder for all methods (which may call Note Since So the solution which works for me for all native methods such as // patchTypeOrmQueryBuilderAddCommonTableExpressionsSupport.ts
import { SelectQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder, InsertQueryBuilder, RelationQueryBuilder } from 'typeorm';
import { QueryExpressionMap } from 'typeorm/query-builder/QueryExpressionMap';
export function patchTypeOrmQueryBuilderAddCommonTableExpressionsSupport(
queryBuilder: OptionableQueryBuilder = require('typeorm').SelectQueryBuilder,
): void {
const queryExpressionMap: typeof QueryExpressionMap = require('typeorm/query-builder/QueryExpressionMap').QueryExpressionMap;
const queryExpressionMapPrototype = queryExpressionMap.prototype;
const originalClone = queryExpressionMapPrototype.clone;
const qbPrototype = queryBuilder.prototype;
const originalGetQuery = qbPrototype.getQuery;
Object.defineProperty(queryExpressionMapPrototype, 'clone', {
value: function clone() {
const context = this as QueryExpressionMap; // For typization only
const result = originalClone.call(context);
result.commonTableExpressions = { ...context.commonTableExpressions };
return result;
},
});
Object.defineProperty(qbPrototype, 'getQuery', {
value: function getQuery(): string {
const context = this as SelectQueryBuilder<unknown>; // For typization only
const ctes = Object.entries(context.expressionMap.commonTableExpressions ?? { });
if (ctes.length && !context.expressionMap.subQuery) {
return [
'WITH',
ctes.map(([name, expression]) => `${context.escape(name)} AS (${expression})`).join(', '),
originalGetQuery.call(context),
].join(' ');
}
return originalGetQuery.call(context);
},
});
Object.defineProperty(qbPrototype, 'addCommonTableExpression', {
value: function addCommonTableExpression<T>(expression: CommonTableExpression<T>, name: string) {
const context = this as SelectQueryBuilder<unknown>; // For typization only
if (!context.expressionMap.commonTableExpressions) context.expressionMap.commonTableExpressions = { };
if (typeof expression === 'function') {
const subQueryBuilder = expression(context.subQuery());
context.setParameters(subQueryBuilder.getParameters());
context.expressionMap.commonTableExpressions[name] = subQueryBuilder.getQuery();
} else {
context.expressionMap.commonTableExpressions[name] = expression;
}
return context;
},
});
}
type OptionableQueryBuilder = typeof SelectQueryBuilder
| typeof UpdateQueryBuilder
| typeof DeleteQueryBuilder
| typeof InsertQueryBuilder
| typeof RelationQueryBuilder;
type CommonTableExpression<T> = string | ((qb: SelectQueryBuilder<T>) => SelectQueryBuilder<T>);
export interface IQueryBuilderWithCommonTableExpressions<Entity> {
addCommonTableExpression<T = Entity>(expression: CommonTableExpression<T>, name: string): this;
getQuery(): string;
}
declare module 'typeorm' {
export interface SelectQueryBuilder<Entity> extends IQueryBuilderWithCommonTableExpressions<Entity> { }
}
declare module 'typeorm/query-builder/QueryExpressionMap' {
export interface QueryExpressionMap {
commonTableExpressions: Record<string, string>;
}
} Usagecall patchTypeOrmQueryBuilderAddCommonTableExpressionsSupport(); // This will patch SelectQueryBuilder as default Use someWhere/at/some/repository.ts qb
.addCommonTableExpression(`SELECT * FROM some_table`, 'some_cte')
.addSelect(/* ... */) |
@timfrans u could try to use solution above |
@mopcweb What version is this meant to work against? I've tried on |
any update on that? |
@Ginden How do we know if it supports a driver ? |
@revskill10 SQL Server is partially supported - https://github.com/typeorm/typeorm/blob/master/src/driver/sqlserver/SqlServerDriver.ts#L239 I wasn't able to implement writable CTEs, because of issues with non-standard OUTPUT clause. |
Hello,
This is more a question than an issue, but how can I build a CTE (
with
) with TypeORM ? Is there a built in feature to do this, or should I use the raw sql query builder ?Thanks.
The text was updated successfully, but these errors were encountered: