Get column values based on last entry (not null) - mysql

I have a table in MySQL which holds conversations. These conversation are composed of messages. A single conversation looks like the table below.
Importance and eId's are only sometimes set. What I am trying to get from the table is the last message in the conversation (messageId = 4) but with the last set importance and last set eId.
So, from this table
+----------------+-----------+----------+----------+------------+-------+---------+
| conversationId | messageId | time | status | importance | eId | message |
+----------------+-----------+----------+----------+------------+-------+---------+
| 25 | 4 | 11:00:00 | feedback | NULL | NULL | d.. |
+----------------+-----------+----------+----------+------------+-------+---------+
| 25 | 3 | 10:00:00 | open | MEDIUM | NULL | c.. |
+----------------+-----------+----------+----------+------------+-------+---------+
| 25 | 2 | 09:00:00 | feedback | NULL | 123 | b... |
+----------------+-----------+----------+----------+------------+-------+---------+
| 25 | 1 | 08:00:00 | open | HIGH | NULL | a... |
+----------------+-----------+----------+----------+------------+-------+---------+
I need to get this result
+----------------+-----------+----------+----------+------------+-------+---------+
| conversationId | messageId | time | status | importance | eId | message |
+----------------+-----------+----------+----------+------------+-------+---------+
| 25 | 4 | 11:00:00 | feedback | MEDIUM | 123 | d.. |
+----------------+-----------+----------+----------+------------+-------+---------+
I can't get the query to work.
Any help would be appriciated. Thanks.

If there is more than one conversationId in the table, and you want to get the desired result for all of the conversationIds at the same time, then I think you need to join 3 subqueries within subqueries. Something like the 3 below:
SELECT messages.conversationId, messages.messageId, messages.time, messages.status, messages.message
FROM messages
JOIN (
SELECT conversationId, MAX(messageId) as messageId
FROM messages
GROUP BY conversationId) as m2
ON (messages.messageId = m2.messageId);
SELECT messages.conversationId, messages.importance
FROM messages
JOIN (
SELECT conversationId, MAX(messageId) as messageId
FROM messages
WHERE importance IS NOT NULL
GROUP BY conversationId) as m3
ON (messages.messageId = m3.messageId);
SELECT messages.conversationId, messages.eId
FROM messages
JOIN (
SELECT conversationId, MAX(messageId) as messageId
FROM messages
WHERE eId IS NOT NULL
GROUP BY conversationId) as m4
ON (messages.messageId = m4.messageId);
The JOIN would look like this:
SELECT
main.conversationId,
main.messageId,
main.time,
main.status,
importance.importance,
eId.eId,
main.message
FROM (
SELECT messages.conversationId, messages.messageId, messages.time, messages.status, messages.message
FROM messages
JOIN (
SELECT conversationId, MAX(messageId) AS messageId
FROM messages
GROUP BY conversationId) AS m2
ON (messages.messageId = m2.messageId)
) AS main
JOIN (
SELECT messages.conversationId, messages.importance
FROM messages
JOIN (
SELECT conversationId, MAX(messageId) AS messageId
FROM messages
WHERE importance IS NOT NULL
GROUP BY conversationId) AS m3
ON (messages.messageId = m3.messageId)
) AS importance
JOIN (
SELECT messages.conversationId, messages.eId
FROM messages
JOIN (
SELECT conversationId, MAX(messageId) AS messageId
FROM messages
WHERE eId IS NOT NULL
GROUP BY conversationId) AS m4
ON (messages.messageId = m4.messageId)
) AS eId
ON (
main.conversationId = importance.conversationId
AND main.conversationId = eId.conversationId
);
Here's an sqlfiddle: http://sqlfiddle.com/#!2/857aa/38. This assumes that messageId is unique. If it is not unique, then you need to join on messageId AND conversationId.

Maybe a bit oldschool but should do the trick :
SELECT c.conversationId as convId, max(c.messageId), t.time, s.status, i.importance, e.eId, c.message
FROM convs c,
(SELECT conversationId, max(time) AS time FROM convs) t,
(SELECT conversationId, status FROM convs WHERE status IS NOT NULL ORDER BY messageId DESC) s,
(SELECT conversationId, importance FROM convs WHERE importance IS NOT NULL ORDER BY messageId DESC) i,
(SELECT conversationId, eId FROM convs WHERE eId IS NOT NULL ORDER BY messageId DESC) e
WHERE 1=1
AND t.conversationId = c.conversationId
AND s.conversationId = c.conversationId
AND i.conversationId = c.conversationId
AND e.conversationId = c.conversationId
GROUP BY c.conversationId
SQL Fiddle demo

This one is not necessarily the best solution as I had to create this in my local SQL Server (SQL fiddle was down when tried and I have no MySQL installed), also a "quick and dirty" query (means: there might be a better solution for your problem), but as I am keen to help I post it anyway.
If you happen to wait for another, possibly better solution from the community, I will not feel offended :) That is what I wanted to say :)
SELECT TOP 1 conversationId,messageId,RecTime,ConvStatus,
(
SELECT TOP 1 importance
FROM Conversations
WHERE importance IS NOT NULL
ORDER BY messageId DESC
) AS importance,
(
SELECT TOP 1 eId
FROM Conversations
WHERE eId IS NOT NULL
ORDER BY messageId DESC
) AS eId,
UsrMessage
FROM Conversations
ORDER BY messageId DESC
GO
(do not forget that you might have to change "formatting items" to make it work in MySQL)

Related

MySQL Query get the last record then join

Lets say I have a table like this:
table account
id | username
1 | myuser123
2 | secretuser
table subscription
id | username | subsStartDate | subsEndDate
1 | myuser123 | 2017-01-19 | 2017-02-19
2 | secretuser| 2017-01-19 | 2017-02-19
3 | myuser123 | 2017-02-19 | 2017-03-19
4 | secretuser| 2017-02-19 | 2017-03-19
How can I get the latest subsEndDate of each user in table account.
I am looking for an output similar to this:
output looking for
id | username | subsStartDate | subsEndDate
3 | myuser123 | 2017-02-19 | 2017-03-19
4 | secretuser| 2017-02-19 | 2017-03-19
To get the row with latest subsEndDate per user you can use following query
select a.*
from subscription a
left join subscription b on a.username = b.username
and a.subsEndDate < b.subsEndDate
where b.id is null
Demo
SELECT a.*
FROM subscription a
JOIN
( SELECT username
, MAX(subsenddate) subsenddate
FROM subscription
GROUP
BY username
) b
ON b.username = a.username
AND b.subsenddate = a.subsenddate;
A variant for the case where order id isn't equals order max_subsEndDate
SELECT s.*
FROM subscription s
JOIN
(
SELECT username,MAX(subsEndDate) max_subsEndDate
FROM subscription
GROUP BY username
) l
ON s.username=l.username AND s.subsEndDate=l.max_subsEndDate
My variant (if order id is equals order max_subsEndDate)
SELECT *
FROM subscription
WHERE id IN(
SELECT MAX(id)
FROM subscription
GROUP BY username
)
A variant with JOIN
SELECT s.*
FROM subscription s
JOIN
(
SELECT MAX(id) max_id
FROM subscription
GROUP BY username
) l
ON s.id=l.max_id
Something like this :
SELECT s.id, s.username, s.subsStartDate, endate as subsEndDate
FROM
subscription s
INNER JOIN
(SELECT id, Max(subsEndDate) as endate
FROM subscription
GROUP BY id) t ON t.id = s.id

Left join sum, group trouble

I have this query
SELECT
`from_id` as user_id,
MAX(`createdon`) as updated_at,
SUM(`unread`) as new,
u.username,
p.sessionid,
s.access
FROM (
SELECT `from_id`, `createdon`, `unread`
FROM `modx_messenger_messages`
WHERE `to_id` = {$id}
UNION
SELECT `to_id`, `createdon`, 0
FROM `modx_messenger_messages`
WHERE `from_id` = {$id}
ORDER BY `createdon` DESC
) as m
LEFT JOIN `modx_users` as u ON (u.id = m.from_id)
LEFT JOIN `modx_user_attributes` as p ON (p.internalKey = m.from_id)
LEFT JOIN `modx_session` as s ON (s.id = p.internalKey)
GROUP BY `from_id`
ORDER BY `new` DESC, `createdon` DESC;
table
id | message | createdon | from_id | to_id | unread
1 | test | NULL | 5 | 6 | 0
2 | test2 | NULL | 6 | 5 | 1
3 | test3 | NULL | 6 | 5 | 1
result new = 28. Why?
If remove joins new = 2, correctly.
Though it depends on the actual database, pure SQL says that a statement using GROUP BY requires all non-aggregated columns to be in the GROUP BY. Without including all columns, weird stuff can happen, which might explain why you get different results. If you know that the other columns are going to be the same within the user_id, you could do MAX(u.username) or something similar (again, depending on your database server). So I'd try and clean up the SQL statement first.

MySQL: Is there a way to eliminates duplicate entries from the result

I have a table named "message" that stores messages from one user to another user. I want to make a message box that contains both incoming and outcoming messages for particular user. This message box should be contain the last message between two users. So, I have to eliminate duplicate messages between two users. I tried group by and it eliminates duplicate messages but I don't pick the most recent message because order by works after group by. I tried distinct function to eliminates duplicate messages. It works well, but I have to select all colums which isn't possible with distinct
My message table:
+-------+---------+------+-----+------------+
| id | from_id | to_id| text| created_at |
+-------+---------+------+-----+------------+
| 1 | 1 | 2 | mes | 2014-01-16 |
| 2 | 2 | 1 | mes | 2014-01-17 |
| 3 | 1 | 3 | mes | 2014-01-18 |
| 4 | 3 | 1 | mes | 2014-01-19 |
+-------+---------+------+-----+------------+
My Group By SQL
SELECT * FROM message WHERE (from_id = 1 OR to_id = 1) GROUP BY(from_id + to_id) ORDER BY created_at DESC;
And Distinct
SELECT DISTINCT(from_id + to_id) FROM message WHERE (from_id = 1 OR to_id = 1)
In the above example, I want to select second and fourth message.
Is there a way to eliminate duplicate messages between two user from the result?
EDIT: I've improved the example
I tried group by and it eliminates duplicate messages but I don't pick the most recent message because order by works after group by
So you can order it before:
SELECT *
FROM (SELECT * FROM message ORDER BY created_at DESC)
WHERE (from_id = 1 OR to_id = 1) GROUP BY(from_id + to_id);
If I understand correctly what you're trying to achieve, you can leverage LEAST(), GREATEST() functions and non-standard GROUP BY extension behavior in MySQL like this
SELECT id, from_id, to_id, text, created_at
FROM
(
SELECT id, from_id, to_id, text, created_at
FROM message
ORDER BY LEAST(from_id, to_id), GREATEST(from_id, to_id), created_at DESC
) q
GROUP BY LEAST(from_id, to_id), GREATEST(from_id, to_id)
That will give you the last message row for each pair of users.
Output:
+------+---------+-------+------+------------+
| id | from_id | to_id | text | created_at |
+------+---------+-------+------+------------+
| 2 | 2 | 1 | mes | 2014-01-17 |
| 4 | 3 | 1 | mes | 2014-01-19 |
+------+---------+-------+------+------------+
Here is SQLFiddle demo
You can use:
ORDER BY id DESC LIMIT 1
or by timestamp (assuming it contains data AND time):
ORDER BY create_at DESC LIMIT 1
This will sort all results in a descending order and only give you the last row.
Hope this helps!
Just use a simple select. There is no reason for duplicates to be created.
SELECT from_id, to_id, text, created_at
FROM message
WHERE
(from_id = ? AND to_id = ??)
OR (from_id = ?? AND to_id = ?)
Here ? represents one id and ?? the other.
There would be no duplicates here. Ordering can be achieved in a few ways:
Order by most recent message regardless of sender:
SELECT from_id, to_id, text, created_at
FROM message
WHERE
(from_id = ? AND to_id = ??)
OR (from_id = ?? AND to_id = ?)
ORDER BY created_at DESC
Order all sender message first (then by created_at)
SELECT from_id, to_id, text, created_at
FROM message
WHERE
(from_id = ? AND to_id = ??)
OR (from_id = ?? AND to_id = ?)
ORDER BY from_id = ? DESC, created_at DESC
Try adding a HAVING clause after your GROUP BY: HAVING COUNT(*) > 1
or
SELECT
columns names, COUNT(*)
FROM
(SELECT DISTINCT
column names
FROM
message
)
message
GROUP BY
column names
HAVING COUNT(*) > 1
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE message
(`id` int, `from_id` int, `to_id` int, `text` varchar(3), `created_at` datetime)
;
INSERT INTO message
(`id`, `from_id`, `to_id`, `text`, `created_at`)
VALUES
(1, 1, 2, 'mes', '2014-01-16 00:00:00'),
(2, 2, 1, 'MUL', '2014-01-17 00:00:00')
;
Query 1:
SELECT *
FROM message
WHERE from_id = 1 OR to_id = 1
ORDER BY created_at DESC
limit 1
Results:
| ID | FROM_ID | TO_ID | TEXT | CREATED_AT |
|----|---------|-------|------|--------------------------------|
| 2 | 2 | 1 | MUL | January, 17 2014 00:00:00+0000 |

combining 2 select statements with different number of columns, where 1st statement is already using JOIN

I'm trying to combine 2 select statements with different number of columns.
The 1st statement is this:
SELECT s.id, s.date_sent, m.sam_subject, m.sam_msg_id, (SELECT
COUNT(id) FROM tbl_something WHERE schedule_id = s.id) AS
total_recipients FROM tbl_something2 AS s INNER JOIN
tbl_something3 AS m ON s.message_id = m.sam_msg_id ORDER BY
s.date_sent DESC
The 2nd statement:
SELECT * FROM sms_something4 WHERE status = '0' ORDER BY id DESC
the table output for the 1st statement:
id date_sent sam_subject sam_msg_id total_recipients
1 1372880628 e-Newsletter 2 2
output for 2nd:
id | subject | sent | failed | date_sent | data_sent | data_failed | message | sam_uid from | select_members | status | from_no
11 | test | 2 | 0 | 1372881670 | 639176286411,639224588324 | | | | | | 0 | 0
any suggestions on how would i be able to combine these two statements?
my target output is
id | subject | sent | failed | date_sent | sam_subject | total_recipients | date_sent for email
sam_msg_id can be ignored.
Thank you.
here is basic that you need to have .. you might have to trouble shoot. add column as you need.
SELECT *
FROM (
SELECT s.id, s.date_sent, m.sam_subject, m.sam_msg_id, (SELECT COUNT(id)
FROM tbl_something
WHERE schedule_id = s.id) AS total_recipients
FROM tbl_something2 AS s
INNER JOIN tbl_something3 AS m
ON s.message_id = m.sam_msg_id
) as tbl
INNER JOIN (SELECT * FROM sms_something4 WHERE status = '0') as tbl2
ON tbl2.subject = tbl.sam_subject
and tbl.date_sent=tbl2.date_sent
and tbl.total_Recipients = tbl2.sent+ tbl2.failed
ORDER BY tbl.date_sent DESC
As AJP said you can just do this:
SELECT s.id, a.subject,a.sent, s.date_sent, m.sam_subject,
(SELECT COUNT(id) FROM tbl_something WHERE schedule_id = s.id) AS total_recipients
FROM tbl_something2 AS s
INNER JOIN tbl_something3 AS m ON s.message_id = m.sam_msg_id
INNER JOIN
(
SELECT * FROM sms_something4 WHERE status = '0' ORDER BY id DESC
) a on a.subject = m.sam_subject and a.date_sent = s.date_sent
ORDER BY
s.date_sent DESC

How can I filter an SQL query with a GROUP BY clause

I'm trying to formulate an SQL query for a messaging system that will return a list of all threads which have a status of 'OPEN' and whose last message was not posted by a user in a certain group.
The tables involved in the query are:
threads
-------
threadId
timestamp
subject
status
clientId
messages
--------
messageId
userId
messagebody
timestamp
threadId
users
-----
userId
username
groupId
The current query is:
SELECT threadId, timestamp, subject, status, clientId
FROM threads
WHERE status='OPEN'
ORDER BY timestamp DESC
which works fine; but I now have a new requirement - if the last message in the thread was posted by a user with a groupId of 1 then it should NOT be in the result set.
Including the information I need is simple enough:
SELECT threadId, timestamp, subject, status, clientId
FROM threads
INNER JOIN messages USING(threadId)
INNER JOIN users USING(userId)
WHERE status='OPEN'
GROUP BY threadId
ORDER BY timestamp DESC
but at this point I get stuck, because I can't figure out how to get the query to filter out threads based on the messages.userId with the highest timestamp within a particular group. I've looked at HAVING and all the aggregate functions and nothing seems to fit the bill.
Edit
I may not have been clear enough, so I'll try to illustrate with an example.
Here's some example data:
threads
threadId | timestamp | subject | status | clientId
--------------------------------------------------
1 | 1 | One | OPEN | 1
2 | 10 | Two | OPEN | 4
3 | 26 | Three | OPEN | 19
4 | 198 | Four | OPEN | 100
messages
messageId | userId | messagebody | timestamp | threadId
-------------------------------------------------------
1 | 3 | Hello, World| 1 | 1
2 | 1 | Hello | 3 | 1
3 | 1 | ^&%&6 | 10 | 2
4 | 2 | Test | 12 | 2
5 | 4 | Hi mum | 26 | 3
6 | 1 | More tests | 100 | 4
users
userId | username | groupId
---------------------------
1 | Gareth | 1
2 | JimBob | 2
3 | BillyBob | 2
4 | Bod | 3
In this example dataset threads 1 and 4 should be eliminated from the query because user Gareth (a member of group 1) is the last one to post to them (messageId 2 in threadId 1, and messageId 5 in threadId 4). So the result set should only have the thread data (threadId, timestamp, subject, status and clientId) for threads 2 and 3.
I've created an SQLfiddle for this test data.
Can anyone point me in the right direction here?
Sounds like you want this:
SELECT threadId, timestamp, subject, status, clientId
FROM threads t
INNER JOIN messages m
ON t.threadId = m.threadId
INNER JOIN users u
ON m.userId = u.userId
and u.groupId != 1 --placed the groupId filter here.
WHERE status='OPEN'
GROUP BY threadId
ORDER BY timestamp DESC
Edit, this appears to give you what you need:
SELECT t.threadId,
t.timestamp,
t.subject,
t.status,
t.clientId
FROM threads t
INNER JOIN messages m
ON t.threadId = m.threadId
INNER JOIN users u
ON m.userId = u.userId
WHERE status='OPEN'
AND NOT EXISTS (select t1.threadid
FROM threads t1
INNER JOIN messages m
ON t1.threadId = m.threadId
INNER JOIN users u
ON m.userId = u.userId
where u.groupid = 1
and t.threadid = t1.threadid)
ORDER BY timestamp DESC
see SQL Fiddle with Demo
Does adding LIMIT 1 to your query solve your problem?
SELECT threadId, timestamp, subject, status, clientId
FROM threads
INNER JOIN messages USING(threadId)
INNER JOIN users USING(userId)
WHERE status='OPEN'
GROUP BY threadId
ORDER BY timestamp DESC
LIMIT 1
Or changing the where clause to
WHERE status='OPEN' and groupID<>1
I think this is what you want?
select * from threads
where threadId not in
(
select messages.threadId
from messages
inner join (select threadId, MAX(timestamp) maxtime from messages group by threadId) t
on messages.threadId = t.threadId
and messages.timestamp = t.maxtime
where messages.userId=1
)