SQL: group by from other table and invert result - mysql

I have some problem with my SQL query
I have table message and table recipient
message is
ID author date
--------------------
0 1 2013-07-08 05:38:47
1 1 2013-07-13 05:38:47
2 1 2013-07-15 05:38:47
3 1 2013-07-15 05:38:47
4 2 2013-07-17 05:38:47
5 1 2013-07-28 05:38:47
recipient is
ID m_id recipient
--------------------
0 0 2
1 1 2
2 2 3
3 3 2
4 4 1
5 5 2
I need return rows from table message with group by recipient column from table recipient with last date in message table
I'll try this
SELECT m.*
FROM message as m
INNER JOIN recipient as r ON (m.ID = r.m_id)
WHERE m.author = 1
GROUP BY r.recipient
ORDER BY m.ID DESC
return is
ID author date
--------------------
2 1 2013-07-15 05:38:47
0 1 2013-07-08 05:38:47
but i need
ID author date
--------------------
5 1 2013-07-28 05:38:47
2 1 2013-07-15 05:38:47
please help
I USE MySQL Server 5.1
I found a solution to my problem
SELECT m.*
FROM (
SELECT * FROM recipient
WHERE 1=1
ORDER BY recipient.ID DESC
) AS r
INNER JOIN message AS m ON (r.m_id = m.ID)
WHERE m.author = 1
GROUP BY r.recipient
just reverse table recipient

Very simple and fast in PostgreSQL with DISTINCT ON - not standard SQL so not available in every RDBMS.
The question doesn't mention it, but deriving from the code examples it is actually looking for the row with the "last date" for each recipient for a given author.
SELECT DISTINCT ON (r.recipient) m.*
FROM message m
JOIN recipient r ON r.m_id = m.id
WHERE m.author = 1
ORDER BY r.recipient, m.date DESC, r.m_id -- to break ties
Details as well as multiple SQL standard alternatives here:
Select first row in each GROUP BY group?
Another solution with basic, standard SQL. Works with every major RDBMS, including MySQL (since that tag has been added):
SELECT m.*
FROM message m
JOIN recipient r ON r.m_id = m.id
WHERE m.author = 1
AND NOT EXISTS (
SELECT 1
FROM message m1
JOIN recipient r1 ON r1.m_id = m1.id
WHERE r1.recipient = r.recipient
AND m1.author = 1
AND m1.date > m.date
)
Only the row with the latest date passes the NOT EXISTS anti-semi-join.

Presuming you're using SQL-Server, you can use a ranking function like ROW_NUMBER:
WITH CTE AS(
SELECT m.*,
RN=ROW_NUMBER()OVER(PARTITION BY r.recipient ORDER BY m.date DESC)
FROM message as m
INNER JOIN recipient as r ON (m.ID = r.m_id)
WHERE m.author = 1
)
SELECT * FROM CTE WHERE RN = 1
Demo

Related

MySQL - Display null column from child table if all values are not distinct

I have the following tables, for example:
invoices
ID Name
1 A
2 B
3 C
4 D
5 E
transactions
ID Invoice_ID User_ID
1 1 10
2 1 10
3 1 10
4 2 30
5 3 20
6 3 40
7 2 30
8 2 30
9 4 40
10 3 50
Now I want to make a select that will pull the invoices and the user_id from the related transactions, but of course if I do that I won't get all the ids, since they may be distinct but there will be only one column for that. What I want to do is that if there are distinct User_ids, I will display a pre-defined text in the column instead of the actual result.
select invoices.id, invoices.name, transactions.user_id(if there are distinct user_ids -> return null)
from invoices
left join transactions on invoices.id = transactions.invoice_id
and then this would be the result
ID Name User_ID
1 A 10
2 B 30
3 C null
4 D 40
5 E null
Is this possible?
You can do the following :
select
invoices.id,
invoices.name,
IF (
(SELECT COUNT(DISTINCT user_id) FROM transactions WHERE transactions.invoice_id = invoices.id) = 1,
(SELECT MAX(user_id) FROM transactions WHERE transactions.invoice_id = invoices.id),
null
) AS user_id
from invoices
Or, alternatively, you can use the GROUP_CONCAT function to output a comma-separated list of users for each invoice. It is not exactly what you asked, but maybe in fact it will be more useful :
select
invoices.id,
invoices.name,
GROUP_CONCAT(DISTINCT transactions.user_id SEPARATOR ',') AS user_ids
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
Try somethingh like:
select i.id, i.name, t.user_id
from invoices i left join
(
select invoice_ID, User_ID
from transactions
group by invoice_ID
having count(invoice_ID)=1
) t on i.id=t.invoice_id
SQL fiddle
You could list all the transactions that have multiple user ids, like this:
select invoices.id, invoices.name, null
from invoices
left join transactions on invoices.id = transactions.invoice_id having count(distinct transactions.user_id) > 1
Also, I think this CASE might suit your needs here:
select invoices.id, invoices.name,
case when count(distinct transactions.user_id) > 1 then null else transactions.user_id end
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
although, I'm not sure this is syntactically correct

My SQL query to retrieve 4 columns based on unique records from two columns

Hi I'm trying to write a correct query for MySQL to retrieve values from 4 columns (from, to, content, date or all if that's easier) based on 2 columns being unique in the same table. The idea is to retrieve a list of only last messages sent and received by a user X
TABLE
msg_id|msg_from|msg_to|msg_new|msg_content|date
1 user1 sw1 1 message1 2014-02-06
2 user1 sw1 1 message2 2014-02-06
3 user1 sw3 0 message3 2014-02-06
4 user1 sw5 0 message4 2014-02-06
5 sw2 sm2 1 message5 0000-00-00
6 sw2 sm4 1 message6 2014-02-20
7 sw1 user1 1 message7 2014-02-20
8 user1 sw5 1 message8 2014-02-20
My last attempt :
SELECT t1.* FROM (SELECT MAX(msg_id) AS nr, msg_from, msg_to
FROM com_msg GROUP BY msg_from) AS t2
INNER JOIN com_msg t1 ON t1.msg_from=t2.msg_from AND t1.msg_id=t2.nr
WHERE t1.msg_to='sw1' OR t1.msg_from='sw1'
which returns :
2| user1|sw1 |1|message2|2014-02-06
7| sw1 |user1|1|message7|2014-02-20
but should only return :
7| sw1 |user1|1|message7|2014-02-20
If I understant your question correctly, this query should return what you need:
SELECT com_msg.*
FROM com_msg INNER JOIN (SELECT MAX(msg_id) max_id
FROM com_msg
WHERE 'sw1' IN (msg_from, msg_to)
GROUP BY
CASE WHEN msg_from!='sw1' THEN msg_from
ELSE msg_to END) m
ON com_msg.msg_id = m.max_id
IDs needs to be ordered, otherwise you should use MAX(date)
Please see fiddle here.
Basically, you need to wrap this with another query, because your where clause is showing you records where either the to or the from equal the user. I'm not a MySQL guru, but you should be able to do something like this:
select top 1 *
FROM (
SELECT t1.* FROM (
SELECT MAX(msg_id) AS nr, msg_from, msg_to
FROM com_msg
GROUP BY msg_from
) AS t2
INNER JOIN com_msg t1 ON t1.msg_from=t2.msg_from AND t1.msg_id=t2.nr
WHERE t1.msg_to='sw1' OR t1.msg_from='sw1'
)
ORDER BY date DESC
This will return the most recent entry from your results based on the date.
If you are looking for just a single record returned of the last email like your example shows, this should do it...
Select TOP 1 msg_from, msg_to, msg_content, date
From com_msg
Where msg_from='sw1' OR msg_to='sw1'
ORDER BY msg_id DESC

Select last N rows following a condition

I've got a database table with logs which has 3 columns:
date | status | projectId
status can be either 0 or 1, primary key is on date and projectID
I'm trying to find out how many times a projectID had status 0 since the last time it was 1.
so if there would be only one projectId
date | status | projectId
1 0 3
2 0 3
3 1 3
4 1 3
5 0 3
6 0 3
this should return 2 (row 5 and 6 are 0 and row 4 is 1)
The thing that makes it hard for me is that I have to maintain the order of date. What would be a good way to tackle such problems, and this one in particular?
Here is how you would do it for one project:
select count(*)
from logs l
where status = 0 and
projectid = 3 and
date > (select max(date) from logs where projectid = 3 and status = 1)
Here is how you would do it for all projects:
select l.projectId, count(l1.projectId)
from logs l left outer join
(select projectId, max(date) as maxdate
from logs
where status = 1
group by projectId
) l1
on l.projectId = l1.projectId and
l.date > l1.date and
l.status = 0
group by l.projectId;
here you have an option in just one select.
http://sqlfiddle.com/#!2/6ce87/11
select *
from logs
where status=0 and date > (select date from logs where status=1 order by date desc limit 1)
Here's one way to get the result for all project_id:
SELECT m.project_id
, COUNT(1) AS mycount
FROM ( SELECT l.project_id
, MAX(l.date) AS latest_date
FROM mytable l
WHERE l.status = 1
) m
JOIN mytable t
ON t.project_id = m.project_id
AND t.date > m.latest_date
AND t.status = 0
If you need only a subset of project_id, the predicate should be added to the WHERE clause in the inline view query:
WHERE l.status = 1
AND l.project_id IN (3,5,7)
EDIT
That query does not return a row if there is no status=0 row after the latest status=1 row. To return a zero count, this could be done with an outer join.
SELECT m.project_id
, COUNT(t.status) AS mycount
FROM ( SELECT l.project_id
, MAX(l.date) AS latest_date
FROM mytable l
WHERE l.status = 1
AND l.project_id IN (3)
) m
LEFT
JOIN mytable t
ON t.project_id = m.project_id
AND t.date > m.latest_date
AND t.status = 0
For optimum performance, the statement could make use of an index with leading columns of project_id and date (in that order) and including the status column, e.g.
ON mytable (`project_id`,`date`,`status`)

Calculate average message quality per user in MySQL

Consider the following tables:
users messages
------------------- ----------------------
user_id avg_quality msg_id user_id quality
------------------- ----------------------
1 1 1 1
2 2 1 0
3 3 1 0
4 1 1
5 1 1
6 2 0
7 2 0
8 3 1
messages.quality is either 0 or 1. I need to calculate the average message quality per user and update users.avg_quality accordingly. So the desired output would be modified users table like so:
users
-------------------
user_id avg_quality <-- DECIMAL (8,2)
-------------------
1 0.60 <-- (3x1 + 2x0) / 5
2 0.00 <-- (2x0) / 2
3 1.00 <-- (1x1) / 1
I've begun my query like this, I know the syntax is incorrect but have no better idea. Do you?
UPDATE messages m, users u
SET avg_quality = (SELECT COUNT(m.msg_id) / SUM(m.quality))
WHERE m.user_id = u.user_id
This should work:
UPDATE users u
INNER JOIN (SELECT a.user_id, AVG(quality) avg_quality
FROM messages a
INNER JOIN users b
ON a.user_id = b.user_id
GROUP BY a.user_id
) tmp
ON u.user_id = tmp.user_id
SET u.avg_quality = tmp.avg_quality;
See the average function:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_avg
Your select should be something like this:
select user_id, AVG(quality) from messages group by user_id
If you begin with an empty users table you could run a query like this one to update it all:
insert into users (user_id, avg_quality)
select m.user_id, coalesce(AVG(m.quality),0) from messages m group by m.user_id
If you need continuous results Luc's proposal will work for you:
update users u left join (
select m.user_id, AVG(m.quality) as average from messages m group by m.user_id
) as average_result_t on u.user_id = average_result_t.user_id
set u.average = coalesce(average_result_t.average,0)

mySQL - GROUP BY but get the most recent row

I've got a budget table:
user_id product_id budget created
-----------------------------------------------------------------
1 1 300 2011-12-01
2 1 400 2011-12-01
1 1 500 2011-12-03
2 2 400 2011-12-04
I've also got a manager_user table, joining a manager with the user
user_id manager_id product_id
------------------------------------
1 5 1
1 9 2
2 5 1
2 5 2
3 5 1
What I'd like to do is grab each of the user that's assigned to Manager #5, and also get their 'budgets'... but only the most recent one.
Right now my statement looks like this:
SELECT * FROM manager_user mu
LEFT JOIN budget b
ON b.user_id = mu.user_id AND b.product_id = mu.product_id
WHERE mu.manager_id = 5
GROUP BY mu.user_id, mu.product_id
ORDER BY b.created DESC;
The problem is it doesn't pull the most recent budget. Any suggestions? Thanks!
To accomplish your task you can do as follows:
select b1.user_id,
b1.budget
from budget b1 inner join (
select b.user_id,
b.product_id,
max(created) lastdate
from budget b
group by b.user_id, b.product_id ) q
on b1.user_id=q.user_id and
b1.product_id=q.product_id and
b1.created=q.lastdate
where b1.user_id in
(select user_id from manager_user where manager_id = 5);
I'm assuming here that your (user_id, product_id, created) combination is unique.
For what it's worth, here's the code that returned what I was looking for:
SELECT DISTINCT(b1.id),mu.user_id,mu.product_id,b1.budget,b1.created
FROM budget b1
INNER JOIN (
SELECT b.user_id, b.product_id, MAX(created) lastdate
FROM budget b
GROUP BY b.user_id, b.product_id) q
ON b1.user_id=q.user_id AND
b1.product_id=q.product_id AND
b1.created=q.lastdate
RIGHT JOIN manager_user mu
ON mu.user_id = b1.user_id AND
mu.product_id = b1.product_id
WHERE mu.manager_id = 5;
Thanks for the help Andrea!