You don't show the structure of your database. Do several chats have the same object ? What is an object in your database ?
Completely stuck with GroupBy and orderBy
Hi all,
I would like to build my query in simple form before jumping with Eloquent.
I am really stuck on this query:
SELECT DISTINCT
chats.title,
chats.object,
MAX(chats.created_at) AS latest,
SUM(IF(chats.admin_read = 0, 1, 0)) AS unread,
chats.id,
chats.group_id,
chats.message
FROM chats
WHERE chats.group_id = 82
GROUP BY chats.object
ORDER BY latest
I am trying to get the results by grouping all the chats.object together while showing the latest chats.message per group of chats.object. eg:
chats.object | chats.message object1 | latest message object2 | latest message object3 | latest message
I was really expecting the "MAX(chats.created_at) AS latest" to reorder internally the messages by the latest ones in the groupBy but I keep seeing the oldest messages per groupBy Instead.
I cannot work it out, any idea what I am doing wrong please?
Thank you so much.
Sorry, this is my structure:
CREATE TABLE IF NOT EXISTS `chats` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`group_id` bigint(20) unsigned NOT NULL,
`admin_read` tinyint(1) NOT NULL DEFAULT 0,
`chat_object` varchar(15) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`chat_title` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`message` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3488 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
I cannot work it out, any idea what I am doing wrong please?
Sure you are working on mysql
Mysql, by default, uses a non strict mode, that does not conform with sql standard ... which says you have to use aggregate function to all attributes not in the group by of the select.
@sr57 Strict or non strict mode by default ?
@sr57 Thanks for the reply. I was trying to build my query with DBForge query builder(for mysql), and while selecting all the columns and adding the clauses, this is the code that it gave me.
Question for you, I was always told in this forum to use strict mode with Laravel, is this correct?
I am set on strict mode right now
non strict mode by default ... that's bad
@User476820 It's better to use strict mode, but it's not obligatory.
@sr57 Sure ... but I thought MySQL was in strict mode by default.
@sr57 Not sure I understood, I said I was set on strict mode, this is correct no?
thought MySQL was in strict mode by default.
It can depend of your provider, but if you install it (on debian) it should be on "non strict mode"
I was always told in this forum to use strict mode with Laravel, is this correct?
With or without Laravel, don't use "non strict mode" except if you know what you do ...
I am set on strict mode right now
Good
2 possibilities
1 - you are able to use a aggregrate function for all fleds not in the group by : simple query
2 - if not, write you query with the aggregate you need and join to a second select on all the fields from the first one.
PS : same solution with postgresql that conforms better to sql standard.
@sr57 Let me try with the second select, not an easy one I have to admit as I tried earlier on but the results was a double SUM of admin_read
Take your time, I'm still here for about an hour.
When I wrote
sql standard ... which says you have to use aggregate function to all attributes not in the group by of the select.
It's in fact pure logic, without aggregate function what should be the return of a field that is not grouped?
I tried this:
DB::select("SELECT
C1.chat_title,
C1.chat_object,
C1.created_at,
C1.id,
MAX(C1.message) AS messages,
C2.unread,
C1.group_id
FROM chats C1
INNER JOIN (SELECT
C2.created_at,
C2.chat_object,
SUM(IF(C2.admin_read = 0, 1, 0)) AS unread
FROM chats C2
GROUP BY C2.chat_object) C2
ON C1.chat_object = C2.chat_object
WHERE C1.group_id = 82
GROUP BY C1.chat_object,
C1.group_id
ORDER BY C1.id DESC")->paginate(50);
But I am getting this: Call to a member function paginate() on array
Good for the query.
Pagination is another question I suggest you to close this thread if your original question is answered and open a new one.
That said , yes paginate does not work with DB::select that returns an array, you'll have to transform your query, and as you wrote, it's not (always) obvious or have a look to this link
https://laravel.com/docs/9.x/pagination#manually-creating-a-paginator
@sr57 Thanks, I will open another thread for the pagination.
So as writen in the other post, no so good for the query ... I focused on the paginate pb.
Inner query
SELECT
C2.created_at, <--- must be in group or aggregate function
C2.chat_object,
SUM(IF(C2.admin_read = 0, 1, 0)) AS unread
FROM chats C2
GROUP BY C2.chat_object
Have a look to this thread
https://laracasts.com/discuss/channels/laravel/get-last-record-every-month-for-a-year
correct syntax but not what you want from your original post
MAX(created_at) AS ...
C2.chat_object,
SUM(IF(C2.admin_read = 0, 1, 0)) AS unread
FROM chats C2
GROUP BY C2.chat_object,
will give you the last date for each chat_object (if you want the last par mont see the link I posted before)
@sr57 Been all day on it, I don't get it. Let's make it super short as I am not sure I explain the issue properly.
Let's say you have this setup:
TABLE 'CHATS'
ID | CHAT_ID | MESSAGES | UNREAD_MESSAGES | CREATED_AT
1 | POIPOIY-099-07 | hEY | 0 | 2022-06-02 13:16:42
2 | POIPOIY-099-07 | HI | 0 | 2022-06-02 13:16:42
3 | POIPOIY-099-08 | HI | 1 | 2022-06-02 13:16:42
And what I would like to achieve is:
-GROUP all the CHAT_UUID by their respective unique UUIDs but also get the latest messages "MESSAGES" per group. -SUM all the UNREAD_MESSAGES as 'IF 0 ? 1 : 0 (SUM( IF(C2.admin_read = 0, 1, 0))
I am not understanding if self referencing on the same table should be done via a join or sub query, I am honestly totally confused on this.
Took like 8 coffees, now starting wine as I am close to having a break-down ............:-( :-( :-( :-( :-(
Let's make it super short
Good point
So
GROUP all the CHAT_UUID by their respective unique UUIDs
Assuming, UUID = chat_id :
GROUP BY chat_id;
Latest date :
SELECT MAX(created_at) AS last_date FROM chats GROUP BY chat_id;
Latest message
= msg at the latest date :
SELECT r1.chat_id, r2.last_date, r1.messages FROM chats r1
INNER JOIN ( SELECT chat_id,MAX(created_at) AS last_date FROM chats GROUP BY chat_id ) r2
ON r1.chat_id=r2.chat_id AND r1.created_at=r2.last_date;
SUM all the UNREAD_MESSAGES
Per group or not, I should assume it' s per group but sure you'll succeed to complete the query by yourself. Try chocolate, it works better (for me) than wine (in such situation)
@sr57 Thank you so much(You are my master lol), let me try now :-)
@sr57 LOL about chocolate :-)
I tried your query and I am having a different problem, the output is:
ID | CHAT_ID | MESSAGES | UNREAD_MESSAGES | CREATED_AT
1 | POIPOIY-099-07 | hEY | 0 | 2022-06-02 13:16:42
2 | POIPOIY-099-07 | HI | 0 | 2022-06-02 13:16:42
3 | POIPOIY-099-08 | HI | 1 | 2022-06-02 13:16:42
3 | POIPOIY-099-08 | HI | 1 | 2022-06-02 13:16:42
3 | POIPOIY-099-09 | HI | 1 | 2022-06-02 13:16:42
The "MESSAGES" ARE GOOD, all showing the latest one but the CHAT_ID is not grouped. I really was expecting it to be grouped with "FROM chats GROUP BY chat_id"
Do you have some duplicate records (same chat_id, msg and date) ?
Probably yes, add DISTINCT at the beginning of your main query.
@sr57 What a day! lol
Something is not right, And I think that's why I spent so much time on this, I have never struggled so much on a query before. Here is my new query based on yours:
SELECT DISTINCT
C1.chat_id,
C2.latest,
C1.messages
FROM chats C1
INNER JOIN (SELECT
chats.chat_id,
MAX(chats.created_at) AS latest
FROM chats
GROUP BY chats.chat_id) C2
ON C1.chat_id = C2.chat_id
AND C1.created_at = C2.latest
But the result is the same, the GROUP BY chats.chat_id of the INNER JOIN is not grouping them. I cannot work out why. I tried like 5 query builders today to get some help, they all give me the same result.
This is my old query that works but when MYSQL strict mode is on, it fails:
$sub = DB::table('chats as c')
->select('messages')
->whereColumn('c.chat_id', 'chats.chat_id')
->limit(1)
->latest();
$history = DB::table('chats')->selectRaw(
'SUM(IF(admin_read = 0, 1, 0)) as unread, chat_id, created_at, MAX(created_at) as latest'
)
->selectSub($sub, 'message')
->where('group_id', '=', $request->userId)
->orderBy('id', 'DESC')
->groupBy('chat_id')
->paginate(50);
Should be a misunderstanding, please (re)share
-
a sample of input data
-
the result from the previous request
-
the expected result
PS : will be on line for only ~15mn
PS 2 : let strict mode on (we 'll be able to discuss of this point later)
@sr57 Leave it for today ahahaha :-) I have already took so much energy out of you, it will take me some time to get a proper example running here: https://www.db-fiddle.com/
Thank you so much again for your time.
Have a good night.
Will be less available next days but should be able to answer regarding the result you got and the expected result you expect.
@sr57 Thank you so much! I really appreciate your help.
As you can see if you click on "run" it will show the Chat_id "0508133" as a duplicate rather than being grouped
It works well, you have simply 2 latest msg, same timestamp for the 2 msg for the same chat_id.
If you want only one line per chat_id for this case, you can use group_concat in mysql
https://www.mariadbtutorial.com/mariadb-aggregate-functions/mariadb-group_concat/
or you can choose to take only one (using max for instance), or you can avoid having this case in you app.
Ok Got it I think
C1.chat_id,
C2.unread
FROM chats C1
INNER JOIN (SELECT
MAX(chats.id) AS latest_id,
chats.chat_id,
SUM(IF(chats.admin_read=0,1,0)) AS unread
FROM chats
GROUP BY chats.chat_id) C2
ON C1.chat_id = C2.chat_id
AND C1.id = C2.latest_id
GROUP BY C1.chat_id,
C1.message,
C1.updated_at,
C2.unread
I was thinking, why the join is on the created_at and not on the id?
Yes good point, Always better after a good night ... and chocolate ?
And, by the way, you found how 'non strict mode' runs under the hook (but let imagine you were using updated_at , ... with two values identical, no reason that id should be the last in this case).
Your sql sounds right, don't understand why you group by updated_at (and message) but it's your app.
@sr57 Yesterday I felt like dead meat and when it is like that...my brain does not think anymore as I worked 7 days and 12 hours each day. Instead of Chocolate I take GABA + B6 together(when mega stressed with work), look for it online, it is magical(but I ran out this week). And when I want to push myself hard on low moral days I take the opposite which are "Mucuna pruriens".
By the way, about C1.message, C1.updated_at,
It is a mistake, my visual query builder added them as default. Each time I do a group, it adds all the selected fields in the group, no idea why this happens all the time. Good night and thanks again for all your help! Not sure what I should do about the best answer as I would like to give you credits too, please let me know what I should add as a best answer.
I just changed the query as an eloquent query and as I said earlier, when strict mode is on I get this: Syntax error or access violation: 1055 'C1.updated_at' isn't in GROUP BY in C:\laragon\www\home\vendor\doctrine\dbal\lib\Doctrine\DBAL\Driver\PDOConnection.php:64 Stack trace:
When strict mode if off, it works.
my visual query builder added them as default. Each time I do a group, it adds all the selected fields in the group, no idea why this happens all the time.
Always same answer :-) all your select fields must be grouped or used in an aggregate function.
In fact, you did not share the beginning of your sql, I assume it was only "SELECT' ... yes? If not see above.
I just changed the query as an eloquent query
Not so obvious (sometimes) have a look to the sql generated. If you don't understand, like before it's another question, open a new thread.
@sr57 Ok, for the grouping issues, I checked on stack overflow and people are saying that Laravel should be set this way:
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'modes' => [
//'ONLY_FULL_GROUP_BY', // Disable this to allow grouping by one column
'STRICT_TRANS_TABLES',
'NO_ZERO_IN_DATE',
'NO_ZERO_DATE',
'ERROR_FOR_DIVISION_BY_ZERO',
'NO_AUTO_CREATE_USER',
'NO_ENGINE_SUBSTITUTION'
],
'engine' => 'InnoDB',
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
PDO::ATTR_PERSISTENT => true,
],
After that, no more errors on grouping with 1 column and my script started to work a lot better. Here more information about it: https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html#:~:text=If%20the%20ONLY_FULL_GROUP_BY%20SQL%20mode,are%20functionally%20dependent%20on%20them.
Not clear.
What sql ?
- raw or eloquent?
- the one from your original question ?
What
- was your previous mysql config ?
- was changed?
Your link deals with the same point we are discussing from the very beginning (no "simple field" in a select with a group by)
To summarize ,
-
original question : non strict mode + right (standard) sql should work
-
if not, it's a second problem that could be the mysql config. If your config solved, it's good but if you tell me that this config solves your original question ...
That's said, if it's works, should be time to (re)close this thread?
@sr57 Sorry I could not reply earlier....Covid got me and I ended up pretty bad....I wanted to say thank you for everything.
@user476820 Thank for your feedback, hope you are doing better now, should be good to close this thread.
@sr57 Thanks, I was not really sure what to add as the best answer. I chose the Mysql settings in the config as this is what seems to have stopped the groupBy Issues I was having.
It's the only way to close a post and a good practice is not to choose one of your post :-)
https://laracasts.com/discuss/channels/general-discussion/guidelines-for-posting-on-laracastscom
Accept answers
If someone has taken the time to help you out, the least you can do is show your appreciation by marking it as solved. To do this just click the check/tick ✓ mark. Do not mark your own response as answered unless you've actually taken the time to format a correct answer, you won't gain experience points either.
@sr57 Oh sorry I see, the best answer is based on appreciation and not on what solved the issue, sorry, this is opposite of Stack overflow. Ok, so in this case not sure, I will choose anything you answered because the actual original issue was the mysql settings and not the queries. When I ran my old queries, everything worked ok with these settings.
@User476820 It is usually a good practice to give the reply that helped you arrive to the final solution the best reply, and it's also a good idea to show the finished solution to the problem as well.
@Tray2 Thanks I posted the full solution above already which has:
'modes' => [
//'ONLY_FULL_GROUP_BY', // <----Disable this to allow grouping by one column
'STRICT_TRANS_TABLES',
'NO_ZERO_IN_DATE',
'NO_ZERO_DATE',
'ERROR_FOR_DIVISION_BY_ZERO',
'NO_AUTO_CREATE_USER',
'NO_ENGINE_SUBSTITUTION'
],
I was unable to have a single groupBy using a single column, no matter what I was trying from the above answers. What solved the issue is what they told me to do on Stackoverflow which is //'ONLY_FULL_GROUP_BY', // <----Disable this to allow grouping by one column . My entire app started to work again. Now what I am not sure about is....this solved my issue yes, but should users do this? From what people say on Stackoverflow, yes, it is fine. But is it really? This is why I am confused about what should be the correct answer.
@User476820 I would not have disabled that, but rather rewritten the query. However that is your decision.
I agree with you, but it's not easy for everyone to understand the good use of "group by" when mysql* allows, with the non-strict mode, to create "simpler" queries that seem/can work. And when, on top of that, you want to create a "Laravel query" it can become a nightmare.
*better to use postgresql.
@sr57 Any idea why in a database you should not use groupBy for a single column? What is the technical issue behind it? This is the part I am not understanding at all(if mysql technically allows you to do it, and it works).
@User476820 If I'm not completely wrong, the SQL standard requires all columns in the group by, changing that is more of a lazy hack,
Share your last SQL query, I should try to answer.
@sr57 My current query completely changed from the original as It has been some time now.
But for example, let's say you have this:
select
id1,
id2,
id3
FROM chats
where id1 = ?
GROUP BY
id1,
id2,
id3
VS This:
select
id1,
id2,
id3
FROM chats
where id1 = ?
GROUP BY
id1
Why would the first groupBy gives no warnings and the second does(unless you uncomment this little settings).
The reason this is super annoying is....if you have select * and have a huge table, you have to add each all of the selected columns to the groupBy or get this warning. Making life so much more complicated as you have to write a lot more. I see this as strange and cannot understand the technical need behind this.
Let's say you have apples, oranges and bananas, why would you need to tell the script that you need to groupBy by "apples, oranges and bananas", you could just tell it to group all the columns by "apples" and not look at the other columns as you are only interested in grouping by Apples.
I hope I explained the question properly :-)
We do not need bananas :-), let''s see with apples (id1) and oranges (id2)
You can group by apples only
select id1 FROM chats GROUP BY id1;
But id2 is "undefined", there is no sense to select it.
Let's take this data
id1 id2
11 21
11 22
12 21
select id1 FROM chats GROUP BY id1;
// 11
// 12
What should be id2 facing id1=11 : 21 or 22 ? "undefined"
What you can do is , use an aggregate function, your can calculate the min, max, count, sum ... of id2
select id1,SUM(id2) FROM chats GROUP BY id1;
// 11 , 43
// 12 , 21
Hope it's clear.
To resumerize this long thread
-
all your select fields must be grouped or used in an aggregate function
-
use "strict mode = true" with mysql (or use postgresql) to force the previous rule
Please or to participate in this conversation.