diff --git a/docs/en/appendices/5-4-migration-guide.md b/docs/en/appendices/5-4-migration-guide.md index 15663ed314..3ab9a579c8 100644 --- a/docs/en/appendices/5-4-migration-guide.md +++ b/docs/en/appendices/5-4-migration-guide.md @@ -110,6 +110,9 @@ version is reported as `unknown`), the header is omitted. See [Query Builder](../orm/query-builder#advanced-conditions). - Added `inOrNull()` and `notInOrNull()` methods for combining `IN` conditions with `IS NULL`. - Added `isDistinctFrom()` and `isNotDistinctFrom()` methods for null-safe comparisons. +- Added `FunctionsBuilder::stringAgg()` for portable string aggregation. + Translates to `STRING_AGG` or `GROUP_CONCAT` per driver. + See [Query Builder](../orm/query-builder#string-aggregation). - Added `Connection::afterCommit()` to register callbacks that run after the outermost transaction commits. Callbacks are discarded on rollback. See [Database Basics](../orm/database-basics#aftercommit) for more details. diff --git a/docs/en/orm/query-builder.md b/docs/en/orm/query-builder.md index 2cb9f5004d..6b9a93d9af 100644 --- a/docs/en/orm/query-builder.md +++ b/docs/en/orm/query-builder.md @@ -346,31 +346,36 @@ You can access existing wrappers for several SQL functions through `SelectQuery: Generate a random value between 0 and 1 via SQL. `sum()` -Calculate a sum. `Assumes arguments are literal values.` +Calculate a sum. *Assumes arguments are literal values.* `avg()` -Calculate an average. `Assumes arguments are literal values.` +Calculate an average. *Assumes arguments are literal values.* `min()` -Calculate the min of a column. `Assumes arguments are literal values.` +Calculate the min of a column. *Assumes arguments are literal values.* `max()` -Calculate the max of a column. `Assumes arguments are literal values.` +Calculate the max of a column. *Assumes arguments are literal values.* `count()` -Calculate the count. `Assumes arguments are literal values.` +Calculate the count. *Assumes arguments are literal values.* + +`stringAgg()` +Aggregate string values using a separator. Translates to `STRING_AGG()`, +`GROUP_CONCAT()`, or `LISTAGG()` depending on the database driver. +*Assumes the first argument is a literal value.* `cast()` Convert a field or expression from one data type to another. `concat()` -Concatenate two values together. `Assumes arguments are bound parameters.` +Concatenate two values together. *Assumes arguments are bound parameters.* `coalesce()` -Coalesce values. `Assumes arguments are bound parameters.` +Coalesce values. *Assumes arguments are bound parameters.* `dateDiff()` -Get the difference between two dates/times. `Assumes arguments are bound parameters.` +Get the difference between two dates/times. *Assumes arguments are bound parameters.* `now()` Defaults to returning date and time, but accepts 'time' or 'date' to return only @@ -471,6 +476,38 @@ FROM articles; > [!NOTE] > Use `func()` to pass untrusted user data to any SQL function. +#### String Aggregation + +The `stringAgg()` method provides a portable way to aggregate string values +using a separator. It translates to the appropriate native SQL function for +each driver (`STRING_AGG()` on PostgreSQL and SQL Server, `GROUP_CONCAT()` on +MySQL, and `STRING_AGG()` or `GROUP_CONCAT()` on MariaDB/SQLite depending on +version): + +```php +$query = $articles->find(); +$query->select([ + 'category_id', + 'titles' => $query->func()->stringAgg('title', ', '), +]) +->groupBy('category_id'); +``` + +You can optionally specify an ordering for the aggregated values via the +third argument: + +```php +$query->func()->stringAgg('title', ', ', ['title' => 'ASC']); +``` + +`STRING_AGG` with aggregate-local ordering is supported on PostgreSQL, +SQL Server, MariaDB 10.5+ and SQLite 3.44+. MySQL translates the call to +`GROUP_CONCAT` in all cases. + +::: info Added in version 5.4.0 +`FunctionsBuilder::stringAgg()` was added. +::: + ### Ordering Results To apply ordering, you can use the `orderBy()` method: