Select most recent change in SQL - sql-server-2008

I have a table which consist of four columns:
TimeStamp | Leg1 | Leg2 | Leg3
I need to select the leg which was most recently changed as the final leg.

Assuming that only one leg value will change for each time stamp, this will give you the time of last change, what leg was changed, what the new value is and what the previous value was.
Here is a link to see it in action http://rextester.com/ZZHB91131
The code is here:
IF OBJECT_ID('tempdb..#thisTest') IS NOT NULL DROP TABLE #thisTest
CREATE TABLE #thisTest(
DateTimeStamp datetime,
Leg1 int,
Leg2 int,
Leg3 int)
INSERT INTO #thisTest (DateTimeStamp,Leg1,Leg2,Leg3) VALUES
('7/1/2016',1,1,1),
('7/2/2016',2,1,1),
('7/3/2016',2,2,1),
('7/4/2016',2,2,2),
('7/5/2016',2,2,3),
('7/6/2016',2,3,3),
('7/7/2016',3,3,3),
('7/8/2016',3,4,4),
('7/9/2016',4,3,4),
('7/10/2016',4,3,5),
('7/11/2016',4,4,5)
SELECT * FROM #thisTest
SELECT TOP 1
DateTimeStamp as TimeOfLastChange,
CASE
WHEN LEAD(Leg1) OVER(ORDER BY DateTimeStamp DESC) <> Leg1 THEN 'Leg1'
WHEN LEAD(Leg2) OVER(ORDER BY DateTimeStamp DESC) <> Leg2 THEN 'Leg2'
WHEN LEAD(Leg3) OVER(ORDER BY DateTimeStamp DESC) <> Leg3 THEN 'Leg3'
END AS LegThatWasLastChanged,
CASE
WHEN LEAD(Leg1) OVER(ORDER BY DateTimeStamp DESC) <> Leg1 THEN LEAD(Leg1) OVER(ORDER BY DateTimeStamp DESC)
WHEN LEAD(Leg2) OVER(ORDER BY DateTimeStamp DESC) <> Leg2 THEN LEAD(Leg2) OVER(ORDER BY DateTimeStamp DESC)
WHEN LEAD(Leg3) OVER(ORDER BY DateTimeStamp DESC) <> Leg3 THEN LEAD(Leg3) OVER(ORDER BY DateTimeStamp DESC)
END AS PreviousLegValue,
CASE
WHEN LEAD(Leg1) OVER(ORDER BY DateTimeStamp DESC) <> Leg1 THEN Leg1
WHEN LEAD(Leg2) OVER(ORDER BY DateTimeStamp DESC) <> Leg2 THEN Leg2
WHEN LEAD(Leg3) OVER(ORDER BY DateTimeStamp DESC) <> Leg3 THEN Leg3
END AS LastLegValue
FROM
#thisTest
ORDER BY
DateTimeStamp DESC

Related

I want to tune a query that gets only the 1st in the ranking

row_number query question.
To get the data where rn is 1, you can wrap the query below and import it with where rn = 1.
Is there a way to get only those whose rn is 1 in the current query??
SELECT a1.member_no
, row_number() OVER (PARTITION BY a1.member_no ORDER BY a1.avg_hit_rate desc , a1.top_hit_cnt ) as rn
, a1.join_no
FROM ht_typing_contents_join_log a1
WHERE a1.reg_date >= STR_TO_DATE(CONCAT( date_format(now(), '%Y%m%d' ) , '000000'), '%Y%m%d%H%i%s')
AND a1.reg_date <= STR_TO_DATE(CONCAT( date_format(now(), '%Y%m%d' ) , '235959'), '%Y%m%d%H%i%s')
and a1.success_yn = 'Y'
AND a1.len_type = '1'
Yes, you should subquery what you currently have and restrict to rn = 1:
WITH cte AS (
SELECT a1.member_no,
ROW_NUMBER() OVER (PARTITION BY a1.member_no
ORDER BY a1.avg_hit_rate DESC, a1.top_hit_cnt) AS rn,
a1.join_no
FROM ht_typing_contents_join_log a1
WHERE DATE(a1.reg_date) = CURDATE() AND
a1.success_yn = 'Y' AND
a1.len_type = '1'
)
SELECT member_no, join_no
FROM cte
WHERE rn = 1;
What I want is below.
SELECT a1. member_no
, row_number() OVER (PARTITION BY a1.member_no ORDER BY a1.avg_hit_rate desc , a1.top_hit_cnt ) as rn
, a1. join_no
FROM ht_typing_contents_join_log a1
WHERE a1.reg_date >= STR_TO_DATE(CONCAT( date_format(now(), '%Y%m%d' ) , '000000'), '%Y%m%d%H%i%s')
AND a1.reg_date <= STR_TO_DATE(CONCAT( date_format(now(), '%Y%m%d' ) , '235959'), '%Y%m%d%H%i%s')
and a1.success_yn = 'Y'
AND a1.len_type = '1'
AND rn = 1

Need to get an id associated with the most recent column value

SQL QUERY
select id, month_id, id_type,
max(case when immediate_prev <> id_type then immediate_prev
end) over (partition by id, id_type, (seqnum - seqnum_2)
) as id_type_prev
from (select *,
row_number() over (partition by id order by month_id) as seqnum,
row_number() over (partition by id, id_type order by month_id) as seqnum_2,
lag(id_type) over (partition by id order by month_id) as immediate_prev
from `my_table`
WHERE id = 123
)
ORDER BY month_id asc
my_table data
id|month_id|id_type
123|202001|aaa
123|202002|aaa
123|202003|aaa
123|202004|bbb
123|202005|bbb
123|202006|bbb
Query return data
id|month_id|id_type|id_type_prev
123|202001|aaa|null
123|202002|aaa|null
123|202003|aaa|null
123|202004|bbb|aaa
123|202005|bbb|aaa
123|202006|bbb|aaa
I have a SQL query that returns the previous id_type value for a given id. I would also like to know the month_id of the previous id_type but I am not sure how to get this information. Above is the table data and what my current query returns.
Below is the additional data I am after, I woud like help getting the month_id_prev added to my above query. This would be the previous id_type's most recent month_id.
id|month_id|id_type|id_type_prev|month_id_prev
123|202001|aaa|null|null
123|202002|aaa|null|null
123|202003|aaa|null|null
123|202004|bbb|aaa|202003
123|202005|bbb|aaa|202003
123|202006|bbb|aaa|202003
Consider the approach below using your sample data:
with sample_data as (
select 123 as id, 202001 as month_id, 'aaa' as id_type,
union all select 123 as id, 202002 as month_id, 'aaa' as id_type,
union all select 123 as id, 202003 as month_id, 'aaa' as id_type,
union all select 123 as id, 202004 as month_id, 'bbb' as id_type,
union all select 123 as id, 202005 as month_id, 'bbb' as id_type,
union all select 123 as id, 202006 as month_id, 'bbb' as id_type,
),
cte1 as (
select id, month_id, id_type,
max(case when immediate_prev <> id_type then immediate_prev
end) over (partition by id, id_type, (seqnum - seqnum_2)
) as id_type_prev,
latest_ym,
lag(latest_ym) over (partition by id order by month_id) as prev_ym
from
(select *,
row_number() over (partition by id order by month_id) as seqnum,
row_number() over (partition by id, id_type order by month_id) as seqnum_2,
lag(id_type) over (partition by id order by month_id) as immediate_prev,
last_value(month_id) over (partition by id,id_type order by id) as latest_ym
from sample_data)
)
select
id,
month_id,
id_type,
id_type_prev,
max(if(month_id > prev_ym, prev_ym, null)) over (partition by id,id_type) as month_id_prev
from cte1
order by month_id asc
Output:

merge 2 login/logout times tables into 1 with MySQL 5.7

i have two table that each one of them have user name and a datetime column, one table for login time and the other for logout. i run into same problem as my, described in this question : Merge two tables based on time,
with this great solution:
select
id,
min(case when action = 'in' then dt end) login_time,
max(case when action = 'out' then dt end) logout_time
from (
select
t.*,
sum(case when action = 'in' then 1 else 0 end)
over(partition by id order by dt) grp
from (
select id, login_time dt, 'in' action from login
union all select id, logout_time, 'out' from logout
) t
) t
group by id, grp
order by id, grp
but this solution use "OVER" clause and it not supported in MySQL5.7.
can anybody help me please convert this same logic but with no the "OVER" clause and support in MySQL5.7.
One method uses variables:
select id,
min(case when action = 'in' then dt end) as login_time,
max(case when action = 'out' then dt end) as logout_time
from (select t.*,
(#grp := if(#id = id, #grp,
if(#id := id, #grp + 1, #grp + 1)
)
) as grp
from (select id, login_time as dt, 'in' as action
from login
union all
select id, logout_time, 'out'
from logout
order by 1, 2
) t cross join
(select #id := -1, #grp := -1) params
) t
group by id, grp
order by id, grp;
Note: If id is a string, then #id should be initialized to '' rather than -1.
Note that the assignment of variables in SELECT statements has been deprecated in MySQL 8+. You really should upgrade to MySQL 8+ and learn window functions.

MySQL: Select last x items of each user

I have a table with lots of timestamped entries for each user in my system:
id (int, PK)
user_id (int, FK)
date (datetime)
description (text)
other columns...
How do I select the last x (e.g. 2) items of each user?
(By last items, I mean of course items sorted desc by date)
Have a correlated sub-query to find a user_id's "second last" date:
select t1.*
from tablename t1
where t1.date >= (select date from tablename t2
where t2.user_id = t1.user_id
order by date desc
limit 1,1)
If you want 3 last rows for each user, adjust to LIMIT 2,1.
Try this:
SELECT t.id, t.user_id, t.`date`, t.`description`
FROM (SELECT id, user_id, `date`, `description`
FROM mytable t1
ORDER BY t1.date desc
LIMIT X) t --Change x to the number which you want.
GROUP BY t.id, t.user_id, t.`date`, t.`description`
Give a row number based on user_id order by descending order of date. Then select the rows having row number 1 and 2.
Query
select t1.id, t1.user_id, t1.`date`, t1.`description` from
(
select id, user_id, `date`, `description`,
(
case user_id when #curA
then #curRow := #curRow + 1
else #curRow := 1 and #curA := user_id end
) as rn
from ypur_table_name t,
(select #curRow := 0, #curA := '') r
order by user_id, `date` desc
)t1
where t1.rn in (1, 2); -- or change t1.rn <= 2. Change 2 accordingly
You can use below query-
SELECT x.*
FROM (SELECT t.*,
CASE
WHEN #category != t.user_id THEN #rownum := 1
ELSE #rownum := #rownum + 1
END AS rank,
#category := t.user_id AS var_category
FROM your_table AS t
JOIN (SELECT #rownum := NULL, #category := '') r
ORDER BY t.user_id,t.date DESC,t.id) x
WHERE x.rank<=2;
Note: x.rank<=2, put here how many rows you need user wise.

To display top 4 rows using mysql Rank is displaying wrong

I need to display the top4 and lease 4 rows based Amount and group by agentId but here rank is showing wrong
And how to show least(last 4 rows?)
schema:
AgentID amount
1 3000
1 3200
2 9000
SELECT Agentid,SUM(AmountRecevied) as Amount,#rownum := #rownum + 1 AS Rank
FROM collection ,(SELECT #rownum := 0) r
GROUP BY AgentID
ORDER BY Amount DESC
limit 4;
Try this way:
SELECT T.Agentid,T.Amount, #rownum := #rownum - 1 AS Rank
FROM
(SELECT Agentid,SUM(AmountRecevied) as Amount
FROM collection
GROUP BY AgentID
ORDER BY Amount
LIMIT 4) T,(SELECT #rownum := 11) r
Try this :
SELECT
C.*,
#rownum := #rownum + 1 AS Rank
FROM (
SELECT
Agentid,
SUM(AmountRecevied) as Amount
FROM collection
GROUP BY AgentID
ORDER BY Amount DESC
LIMIT 4
) AS C, (SELECT #rownum := 0) r
In case of amount matching for different agentids, then, I believe, ranks should be assigned same.
This solution should help you:
select
/*case when rank>6 then '' else rank end as */
rank, agentid, amount
from (
select agentid, #ca:=amount amount
, case when #pa=#ca then #rn:=#rn
else #rn:=( #rn + 1 )
end as rank
, #pa:=#ca as temp_currAmount
from ( select agentid, sum(amount) as amount
from agents
group by agentid
order by amount
) amounts_summary,
(select #pa:=0, #c0:=0,
#rn:=0) row_nums
order by rank desc
) results
where rank > 6
order by rank
;
Demo # MySQL 5.6.6 Fiddle
And if you want no display ranks greater than '6' but empty, then
just uncomment the case line and comment the where condition line
select
case when rank>6 then '' else rank end as
rank, agentid, amount
from (
select agentid, #ca:=amount amount
, case when #pa=#ca then #rn:=#rn
else #rn:=( #rn + 1 )
end as rank
, #pa:=#ca as temp_currAmount
from ( select agentid, sum(amount) as amount
from agents
group by agentid
order by amount
) amounts_summary,
(select #pa:=0, #ca:=0,
#rn:=0) row_nums
order by rank
) results
-- where rank > 6
order by rank
;
You can modify asc or desc as required.
Demo # MySQL 5.6.6 Fiddle