Selecting latest row of record with distinct parameter - mysql

I want to select a list of non-duplicate records, that invlove a certain user (either in user_to or user_from). I want to retrieve the other user and also the latest content in that record. The list must not have duplications in the other user selected.
For example, I have the following set of records
id user_to user_from content time
1 1 2 ABC 2013-11-05
2 4 2 BBC 2013-11-06
3 3 1 CBC 2013-11-07
4 5 1 ABC 2013-11-08
5 1 2 AAC 2013-11-09
6 5 1 ABB 2013-11-10
7 3 4 CBC 2013-11-11
8 1 2 ACC 2013-11-12
In this case, If the parameter to provide is 1, I want to select record 3,6,8 , the others are not selected because either they are duplicated and older or they do not involve 1.
I have looked into this post and tried something like this:
SELECT u, content, date FROM(
(SELECT
user_from AS u,
MAX(time) AS date,
content
FROM t1
WHERE user_to = :user
)
UNION
(SELECT
user_to AS u,
MAX(time) AS date,
content
FROM t1
WHERE user_from = :user
)
) t2
WHERE date IN (SELECT MAX(date) FROM t2 GROUP BY u)
But no, can't get it done.
Any idea how to write the query? Thanks!

Your query should be this:
select m.* from
message m,
( select user_to,
user_from,
max(dtime) mxdate
from message
where user_from = 1 or user_to = 1
group by user_to, user_from) m2
where m.dtime = m2.mxdate
and (m.user_from = 1 or m.user_to = 1)
See it here at fiddle: http://sqlfiddle.com/#!2/13d4e/4
As you ask in comments: ok. but as I only want the user_id of the other user, is there a way to select only user_to when user_from=1 and user_from when user_to=1 ?
select if(m.user_to=1,m.user_from,m.user_to) as user,
m.content,
m.dtime
from
message m,
( select user_to,
user_from,
max(dtime) mxdate
from message
where user_from = 1 or user_to = 1
group by user_to, user_from) m2
where m.dtime = m2.mxdate
and (m.user_from = 1 or m.user_to = 1)
see it here: http://sqlfiddle.com/#!2/13d4e/5

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,user_to INT NOT NULL
,user_from INT NOT NULL
,content CHAR(3) NOT NULL
,dt DATE NOT NULL
);
INSERT INTO my_table VALUES
(1,1,2,'ABC','2013-11-05'),
(2,4,2,'BBC','2013-11-06'),
(3,3,1,'CBC','2013-11-07'),
(4,5,1,'ABC','2013-11-08'),
(5,1,2,'AAC','2013-11-09'),
(6,5,1,'ABB','2013-11-10'),
(7,3,4,'CBC','2013-11-11'),
(8,1,2,'ACC','2013-11-12');
SELECT x.*
FROM my_table x
JOIN
( SELECT LEAST(user_to,user_from) l
, GREATEST(user_to,user_from) g
, MAX(dt) max_dt FROM my_table
GROUP
BY LEAST(user_to,user_from)
, GREATEST(user_to,user_from)
) y
ON y.l = LEAST(x.user_to,x.user_from)
AND y.g = GREATEST(x.user_to,x.user_from)
AND y.max_dt = x.dt
WHERE 1 IN (x.user_to,x.user_from);
+----+---------+-----------+---------+------------+
| id | user_to | user_from | content | dt |
+----+---------+-----------+---------+------------+
| 3 | 3 | 1 | CBC | 2013-11-07 |
| 6 | 5 | 1 | ABB | 2013-11-10 |
| 8 | 1 | 2 | ACC | 2013-11-12 |
+----+---------+-----------+---------+------------+

if filtering is "1" add this to the query of #Jorge Campos
where user_from = 1 OR user_to=1

Related

How to delete old duplicate rows based on 2 columns but keep the latest row?

So I have this table (called test_table)
id | hotel_id | user_id
1 | 1 | 1
2 | 1 | 1
3 | 1 | 2
4 | 2 | 3
5 | 1 | 2
6 | 3 | 3
So if the hotel_id and the user_id is the same, then I want to delete the duplicate rows but keep the latest row (the latest row is the row with the higher id).
So after deleting my table would look like the table below.
I deleted id 1 because there is a newer row id 2.
I deleted id 3 because there is a newer row id 5.
id | hotel_id | user_id
2 | 1 | 1
4 | 2 | 3
5 | 1 | 2
6 | 3 | 3
I tried with the code below but it only checks if one column is a duplicate. What is the most efficient way to do this?
delete test_table
from test_table
inner join (
select max(id) as lastId, hotel_id
from test_table
group by hotel_id
having count(*) > 1) duplic on duplic.hotel_id = test_table.hotel_id
where test_table.id < duplic.lastId;
The traditional way in MySQL uses a JOIN:
delete tt
from test_table tt join
(select tt.hotel_id, tt.user_id, max(tt.id) as max_id
from test_table tt
group by tt.hotel_id, tt.user_id
) tokeep
on tokeep.hotel_id = tt.hotel_id and
tokeep.user_id = tt.user_id and
tokeep.max_id > tt.id;
If id is unique in the table, this can be simplified to:
delete tt
from test_table tt left join
(select tt.hotel_id, tt.user_id, max(tt.id) as max_id
from test_table tt
group by tt.hotel_id, tt.user_id
) tokeep
on tt.id = tokeep.max_id
where to_keep.max_id is null;
In MySQL 8.x (available since April 2018) you can use windows functions to identify the obsolete rows. For example:
delete from test_table where id in (
select id
from (
select
id, row_number() over(partition by hotel_id, user_id order by id desc) as rn
from test_table
) x
where rn <> 1
)

Get Previous and Next record from database and loop them

I have a table with IDs from 1 to 8. I want something like this
If i'm on 4, I should get 3,5
If i'm in on 1, I should get 8,2
If in on 8, I should get 7, 1
Basically looping through the table records
This is my current code
-- previous or last, if there is no previous
SELECT *
FROM news
WHERE id < 1 OR id = MAX(id)
ORDER BY id DESC
LIMIT 1
-- next or first, if there is no next
SELECT *
FROM news
WHERE id > 1 OR id = MIN(id)
ORDER BY id ASC
LIMIT 1
But it says Invalid use of group function. Any help?
If id is sequential you can do this:
SQL DEMO
SELECT o.id,
COALESCE(b.id, (SELECT MAX(ID) FROM Table1)) as before_id,
COALESCE(a.id, (SELECT MIN(ID) FROM Table1)) as after_id
FROM Table1 o
LEFT JOIN Table1 b
ON o.id = b.id + 1
LEFT JOIN Table1 a
ON o.id = a.id - 1
ORDER BY o.id
OUTPUT
| id | before_id | after_id |
|----|-----------|----------|
| 1 | 8 | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 4 |
| 4 | 3 | 5 |
| 5 | 4 | 6 |
| 6 | 5 | 7 |
| 7 | 6 | 8 |
| 8 | 7 | 1 |
If ids are not sequential you need use row_number() (mysql ver 8+) or session variables to create a sequence.
I guess that you want to show "prev" and "next" buttons, when the user views a news article. I would get the previous and the next ID in the main query, when you fetch the article data:
select n.*, -- select columns you need
coalesce(
(select max(n1.id) from news n1 where n1.id < n.id ),
(select max(id) from news)
) as prev_id,
coalesce(
(select min(n1.id) from news n1 where n1.id > n.id ),
(select min(id) from news)
) as next_id
from news n
where n.id = ?
db-fiddle demo
Now you can use prev_id and next_id for your buttons, or prefetch the corresponding articles with a simple select * from news where id = ? query.
You can remove the filtering in your approach and add logic to the ORDER BY:
(SELECT n.*
FROM news
ORDER BY (id < 1), id DESC
LIMIT 1
) UNION ALL
(SELECT n.*
FROM news
ORDER BY (id > 1), id ASC
LIMIT 1
) ;
If you want the id values in one row, you can use aggregation:
select coalesce(max(case when id < 1 then id end), max(id)) as prev_id,
coalesce(min(case when id > 1 then id end), min(id)) as next_id
from news n;
In both cases, 1 is a sample id and the "1" can be replaced with any value.

How to limit a query by column value

Following query...
SELECT event_id, user_id FROM EventUser WHERE user_id IN (1, 2)
...gives me the following result:
+----------+---------+
| event_id | user_id |
+----------+---------+
| 3 | 1 |
| 2 | 1 |
| 1 | 1 |
| 5 | 1 |
| 4 | 1 |
| 6 | 1 |
| 4 | 2 |
| 2 | 2 |
| 1 | 2 |
| 5 | 2 |
+----------+---------+
Now, I want to modify the above query so that I only get for example two rows for each user_id, eg:
+----------+---------+
| event_id | user_id |
+----------+---------+
| 3 | 1 |
| 2 | 1 |
| 4 | 2 |
| 5 | 2 |
+----------+---------+
I am thinking about something like this, which of course does not work:
SELECT event_id, user_id FROM EventUser WHERE user_id IN (1, 2) LIMIT 2 by user_id
Ideally, this should work with offsets as well because I want to use it for paginations.
For performance reasons it is essential to use the WHERE user_id IN (1, 2) part of the query.
One method -- assuming you have at least two rows for each user -- would be:
(select min(event_id) as event_id, user_id
from t
where user in (1, 2)
group by user_id
) union all
(select max(event_id) as event_id, user_id
from t
where user in (1, 2)
group by user_id
);
Admittedly, this is not a "general" solution, but it might be the simplest solution for what you want.
If you want the two biggest or smallest, then an alternative also works:
select t.*
from t
where t.user_id in (1, 2) and
t.event_id >= (select t2.event_id
from t t2
where t2.user_id = t.user_id
order by t2.event_id desc
limit 1, 1
);
Here is a dynamic example for such problems, Please note that this example is working in SQL Server, could not try on mysql for now. Please let me know how it works.
CREATE TABLE mytable
(
number INT,
score INT
)
INSERT INTO mytable VALUES ( 1, 100)
INSERT INTO mytable VALUES ( 2, 100)
INSERT INTO mytable VALUES ( 2, 120)
INSERT INTO mytable VALUES ( 2, 110)
INSERT INTO mytable VALUES ( 3, 120)
INSERT INTO mytable VALUES ( 3, 150)
SELECT *
FROM mytable m
WHERE
(
SELECT COUNT(*)
FROM mytable m2
WHERE m2.number = m.number AND
m2.score >= m.score
) <= 2
How about this?
SELECT event_id, user_id
FROM (
SELECT event_id, user_id, row_number() OVER (PARTITION BY user_id) AS row_num
FROM EventUser WHERE user_id in (1,2)) WHERE row_num <= n;
And n can be whatever
Later but help uses a derived table and the cross join.
For the example in this post the query will be this:
SELECT
#row_number:=CASE
WHEN #user_no = user_id
THEN
#row_number + 1
ELSE
1
END AS num,
#user_no:=user_id userid, event_id
FROM
EventUser,
(SELECT #user_no:=0,#row_number:=0) as t
group by user_id,event_id
having num < 3;
More information in this link.

Query with multiple IN clause on multiple rows

Say I have this table:
| ID | idService |
| 1 | 5 |
| 1 | 10 |
| 2 | 5 |
| 2 | 15 |
| 3 | 5 |
| 3 | 20 |
| 4 | 5 |
| 4 | 25 |
I'd like to be able to select all the clients(ID) where the product 5 is present with at least one of the other products (10, 15, 20, 25).
I'm able to do it for one of those combinations with the query:
SELECT ID FROM table WHERE idService IN (5,10) GROUP BY ID HAVING COUNT(DISTINCT idService) = 2
However, if I add the other conditions in this query, I will get all the clients that have at least two of the products, doesn't matter if it's 5 with 10 or 10 with 20.
SELECT ID FROM table WHERE idService IN (5,10) OR idService IN (5,15) OR idService IN (5,20) OR idService IN (5,25) GROUP BY ID HAVING COUNT(DISTINCT idService) = 2
I'm wondering if it's possible to change the IN clauses or replace them to get only the clients with one of the four valid combinations.
Edit:
I've been able to make the query work using a subquery.
SELECT ID FROM table WHERE ID IN ( SELECT ID FROM table WHERE idService =5) AND ( idService =10 OR idService =15 OR idService =20 OR idService =25 ) GROUP BY idSPOrder
select distinct ID from table t1
where (select count(*) from table where ID = t1.ID group by ID) >=2
and (select count(*) from table where Idservice = 5 and ID = t1.ID group by ID) > 0
select Id
from Table T
where exists (select 1 from Table where Id = T.Id AND idService = 5)
group by Id
having count(*) >= 2
select table.id from table,
(select id from table where idservice != 5 group by id)t
where idservice = 5 and table.id = t.id

mySQL select occurrences in two columns given third colum value

i have a table like this
Sender_id | Receiver_id | Topic
456 | 123 | 1
123 | 456 | 3
123 | 456 | 2
456 | 123 | 2
123 | 789 | 1
123 | 456 | 1
123 | 789 | 4
456 | 123 | 1
I want to know for every given Topic who are the speakers (can be both sender or receiver) involved in that conversation
eg. for Topic 2, Mr. 123 and Mr. 456 are the only two speakers
The desired result would be to have just the values: 123, 456
Speakers | Topic
123 | 2
456 | 2
this is my first attempt but i want ot merge the two resulting columns
SELECT *
FROM (
SELECT sender_id
FROM [table]
WHERE topic = '2'
AND sender_id !=0
GROUP BY sender_id) a
INNER JOIN (
SELECT receiver_id
FROM [table]
WHERE topic = '2'
AND receiver_id !=0
GROUP BY receiver_id) b
ON sender_id
You don't need a JOIN, you need a UNION:
SELECT sender_id Speakers
FROM [table]
WHERE topic = '2'
AND sender_id !=0
UNION
SELECT receiver_id
FROM [table]
WHERE topic = '2'
AND receiver_id !=0
You don't need to group to remove duplicate values as the UNION does that for you.
SELECT Sender_id as speakers, Topic from Table1 WHERE Topic ='2'
UNION
SELECT Receiver_id as speakers, Topic FROM Table1 WHERE Topic = '2'
Working fiddle http://sqlfiddle.com/#!9/4cf5e3/2
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,Sender_id INT NOT NULL
,Receiver_id INT NOT NULL
,Topic INT NOT NULL
);
INSERT INTO my_table (sender_id,receiver_id,topic) VALUES
(456,123,1),
(123,456,3),
(123,456,2),
(456,123,2),
(123,789,1),
(123,456,1),
(123,789,4),
(456,123,1);
SELECT speaker FROM
(
SELECT sender_id speaker,topic FROM my_table
UNION
SELECT receiver_id,topic FROM my_table
) x
WHERE topic = 2;
+---------+
| speaker |
+---------+
| 123 |
| 456 |
+---------+