-
Notifications
You must be signed in to change notification settings - Fork 11k
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
Add orderByPriority Method for Custom Sorting in Query Builder V2 #52535
base: 11.x
Are you sure you want to change the base?
Conversation
* @param $direction | ||
* @return string | ||
*/ | ||
public function orderByPriority($column, array $priority, $direction = 'asc') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this implementation: MariaDbGrammar
extends MySqlGrammar
and inherits the method from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. I have removed the code from MariaDbGrammar.
Please lowercase all the generated SQL: Please also add tests for |
{ | ||
$placeholders = implode(',', array_fill(0, count($priority), '?')); | ||
|
||
return "FIELD($column, $placeholders) $direction"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The column name needs to be wrapped with $this->wrap($column)
. This also affects the other grammars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have applied the wrap function on all grammars and updated the tests accordingly.
/** | ||
* Apply custom ordering to a query based on a priority array. | ||
* | ||
* @param $column |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grammars are missing the parameter types for $column
and $direction
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated the code to add the type for column and direction
- apply wrap on columns - lowercase SQL - add new test for exceptions - add test on builder for orderByPriority
Thank you for taking the time to review. I have applied lowercase for all the generated SQL. |
I noticed that the two types of implementation behave differently with values that aren't part of the
I think it makes much more sense to put them last, but I don't see an elegant way to achieve this behavior on MySQL/MariaDB. Not sure if that's an issue. You could argue that |
Would it be worth using |
@staudenmeir
In order to make this field behavior consistent with the case behavior, it might make sense to change the "else" condition However, I do agree that it does not feel right that these values come first. |
Observation In a table of 5000 records and a priority array of 1000, CASE executed in 34 ms on average In the same table of 10000 records and a priority array of 1000, CASE executed in 35 ms on average As observed, FIELD is better but not by a significant amount. Of course, it might get significant depending on the query. I am leaning on using CASE only so as to have better consistency of behavior across all grammars. Food for thought For example, in the postgres grammar, I would set this instead public function orderByPriority(string $column, array $priority, string $direction = 'asc', int $defaultIndex = 0)
{
$column = $this->wrap($column);
$cases = [];
foreach ($priority as $index => $value) {
$cases[] = "when {$column} = ? then {$index}";
}
$caseStatement = 'case '.implode(' ', $cases).' else '.$defaultIndex.' end';
return "{$caseStatement} {$direction}";
} |
I question if we really need this at the database layer? 😬 $ids = [9, 10, 7, 8, 4, 6, 5, 2, 3, 1];
return User::whereIn('id', $ids)
->get()
->sortBy(fn ($model) => array_search($model->id, $ids))
->values(); |
Drafting pending further discussion around inconsistencies around database drivers. |
This kinda creates horrible queries for any query with over 10 records, but I think it would be good to actually provide a benchmark against a php-side sorting to see if it actually improves speed in a general sense (which I think it does). Whenever sorting/aggregating/selecting can be done on the database side, it's usually a performance benefit. |
@taylorotwell Here is another example of a use case return User::orderByPriority('role', $roles) |
@bert-w However, lets assume i have a table with 1M+ records of users with a role column with the possible values : I want to display a list of users, with the order above. It might not make sense to load 1M+ records in memory before sorting. Anyway I'll try to benchmark this and post the results here. |
I can vouch for the performance. Using |
I have thought about this and my conclusion would be to use CASE even for mysql. The behavior would be:
Let me know what you think If agreed, i will move the code back to builder itself instead of the corresponding builder grammar since it is the same implementation. From my tests, I see that field is indeed faster, but marginally faster. I think consistency is more important. |
Building upon the work done in PR #52483
Explanation:
This commit introduces the orderByPriority method to Laravel's query builder, enabling developers to sort query results based on a custom priority array. For example, you can now order records by specific IDs in a preferred sequence like this:
Model::whereIn('id', [2, 4, 6, 1, 3, 5])->orderByPriority('id', [2, 4, 6, 1, 3, 5])->get();
For MYSQL and Mariadb
This method leverages SQL's FIELD() function, allowing developers to easily display results in a user-defined order.
For Postgres, Sqlite and SQL server, the CASE() function was used to achieve the same result.
Compared to the previous PR, I have placed the logic in the corresponding grammar class as suggested to allow for flexibility of implementation depending on the database server.
Impact:
This feature greatly enhances the flexibility of data retrieval, particularly when a specific order of items is crucial for the application's logic or user interface.
We have to note that if the larger the array of priority, we might introduce performance issues
For reference: