typeorm: QueryBuilder produces wrong SQL with order by + nested fields, breaks pagination-related queries with skip() and take()
Issue type:
[ ] question [x] bug report [ ] feature request [ ] documentation issue
Database system/driver:
[ ] cordova
[ ] mongodb
[ ] mssql
[ ] mysql / mariadb
[ ] oracle
[x] postgres
[ ] cockroachdb
[ ] sqlite
[ ] sqljs
[ ] react-native
[ ] expo
TypeORM version:
[ ] latest
[ ] @next
[x] 0.2.25
Steps to reproduce or a small repository showing the problem:
I am encountering unexpected behaviour with pagination-related skip() and take() methods using the query builder. It occurs when I add an order by clause to a joined field.
Suppose I have a query that returns 3 items with a given :accountId:
const query = this.createQueryBuilder('invoice')
.innerJoin('invoice.account', 'account', 'account.id = :accountId', { accountId: user.account.id })
.leftJoinAndSelect('invoice.items', 'items')
.leftJoinAndSelect('invoice.client', 'client')
.where('invoice.active = :active', { active: true })
.addOrderBy('invoice.dueDate', 'ASC')
If I add skip(0) and take(2) I get the expected 2 items. If I query with getManyAndCount(), the count is the expected value of 3.
If I modify my query to sort the invoice line items based on a sort field, then skip() and take() no longer work as expected. See modified query below:
const query = this.createQueryBuilder('invoice')
.innerJoin('invoice.account', 'account', 'account.id = :accountId', { accountId: user.account.id })
.leftJoinAndSelect('invoice.items', 'items')
.leftJoinAndSelect('invoice.client', 'client')
.where('invoice.active = :active', { active: true })
.addOrderBy('invoice.dueDate', 'ASC')
.addOrderBy('items.sort', 'ASC') // new offending line
Note that the above order-by clause produces the desired result: the invoice line items are correctly sorted within each invoice.
However, if I add skip(0) and take(2) I now get an unexpected 1 item in the result. If I query with getManyAndCount(), the count is the expected value of 3.
If I use take(3) and take(4) I still get the unexpected 1 item in the result, again with the count showing the expected value of 3.
If I try take(5) I will get 2 items in the result. If I try take(6) I will finally get all 3 items.
I’m not too sure what’s going on or why this particular order-by clause is causing this issue. I’m relatively inexperienced with typeorm so there is a definite chance that I’m missing something.
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 14
- Comments: 20 (2 by maintainers)
I just encountered this issue using
findAndCountusingtypeorm@0.3.17. I wanted to sort joined entities by their primary key as they appear to be unsorted by default.When adding a nested sort order I no longer got the expected entries returned. In my test data set I got 7 entries whereas I was expecting 10.
If there is another way to sort nested relations when using
findAndCountplease let me know 😸I inspected the generated SQL and believe I’ve spotted the issue.
This is doing DISTINCT and LIMIT with the nested ids joined on. This is the problem. Here’s an example of the output. Notice that there are duplicates in
Foo_idThis output is used in the 3rd and final query.
I don’t know how to fix this in typeorm though. But hope it can help fix the issue.
Adding my take to the discussion:
(with multiple
joinedEntitys permainEntity)Basically, replacing
SELECT DISTINCT id, <other orderBy fields> FROM everything ORDER BY <all orderBy fields>bySELECT id FROM everything GROUP BY id ORDER BY <min of all orderBy fields>I would be happy to submit a PR, but it would be awesome having feedbacks from typeorm core contributor(s) beforehand!
Thanks for the reading 😉
@Nilegfx now that we have a better handle on the issue (thanks everyone), I will rename it from original Unexpected behaviour with skip() and take() re queries with a particular order-by clause to QueryBuilder produces wrong SQL with order by + nested fields, breaks pagination-related queries with skip() and take() in hopes it helps get the issue more attention.
I’ve just encountered this bug too. Thank you firxworx for pointing out that the issue is with the orderby on nested field! In my case, I have a parent entity (left side of one to many), and a child entity with composite primary key (right side of one to many). One column of 2-column composite primary key references parent entity. I make the query builder as such:
If I have the noted line in place, I see TypeORM produces the query:
SELECT DISTINCT "distinctAlias"."sessions_id" as "ids_sessions_id", "distinctAlias"."sessions_id", "distinctAlias"."packets_sequence" FROM ...Which is incorrect: it selects primary column of parent entity AND BOTH primary columns of child entity, thus the result will have more than one row for one parent entity.
For comparison, without the noted line, here’s what TypeORM generates:
SELECT DISTINCT "distinctAlias"."sessions_id" as "ids_sessions_id", "distinctAlias"."sessions_id" FROM ...Which is correct: it will give only one row per one parent entity (session).
Still facing the issue as well with 0.3.11
The bug still persists
We are at 0.3.7 right now. I will check later and inform you.
@firxworx , thanks for your reply.
Unfortunately, I’m trying to add a sorting feature to my library https://github.com/benjamin658/typeorm-cursor-pagination, and it relies on the query builder, so it’s not possible to use the raw query.
@benjamin658 if you need an escape hatch you can always hand-bomb a raw SQL query yourself. Refer to
EntityManager.query(): https://typeorm.io/#/entity-manager-api. Of course, be careful re potential for SQL Injection vulnerabilities, etc. as the onus is now on you to be safe.A part of me is surprised that this issue from last year is still kicking about… how are a ton of devs not facing this exact issue given the overall popularity of TypeORM especially w/ postgres!?!?