SQL query to find user by it's specific latest action - sql

I have following db scheme below where all user actions are collected:
user_action
===============================
user_id action_id timestamp
===============================
1 1 2022/05/07 17:23
1 2 2022/05/07 17:24
1 1 2022/05/07 17:25
2 1 2022/05/07 17:23
2 2 2022/05/07 17:24
3 2 2022/05/07 17:23
3 1 2022/05/07 17:24
action
===============================
id name
===============================
1 blocked
2 unblocked
The goal is to find all recently blocked users. So the expected result is to find 1 and 3 user ids since those users were recently blocked.
I've tried to play with following SQL below, but still do not have good understending how to finalize this:
select user_id, action_id, max(timestamp) as timestamp
from user_action
where action_id in (1,2)
group by user_id, action_id
Currently query is able to return only following:
===============================
user_id action_id timestamp
1 2 2022/05/07 17:24
1 1 2022/05/07 17:25
2 1 2022/05/07 17:23
2 2 2022/05/07 17:24
3 2 2022/05/07 17:23
3 1 2022/05/07 17:24
For the result above I need to all users where action_id = 1 and timestamp is bigger than in action_id = 2

One solution is to use ROW_NUMBER inside an embedded query and then filter the result for the last timestamp and the desired action_id.
SELECT ua.user_id, ua.action_id, ua.timestamp
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY timestamp DESC) rn
FROM user_action) ua
WHERE ua.rn = 1 AND ua.action_id = 1

You can use CROSS APPLY to retrieve the last action_id by timestamp and then apply a filter.
SELECT DISTINCT ua.user_id, ca.action_id, ca.timestamp
FROM user_action ua
CROSS APPLY (SELECT TOP 1 *
FROM user_action
WHERE user_id = ua.user_id
ORDER BY timestamp DESC) ca
WHERE ca.action_id = 1

An easier solution for most dbms might be:
with cte as (
select *,
row_number() over(partition by user_id order by timestamp_dt desc) as row_num
from user_action
)
select user_id,
action_id,
timestamp_dt
from cte
where row_num=1
and action_id=1 ;
Result:
user_id action_id timestamp_dt
1 1 2022-05-07 17:25:00
3 1 2022-05-07 17:24:00
Demo
Note that you don't need action table because you have action_id=1.
If you should include action table than use:
with cte as (
select *,
row_number() over(partition by user_id order by timestamp_dt desc) as row_num
from user_action
)
select user_id,
action_id,
timestamp_dt
from cte
inner join action a on a.id=cte.action_id
where row_num=1
and a.name='blocked' ;
Demo

You can use an inner join with a subquery to retrieve what you are looking for
SELECT u.user_id,u.action_id,u.timestamp
FROM user_action u
INNER JOIN (
SELECT ua.user_id, MAX(ua.timestamp) as ts FROM user_action ua
GROUP BY ua.user_id
)as t ON u.user_id = t.user_id AND u.timestamp = t.ts
WHERE action_id = 1;
This should work for the common dbms and return this according to your data
user_id action_id timestamp
=========================================
1 1 2022-05-07 17:25:00
3 1 2022-05-07 17:24:00
You can check the fiddle right here
Note: You can avoid having the action table as they mentioned

Related

row_number based on case when with text content

Here is a task I need to get three elements based on the given conditions:
three elements: user_id, order_time, ordered_subject
each unique user_id
earliest order_time
ordered_subjects' order should be app-> acc ->ayy
if there are several order_time are the same, you should take only one subject followed by the 3rd requirement
original table: user_order
user_id
order_time
ordered_subject
1
2001-02-09
app
2
2001-02-09
app
3
2001-02-10
ayy
1
2001-02-09
acc
1
2001-02-10
app
4
2001-02-08
ayy
5
2001-02-09
acc
5
2001-02-09
ayy
expected table:
user_id
order_time
ordered_subject
1
2001-02-09
app
2
2001-02-09
app
3
2001-02-10
ayy
4
2001-02-08
ayy
5
2001-02-09
acc
I come up with the idea of case when and row_number() over, but it doesn't work
the code I tried:
select
a.uid,
a.subject,
b.min_time,
(case when "app" then 1
when "acc" then 2
when "ayy" then 3
else 4 end) as rn,
row_number() over(partition by
concat(uid,order_id)
order by
rn)
from (
select uid, min(order_time) as min_time
from user_order
group by
uid
) as b
-- join
user_order as a
-- on
where
a.uid = b.uid
and
b.min_time = a.order_time
How should I fix this?
You want one result row per user. Per user you want the earliest order and if there is more than one order on the earliest date you prefer the order subject app over acc and acc over ayy.
You want to use ROW_NUMBER, so partition by user ID and order by date and the order subject in the desired order.
select user_id, order_time, ordered_subject
from
(
select
user_id, order_time, ordered_subject,
row_number() over
(partition by user_id
order by order_time,
case ordered_subject
when 'app' then 1
when 'acc' then 2
when 'ayy' then 3
else 4
end) as rn
from mytable
) numbered
where rn = 1
order by user_id;

SQL: How do I display all records per unique id, but not the first record ever recorded in SQL

Example:
id Pricemoney time/date
1 100 01/20/2017
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017
2 100 01/24/2017
3 1000 01/19/2017
3 100 01/22/2017
3 10 01/24/2017
I want to run a SQL query where I can display all the Id and it's pricemoney BUT NOT include the first record (based on time/date) per unique
Just to clarify what I do not want to be displayed
userid Pricemoney issuedate
1 100 01/20/2017 -- not included
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017 --- not inlcuded
2 100 01/24/2017
3 1000 01/19/2017 -- not included
3 100 01/22/2017
3 10 01/24/2017
Expected result:
id Pricemoney time/date
1 10 01/21/2017
1 1000 01/21/20147
2 100 01/24/2017
3 100 01/22/2017
3 10 01/24/2017
You can use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum
from <tablename> t
) t
where seqnum > 1;
If you want to keep single rows, you can do:
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum,
count(*) over (partition by id) as cnt
from <tablename> t
) t
where seqnum > 1 and cnt > 1;
You may use EXISTS
select t1.*
from data t1
where exists (
select 1
from data t2
where t1.id = t2.id and t2.time_date < t1.time_date
)
you can try this :
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney
above query not duplicated records also ignore, if want
not duplicated records then use having count(id) > 1 in left query e,g.
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
having COUNT(id) > 1
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney

Oracle SQL: Transform rows to multiple columns

I'm using Oracle 11G and need a way to turn rows into new groups of columns in a select statement. We're transitioning to a 1:3 relationship for some of our data and need a way to get it into a view. Can you help us transform data that looks like this:
+---------+------------+
| User_Id | Station_Id |
+---------+------------+
| 1 | 203 |
| 1 | 204 |
| 2 | 203 |
| 3 | 487 |
| 3 | 3787 |
| 3 | 738 |
+---------+------------+
into this:
+---------+-------------+-------------+---------------+
| User_Id | Station_One | Station_Two | Station_Three |
+---------+-------------+-------------+---------------+
| 1 | 203 | 204 | Null |
| 2 | 203 | Null | Null |
| 3 | 487 | 3787 | 738 |
+---------+-------------+-------------+---------------+
Let me know what ever other specifics you would like and thank you for any help you can give!
You can use row_number and self joins:
with cte as
(
select userid, stationid,
row_number() over(partition by userid order by stationid) rn
from tbl
)
select distinct c1.userid,
c1.stationid station_one,
c2.stationid station_two,
c3.stationid station_three
from cte c1
left join cte c2 on c1.userid=c2.userid and c2.rn=2
left join cte c3 on c1.userid=c3.userid and c3.rn=3
where c1.rn=1
See the demo
You can also do it with row_number and subqueries:
with cte as
(
select userid, stationid,
row_number() over(partition by userid order by stationid) rn
from tbl
)
select distinct userid,
(select stationid from cte c where c.userid=cte.userid and c.rn=1) station_one,
(select stationid from cte c where c.userid=cte.userid and c.rn=2) station_two,
(select stationid from cte c where c.userid=cte.userid and c.rn=3) station_three
from cte
See the demo
The easiest way to accomplish this in my experience is to use conditional aggregation:
WITH mydata AS (
SELECT 1 AS user_id, 203 AS station_id FROM dual
UNION ALL
SELECT 1 AS user_id, 204 AS station_id FROM dual
UNION ALL
SELECT 2 AS user_id, 203 AS station_id FROM dual
UNION ALL
SELECT 3 AS user_id, 487 AS station_id FROM dual
UNION ALL
SELECT 3 AS user_id, 3787 AS station_id FROM dual
UNION ALL
SELECT 3 AS user_id, 738 AS station_id FROM dual
)
SELECT user_id
, MAX(CASE WHEN rn = 1 THEN station_id END) AS station_one
, MAX(CASE WHEN rn = 2 THEN station_id END) AS station_two
, MAX(CASE WHEN rn = 3 THEN station_id END) AS station_three
FROM (
SELECT user_id, station_id, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY rownum ) AS rn
FROM mydata
) GROUP BY user_id;
Just replace the mydata CTE in the above query with whatever your table's name is:
SELECT user_id
, MAX(CASE WHEN rn = 1 THEN station_id END) AS station_one
, MAX(CASE WHEN rn = 2 THEN station_id END) AS station_two
, MAX(CASE WHEN rn = 3 THEN station_id END) AS station_three
FROM (
SELECT user_id, station_id, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY rownum ) AS rn
FROM mytable
) GROUP BY user_id;

Tsql Update row with same ID and latest date

How can I update row U with the same ID2 and latest date. I am able to write the select clause:
SELECT ID2
, MAX(Date)
FROM TABLE
GROUP BY ID2
but I have problem with update clause.
I have table:
ID1 |ID2 |Date |U
1 1 2015-02-18 NULL
2 1 2015-02-11 NULL
3 2 2015-02-17 NULL
4 2 2015-02-14 NULL
5 2 2015-02-11 NULL
6 3 2015-02-14 NULL
7 3 2015-02-10 NULL
What I want to achive:
ID1 |ID2 |Date |U
1 1 2015-02-18 Update
2 1 2015-02-11 NULL
3 2 2015-02-17 Update
4 2 2015-02-14 NULL
5 2 2015-02-11 NULL
6 3 2015-02-14 Update
7 3 2015-02-10 NULL
I will do this using CTE with Row_Number window function
;with cte as
(
select ID1 ,ID2 ,Date ,U, Row_Number() over(partition by ID2 order by Date desc) rn
From Yourtable
)
update Cte set U = 'Update'
where RN=1
When there is a tie in max date per ID2 then use Dense_rank to update both the records.
;with cte as
(
select ID1 ,ID2 ,Date ,U, Dense_Rank() over(partition by ID2 order by Date desc) rn
From Yourtable
)
update Cte set U = 'Update'
where RN=1
One approach would be to use your SELECT...MAX query to filter the TABLE. Something like this;
UPDATE T SET U = 'UPDATE'
FROM T JOIN (
SELECT ID2
, MAX(Date) AS MaxDate
FROM TABLE
GROUP BY ID2) AS X ON X.ID2 = T.ID2 AND X.MaxDate = T.Date
One point to note about this approach is that if there is more than 1 record with the max Date per ID2 then all records with the max Date will be updated.
interesting way to achieve this:
UPDATE T1
SET T1.U='Update'
FROM TestTable T1
WHERE 0=(SELECT COUNT(1) FROM TestTable T2 WHERE T1.ID2=T.ID2 AND T1.Date<T2.Date)

SQL incremental id for every user_id

I have data:
user_id user_login_date
1 2013.07.05
1 2013.07.15
1 2013.07.16
1 2013.07.17
2 2013.07.05
2 2013.07.05
2 2013.07.15
And I want to make virtual table that would look like this:
user_id user_login_date date_id
1 2013.07.05 1
1 2013.07.15 2
1 2013.07.16 3
1 2013.07.17 4
2 2013.07.05 1
2 2013.07.05 2
2 2013.07.15 3
How do I do that?
I tried:
WITH user_count
AS (
SELECT user_id, user_login_date
FROM users
)
SELECT user_count.user_id, user_count.user_login_date, COUNT(user_count.user_id)
FROM users, user_count
WHERE users.user_login_date >= user_count.user_login_date
AND users.user_id = user_count.user_id
GROUP BY user_count.user_id, user_count.user_login_date
ORDER BY user_count.user_id, user_count.user_login_date;
But the result isn't that that I want.
select
user_id, user_login_date,
row_number() over(
partition by user_id
order by user_login_date
) as date_id
from users
order by user_id, date_id
select row_number() over (partition by user_id order by user_login_date) as date_id
, yt.*
from YourTable yt