Skip to content

Conversation

@Fridious
Copy link

@Fridious Fridious commented Jan 4, 2026

This feature implements the possibility to use many-to-many relation ordering.

@Fridious Fridious changed the title Draft: allow to-many orderning feat: allow to-many orderning Jan 4, 2026
Comment on lines +109 to +114
it "works on a to-many relationship" $ do
get "/clients?select=*,projects(*)&order=projects(id)&projects.order=id.asc" `shouldRespondWith`
[json|[
{"id":1,"name":"Microsoft","projects":[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]},
{"id":2,"name":"Apple","projects":[{"id":3,"name":"IOS","client_id":2},{"id":4,"name":"OSX","client_id":2}]}
]|]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are multiple rows in the to-many end, how do we decide which row we order into?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean exactly? can you please give me an example?

Copy link
Member

@laurenceisla laurenceisla Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be misinterpreting @steve-chavez, but in this example the nested relationship has two rows for the first client:

"projects":[
  {"id":1,"name":"Windows 7","client_id":1},
  {"id":2,"name":"Windows 10","client_id":1}
]

So, does it take {"id":1,"name":"Windows 7", ...} or {"id": 2,"name":"Winodws 10", ...} to order the top relationship?

From what I see in other example, it should order the top relationship according to the first element of the nested relationship (in this case {"id": 1 ...}}), right? Or does it behave in a different way?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I understand what you mean. It works like the internal postgres array sorting mechanism. I attached an example image from my application. For your info, the application uses the uuid behind the displayName of the account to sort.

Example matrix:
data:
Name|Accounts
Person-2|Account-2, Account-1
Person-1|Account-1, Account-2
Person-3|Account-2

results in:
Person-1|Account-1, Account-2
Person-2|Account-1, Account-2
Person-3|Account-2,

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see in other example, it should order the top relationship according to the first element of the nested relationship (in this case {"id": 1 ...}}), right? Or does it behave in a different way?

@Fridious Could you answer/clarify the above? I don't quite get your example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so basically this is the main query without the WITH pgrst wrapper that log-query = true shows:

SELECT "test"."clients".*,
       COALESCE("clients_projects_1"."clients_projects_1", '[]') AS "projects"
FROM "test"."clients"
LEFT JOIN LATERAL
 (SELECT json_agg("clients_projects_1" ORDER BY "clients_projects_1"."id" ASC)::jsonb AS "clients_projects_1",
         array_agg("clients_projects_1"."id" ORDER BY "clients_projects_1"."id") AS "clients_projects_1_ord_1"
  FROM
    (SELECT "projects_1".*
     FROM "test"."projects" AS "projects_1"
     WHERE "projects_1"."client_id" = "test"."clients"."id"
     ORDER BY "projects_1"."id" ASC) AS "clients_projects_1") AS "clients_projects_1" ON TRUE
ORDER BY "clients_projects_1"."clients_projects_1_ord_1"

So it would order by the result of the array_agg(...) aggregate (an integer[] in this case). However it's a bit confusing when using both orderings at the same time, should the first ordering affect the other? Like for example, add a new project here:

insert into test.projects values (20,'Test',1);

Ordering the internal by projects.order=id.desc and then the top by order=projects(id).asc, it shows the "id": 20 first instead of the ones with less id:

curl 'localhost:3000/clients?select=*,projects(*)&projects.order=id.desc&order=projects(id).asc'
[
  {"id":1,"name":"Microsoft","projects":[
    {"id": 20, "name": "Test", "client_id": 1},
    {"id": 2, "name": "Windows 10", "client_id": 1},
    {"id": 1, "name": "Windows 7", "client_id": 1}
  ]},
  {"id":2,"name":"Apple","projects":[
    {"id": 4, "name": "OSX", "client_id": 2},
    {"id": 3, "name": "IOS", "client_id": 2}
  ]}
]

Is this expected? If so it looks confusing from a user perspective.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I explained it a little too confusing. To me, this looks correct.
If you change the query to &projects.order=id.desc&order=projects(id).desc, then the result should be the same because the nested objects are responsible for sorting the parent object. I didn't change any of the code for this. It's the same as before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so order=projects(id).asc is the main order here and then the projects.order=id.desc is done after or separately from that. Makes sense from the perspective of: "ordering the nested resource does not affect the top order", however this could be confusing at first glance IMO... this would need a doc explanation, I think.

It's the same as before.

There are many new things here since we didn't allow to-many ordering before, that's why we're doing many questions here to understand better the implementation. The next step would be to check the integration with other features like aggregates, spread operator, nested resources, etc. I'll be reviewing those and check if anything breaks (these would need to be added to tests too).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for the clarification. Please let me know if there’s anything you’d like me to change or add on my side.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For starters, this would need a Documentation entry, but in order to do that we need to define the use case with clarity as mentioned here #4592 (comment) (what exactly does the user expect to get with this feature).

Comment on lines -109 to +110
it "fails when is not a to-one relationship" $ do
get "/clients?select=*,projects(*)&order=projects(id)" `shouldRespondWith`
[json|{
"code":"PGRST118",
"details":"'clients' and 'projects' do not form a many-to-one or one-to-one relationship",
"hint":null,
"message":"A related order on 'projects' is not possible"
}|]
{ matchStatus = 400
it "works on a to-many relationship" $ do
get "/clients?select=*,projects(*)&order=projects(id)&projects.order=id.asc" `shouldRespondWith`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these test case changes are changing something like order=x(y) to order.x=y. But that's not the same, right?

I believe the order.x=y ordering worked before already and just sorts the embedding, not the top-level.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. I will adjust the tests. thanks

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steve-chavez steve-chavez requested review from laurenceisla and removed request for laurenceisla January 8, 2026 02:05
@wolfgangwalther wolfgangwalther changed the title feat: allow to-many orderning feat: allow to-many ordering Jan 13, 2026
@laurenceisla
Copy link
Member

for my personal use case

It would be nice to have the use case explained here for better understanding of the feature, this will be useful for the documentation too. I understand from your explanations that you're going with: "That's how SQL orders it", which is fair for us who are accustomed with the source code and know how the query is built under the hood. But for the end user there would need to be an explanation of the use case, IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants