Getting the last row in each group by? - php

Hello basically im trying to copy the messaging system that facebook has onto my site.
This is the logic...
"When a user1 CREATES A NEW MESSAGE to send to user7, A new thread is created with thread_id of 1(table: messages_thread) and a new entry is inserted into table:messages which is message_id 1(table:messages). When user7 REPLYS to user1's message, message2 is created, and it has a thread_id of 1.
Now when user 1 CREATES A NEW MESSAGE to sent to user7 thread 2 is created, and message 3 is created. When user7 replies to thread2, message 4 is created (hopefully you get the logic.)
Everything is fine. the only problem is i need to select the newest message in the thread but im having trouble with the sql,
This sql that I have as of right now...
SELECT max(message_id) message_id, m.thread_id, m.body, m.user_id,m.to_id, m.message_status, m.new, m.date, u.id, u.displayname, u.username, u.profile_img
FROM messages m INNER JOIN users u ON u.id = m.user_id
WHERE to_id = 7 AND (message_status = 'unread' or message_status='read' or message_status='saved')
group by thread_id Order by message_id Desc LIMIT 10
Produces this...
+------------+-----------+----------------------+---------+-------+----------------+-----+------------+----+--------------+----------+-------------+
| message_id | thread_id | body | user_id | to_id | message_status | new | date | id | displayname | username | profile_img |
+------------+-----------+----------------------+---------+-------+----------------+-----+------------+----+--------------+----------+-------------+
| 6 | 2 | Really nice | 1 | 7 | read | 0 | 1298617367 | 1 | Kenny Blake | imkenee | 28_1 |
| 4 | 1 | Whats good with you? | 1 | 7 | read | 0 | 1298607438 | 1 | Kenny Blake | imkenee | 28_1 |
+------------+-----------+----------------------+---------+-------+----------------+-----+------------+----+--------------+----------+-------------+
This is good but one small problem, it selects the first row in each group and im trying to select the newest (the last row) in each group
how can i do this? here is the tables. Thanks!
Table: Messages_thread
+----+---------+----------------+-------------+-----------+---------------+-------------+------------+
| id | user_id | subject | from_status | to_status | from_s_delete | to_s_delete | date |
+----+---------+----------------+-------------+-----------+---------------+-------------+------------+
| 1 | 1 | Hey Kenny | unread | unread | 0 | 0 | 1298607438 |
| 2 | 7 | Check out this | unread | unread | 0 | 0 | 1298617344 |
+----+---------+----------------+-------------+-----------+---------------+-------------+------------+
Table Messages
+------------+-----------+---------+-------+-----------------------------------------------------------+----------------+-----------------+-----+------------+
| message_id | thread_id | user_id | to_id | body | message_status | is_sent_deleted | new | date |
+------------+-----------+---------+-------+-----------------------------------------------------------+----------------+-----------------+-----+------------+
| 1 | 1 | 1 | 7 | Whats good with you? | read | 0 | 0 | 1298607438 |
| 2 | 1 | 7 | 1 | Nothing Kenny just chilling. Whats up with you though???? | read | 0 | 0 | 1298607473 |
| 4 | 1 | 1 | 7 | Just posted victor how are you man? | read | 0 | 0 | 1298607956 |
| 5 | 2 | 7 | 1 | Look at this poem.... | read | 0 | 0 | 1298617344 |
| 6 | 2 | 1 | 7 | Really nice | read | 0 | 0 | 1298617367 |
| 7 | 2 | 7 | 1 | Yea i know right :) | unread | 0 | 0 | 1298617383 |
+------------+-----------+---------+-------+-----------------------------------------------------------+----------------+-----------------+-----+------------+

Group the threads in a subquery, which will return the last message for each thread:
SELECT m.message_id, m.thread_id, m.body, m.user_id,
m.to_id, m.message_status, m.new, m.date, u.id, u.displayname, u.username, u.profile_img
FROM messages m
INNER JOIN users u ON u.id = m.user_id
INNER JOIN (
SELECT MAX(message_id) MaxMsgIDForThread
FROM messages
WHERE to_id = 7
AND (message_status = 'unread'
or message_status='read'
or message_status='saved')
GROUP BY thread_id
) g ON m.message_id = g.MaxMsgIDForThread
Order by m.message_id Desc
LIMIT 10
The WHERE may need to be moved to the outer query, right now it will pick the last message the meets the criteria, move it to the outer query if you want to skip the thread entirely if the conditions are not met.
You should also consider storing the message status as a ENUM which will help the comparisons.

There is no way to add order to group by
But maybe this works:
SELECT max(message_id) message_id, MAX(m.thread_id), m.body, m.user_id,m.to_id, m.message_status, m.new, m.date, u.id, u.displayname, u.username, u.profile_img
FROM messages m INNER JOIN users u ON u.id = m.user_id
WHERE to_id = 7 AND (message_status = 'unread' or message_status='read' or message_status='saved')
group by thread_id Order by message_id Desc LIMIT 10

Related

How to get a value from MySQL n days before from CURDATE()

I want to join 4 tables to list all the values from a table those have the duration from last updated to current date is more that the duration in other table, table are given below (my English not good to understand so am explaining with examble)
first table daily_tasks
+---------+---------+
| task_id | type_id |
+---------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+---------+---------+
Second Table daily_task_report
+-----------+---------+------------+
| report_id | task_id | task_date |
+-----------+---------+------------+
| 1 | 1 | 2015-09-10 |
| 2 | 3 | 2015-09-10 |
| 3 | 1 | 2015-09-11 |
| 4 | 3 | 2015-09-16 |
+-----------+---------+------------+
Third Table duration_types
+---------+---------------+------------------------+
| type_id | duration_type | duration_time(in days) |
+---------+---------------+------------------------+
| 1 | Daily Task | 1 |
| 2 | Weekly Task | 6 |
| 3 | Monthly Task | 26 |
| 4 | Yearly Task | 313 |
+---------+---------------+------------------------+
Fourth Table calendar
+--------+------------+---------+
| cal_id | cal_date | holiday |
+--------+------------+---------+
| 1 | 2015-09-10 | 0 |
| 2 | 2015-09-11 | 0 |
| 3 | 2015-09-12 | 0 |
| 4 | 2015-09-13 | 1 |
+--------+------------+---------+
Here daily_tasks.type_id is from duration_types.type_id and daily_task_report.task_id is from daily_tasks.task_id. I want to select all the task_id those task_date and current_date difference will greater than duration_time, also while calculating the duration i have to avoid the dates those have holiday=1 from calendar.
I tried queries but not proper, i got the values without including the calendar table, but that not a good way, query is taking more time to execute.
"SELECT dailyTasks.task_id FROM
(SELECT tab.* FROM (SELECT
tasks.task_type,report.*
FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
WHERE 1 ORDER BY report.task_date DESC) as tab GROUP BY tab.d_task_id) AS dailyTasks
LEFT JOIN duration_types AS type ON dailyTasks.task_type=type.type_id
WHERE DATEDIFF(CURDATE(),dailyTasks.task_date)>=type.duration_time"
Please someone help, I stuck in this section
According to given table You have some unexpected text or unknown columns in your query
Try this query
"SELECT dailyTasks.d_task_id FROM
(SELECT tab.* FROM
(SELECT tasks.type_id,report.* FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
ORDER BY report.task_date DESC)
as tab GROUP BY tab.task_id) AS dailyTasks
LEFT JOIN duration_types AS type ON dailyTasks.type_id=type.type_id
WHERE DATEDIFF(CURDATE(),dailyTasks.task_date)>=type.duration_time"
It can be also works in single query
SELECT tasks.task_id FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
LEFT JOIN duration_types AS type ON tasks.type_id = type.type_id and DATEDIFF(CURDATE(),report.task_date) >= type.duration_time
*I tried but not exclude that id which have holiday in calendar
You can create it on your coding side I gave you new query included with calendar
*
SELECT tasks.task_id,report.task_date,calendar.holiday FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
LEFT JOIN duration_types AS type ON tasks.type_id = type.type_id and DATEDIFF(CURDATE(),report.task_date) >= type.duration_time
LEFT JOIN calendar ON report.task_date=calendar.cal_date
where calendar.holiday = '0'
order By report.task_date desc

Select and count query from two tables

I must retrive all those who have sent messages to a given user and if the messages have not been read, return the number.
I have two tables
users
| id_user| username | status |
| 1 | user1 | yes |
| 2 | user2 | yes |
| 3 | user3 | yes |
messages
| id_message |id_sender|id_dest| message | read |
| 1 | 1 | 2 | some text | yes |
| 2 | 3 | 2 | some text | no |
| 3 | 2 | 1 | some text | no |
and I have this query
select id_sender,username, count(distinct id_message) as nr_messages
FROM messages
INNER JOIN users
ON id_sender = id_user
WHERE id_dest=$dest and status='yes'
GROUP by id_sender
I can count the number of messages for each user but I can not know how many are unread. I must have a number if the messages are unread and 0 i the user do not have unread messages.
for example for user2
sender: user3 (1 unread message)
sender: user1 (0 unred message)
messages table is ~2000000 rows
If you need to count both read and unread at the same time, you can use conditional sum as
select
m.id_sender,
u.username,
sum(m.`read` = 'yes') as read_messages,
sum(m.`read` <> 'yes') as unread_messages
from messages m
join users u on u.id_user = m.id_sender
where m.id_dest = 2
group by m.id_sender ;
For the above sample data you will get
+-----------+----------+---------------+-----------------+
| id_sender | username | read_messages | unread_messages |
+-----------+----------+---------------+-----------------+
| 1 | user1 | 1 | 0 |
| 3 | user3 | 0 | 1 |
+-----------+----------+---------------+-----------------+

Select values from another table with most recent entry

I have 2 MySQL Tables:
Table with all Events
+----+---------------------+
| id | Eventtitle |
+----+---------------------+
| 1 | Event 1 |
| 2 | Event 2 |
| 3 | Event 3 |
| 4 | Event 4 |
+----+---------------------+
Table with user attend statuses - Users can change their status multiple times. Every time they change it, a new entry is inserted into the table
+----+------------+----------+---------+---------------------+
| id | event_id | user_id | status | attend_time |
+----+------------+----------+---------+---------------------+
| 1 | 1 | 2 | 1 |2013-07-03 15:34:02 |
| 2 | 1 | 2 | 2 |2013-08-03 19:01:02 | <--
| 3 | 3 | 1 | 1 |2013-07-03 15:34:02 |
| 4 | 4 | 4 | 3 |2013-07-03 15:34:02 |
| 5 | 4 | 6 | 2 |2013-07-03 15:34:02 |
| 6 | 4 | 6 | 1 |2013-07-03 18:55:02 | <--
+----+-----------------------+---------+---------------------+
Now I want all events listed and additionally the most recent attend state and attend_time of the currently logged in user (for example user_id 2 oder 54) - IF he is not in the attend table i still need an entry of the event. (in that case a NULL for the state and attend_time would be good)
Here I rebuild the Data Structure: http://sqlfiddle.com/#!2/c1b3f
IF there is no way on how to get the result with MySQL - I will try to get it with PHP
Try something like this:
SELECT s.id, eventtitle, status, attend_time
FROM events e
JOIN status s ON e.id = s.event_id
WHERE user_id = 2 AND attend_time = (SELECT MAX(attend_time) FROM status
WHERE user_id = 2)
SQLFiddle
Or:
SELECT s.id, eventtitle, s.status, s.attend_time
FROM events e
JOIN status s ON e.id = s.event_id
LEFT JOIN status s2 ON s.attend_time < s2.attend_time
WHERE s.user_id = 2 AND s2.attend_time IS NULL
SQLFiddle
(not tested)
SELECT a.id, eventtitle, status, attend_time FROM events b JOIN status a ON b.id = a.event_id WHERE user_id = 2 order by attend_time desc

PHP: Concatenating database results by email

I have a list of tasks assigned to multiple users. I'd like to set up a cron job and send out email notifications once an hour to each user. Here's my query:
SELECT t.*, u.name, u.email
FROM tasks t, users u
WHERE t.date_created > DATE_SUB(NOW(), INTERVAL 1 HOUR) AND t.user_id = u.id
ORDER BY t.date_created ASC
Here's the result (I trimmed a few columns to fit better):
+----+-------+---------------------+---------+--------+------+--------------+
| id | title | date_created | user_id | status | name | email |
+----+-------+---------------------+---------+--------+------+--------------+
| 9 | task1 | 2013-09-01 17:56:10 | 2 | active | John | js#gmail.com |
| 10 | task2 | 2013-09-01 17:57:20 | 1 | active | Tim | ti#gmail.com |
| 11 | task3 | 2013-09-01 17:58:30 | 2 | active | John | js#gmail.com |
| 12 | task4 | 2013-09-01 17:59:40 | 1 | active | Tim | ti#gmail.com |
+----+-------+---------------------+---------+--------+------+--------------+
The problem is that I can't figure out how to concatenate the tasks belonging to a certain user_id and then send them out to the assigned email. So for the above example there would be two emails sent out:
task1 and task 3 (to js#gmail.com)
task2 and task 4 (to ti#gmail.com)
Try with the GROUP_CONCAT aggregate command :
SELECT GROUP_CONCAT(t.title), u.name, u.email
FROM tasks t, users u
WHERE t.date_created > DATE_SUB(NOW(), INTERVAL 1 HOUR) AND t.user_id = u.id
GROUP BY u.id
This will output something like :
task1,task3 | John | js#gmail.com
task2,task4 | Tim | ti#gmail.com
Default is a comma delimited string, but you can specify another separator like this : GROUP_CONCAT(t.title,' and ')

mysql group by select with conditional value

In my messages table I have following rows for example,
|----|---------|--------------|------|
| id | user_id | message |status|
|====|=========|==============|======|
| 1 | 2 | msgs 11 | r |
|----|---------|--------------|------|
| 2 | 3 | msgs 12 | r |
|----|---------|--------------|------|
| 3 | 2 | msgs 13 | r |
|----|---------|--------------|------|
| 4 | 3 | msgs 14 | u |
|----|---------|--------------|------|
Now, I need to know two things for each user_id
Whether it has any status u or not.
How many messages are there
For example, a query like below
select user_id, status, count(*) as totalMsg from messages group by user_id
Would brought me following rows
| user_id | status| totalMsg |
|=========|=======|==========|
| 2 | r | 2 |
|---------|-------|----------|
| 3 | r | 2 |
^
|------> I need this value to be 'u' because user 3 has a message u
My current query doesnt really gurantee that it will look for a u in the status column.
Is that possible to do? If so how?
MAX() will work on this since r is the least value based on the lexicographical order.
SELECT user_ID,
MAX(status) status,
COUNT(*) totalMsg
FROM messages
GROUP BY user_ID

Categories