sinnbeck.dev – Laravel groupBy() error
Helping out at the laracasts forum, I often see people running into issues with using groupBy()
in queries. If you are running into errors like this
1
SQLSTATE[42000]: Syntax error
or
access violation:
2
1055
Expression #
1
of
SELECT
list
is
not
in
GROUP BY
clause
and
contains nonaggregated colum
.. then you are most likely the target of this blog post. Be aware that this post isn’t meant to teach mysql GROUP BY
, but rather give you a simple understanding of why the query is failing.
The error often comes from a query like this
1
$
posts
=
Post
::
query
()
2
->
groupBy
(
'
category_id
'
)
3
->
get
();
Now the idea is most likely that you just want to group the posts into their respective categories with all columns. But that is now how it works on a database level.
To make it easier to understand, lets use a spreadsheet as an example instead
id
name
category_id
author_id
visits
1
Post 1
1
4
32
2
Post 2
1
8
12
3
Post 3
2
12
201
4
Post 4
2
4
3
The database will always return rows, so let us try and run the above query on the data set.
Open a spreadsheet a try to make two rows with all data. It is two rows as category_id can either be 1 or 2.
So which id
should it pick? 1 or 2? and the name, slug or even owner?
id
name
category_id
author_id
visits
1 or 2
Post 1 or Post 2
1
4 or 8
32 or 12
3 or 4
Post 3 or Post 4
2
12 or 4
201 or 3
Are you starting to see the problem?
Mục Lục
“So how can I fix it?”
The simplest way is to only get only the columns we are grouping by, in this case category_id
1
$
posts
=
Post
::
query
()
2
->
select
(
'
category_id
'
)
3
->
groupBy
(
'
category_id
'
)
4
->
get
();
category_id
1
2
While this works, maybe it isn’t actually what we want. So now we need to plan out exactly what we want for each column. Perhaps we want to know what category id has the most visits. Here we can use what is called an aggregate function.
1
$
posts
=
Post
::
query
()
2
->
select
(
'
category_id
'
,
\
DB
::
raw
(
'
MAX(visits) as max_visits
'
))
3
->
groupBy
(
'
category_id
'
)
4
->
get
();
We are using a raw query as there isn’t anyway to specify the MAX function in a select in laravel. Also we are aliasing it to max_visits
to make it easy to reference in laravel.
category_id
max_visits
1
32
2
201
“But the query works in my database manager!”
You might indeed experience that the raw group by query actually works in your database manager. This is because laravel runs SQL queries in “strict mode”, while your database manager does not. It is possible to simply disable “strict mode” in laravel, by setting it to false
in the database.php config file. While possible I cannot recommend doing so. It is better to spend the time learning how to write proper SQL queries, as the results given by turning “strict mode” off, can be unpredictable and lead to problems down the road.
“But I want all columns!”
Okay so you want to get all columns. Then the trick is to simply not use groupBy()
on a database level. Instead you can use it with the returned collection instead. This will group the posts by the category it belongs to as expected (a nested collection)
1
$
posts
=
Post
::
query
()
2
->
get
()
3
->
groupBy
(
'
category_id
'
);
This is result in a structure like (here shown as a php array to make it easier to read)
1
[
2
3
'
1
'
=>
[
4
[
'
id
'
=>
1
,
'
name
'
=>
'
Post 1
'
,
'
category_id
'
=>
1
,
'
author_id
'
=>
4
'
visits
'
=>
32
],
5
[
'
id
'
=>
2
,
'
name
'
=>
'
Post 2
'
,
'
category_id
'
=>
1
,
'
author_id
'
=>
8
'
visits
'
=>
12
],
6
],
7
'
2
'
=>
[
8
[
'
id
'
=>
3
,
'
name
'
=>
'
Post 3
'
,
'
category_id
'
=>
2
,
'
author_id
'
=>
12
'
visits
'
=>
201
],
9
[
'
id
'
=>
4
,
'
name
'
=>
'
Post 4
'
,
'
category_id
'
=>
2
,
'
author_id
'
=>
4
'
visits
'
=>
0
],
10
],
Wrap up
So I hope this gave you some idea as to why your query is failing. If not, try asking at https://laracasts.com/discuss