SQL get the last time number was set to 1 - sql

I have a sql table similar to the below. I would like to get the last time it was changed from 0 to 1 and the last time it changed back to 0 (highlighted below for an example id).
What I have tried is:
select * from Table t1 join Table t2 on t1.id = t2.id join Table t3 on t1.id = t3.id
where t1.flag = 1 and t2.flag = 0 and t3.flag
group by t1.id
having min(t1.createdtime) between max(t2.createdtime) and min(t3.createdtime)

For this dataset, you could use lag() to bring in the flag of the previous row, use it as a filter condition, and then aggregate:
select
id,
max(createdtime) createdtime,
flag
from (
select
t.*,
lag(flag) over(partition by id order by createdtime) lagflag
from mytable t
) t
where (flag = 0 and lagflag = 1) or (flag = 1 and lagflag = 0)
group by id, flag

You can use lag() to get the recent flag. So you can filter for changes, i.e. when the (current) flag and the recent flag relate as desired. To get only the most recent of the changes you can filter by row_number().
SELECT z.id,
z.createdtime,
z.flag
FROM (SELECT y.id,
y.createdtime,
y.flag FROM (SELECT x.id,
x.createdtime,
x.flag,
row_number() OVER (PARTITION BY x.id
ORDER BY x.createdtime DESC) rn
FROM (SELECT t.id,
t.createdtime,
t.flag,
lag(t.flag) OVER (PARTITION BY t.id
ORDER BY createdtime) recentflag
FROM elbat t) x
WHERE x.flag = 0
AND x.recentflag = 1) y
WHERE y.rn = 1
UNION ALL
SELECT y.id,
y.createdtime,
y.flag FROM (SELECT x.id,
x.createdtime,
x.flag,
row_number() OVER (PARTITION BY x.id
ORDER BY x.createdtime DESC) rn
FROM (SELECT t.id,
t.createdtime,
t.flag,
lag(t.flag) OVER (PARTITION BY t.id
ORDER BY createdtime) recentflag
FROM elbat t) x
WHERE x.flag = 1
AND x.recentflag = 0) y
WHERE y.rn = 1) z
ORDER BY z.createdtime DESC;

Use lag() in a CTE to get the previous flag for each row and then NOT EXISTS:
with cte as(
select *, lag(flag) over(partition by id order by createdtime) prevflag
from tablename
)
select c.id, c.createdtime, c.flag
from cte c
where c.flag <> c.prevflag
and not exists (
select 1 from cte
where id = c.id and flag = c.flag and prevflag = c.prevflag and createdtime > c.createdtime
)
order by c.createdtime
Or:
with cte as(
select *, lag(flag) over(partition by id order by createdtime) prevflag
from tablename
)
select id, max(createdtime) createdtime, flag
from cte
where flag <> prevflag
group by id, flag
order by createdtime
See the demo.
Results:
> id | createdtime | flag
> -: | :------------------ | ---:
> 5 | 2019-11-02 14:30:00 | 1
> 5 | 2020-08-01 14:30:00 | 0

Related

How to select the first date and last date of each block in SQL

For the table:
date
phase
user_id
1.1.20
a
10
2.1.20
a
10
3.1.20
b
10
4.1.20
a
10
5.1.20
a
10
6.1.20
b
10
I need to return for each user_id the start date and end date for each phase when a contains all the phases in the middle and b is the end phase, without using window functions.
Should look like this:
user_id
start_date
end_date
10
1.1.20
3.1.20
10
4.1.20
6.1.20
Without using window functions.
You can do a self-join to the table.
Then calculate a rank/group for the phases.
Then it's simple to group by the user and the phase group.
select user_id
, min([date]) as start_date
, max([date]) as end_date
from
(
select t1.user_id, t1.[date], t1.phase
, count(t2.phase) as grp
from your_table t1
left join your_table t2
on t2.user_id = t1.user_id
and t2.phase = 'b'
and t2.[date] >= t1.[date]
group by t1.user_id, t1.[date], t1.phase
) q
group by user_id, grp
order by user_id, start_date
user_id
start_date
end_date
10
2020-01-01
2020-01-03
10
2020-01-04
2020-01-06
Test on db<>fiddle here
with cte1 as(
select user_id,date,
case
when phase = 'a' and (lag(phase)over(order by date) is null or lag(phase)over(order by date) = 'b')
then 1
else 0
end grp
from tb),
cte2 as(
select user_id,date, sum(grp)over(order by date) as rnk
from cte1)
select user_id,min(date)start_date,max(date)end_date
from cte2
group by user_id,rnk
EDIT: without window functions
with cte1 as(
select *,
case
when phase = 'b' then 'end'
when phase = 'a' and
(select top 1 phase from tb t2 where t2.date < t1.date order by date desc) = 'a'
then 'no'
else 'start' end grp
from tb t1),
cte2 as(
select *,
(select top 1 date from cte1 t2 where t2.phase = 'a' and t2.date = t1.date)start_date,
(select top 1 date from cte1 t2 where t2.phase = 'b' and t2.date = t1.date)end_date
from cte1 t1
where grp = 'start' or grp = 'end'),
cte3 as(
select *
from cte2
where start_date is not null
),
cte4 as(
select *
from cte2
where end_date is not null
)
select t1.user_id,t1.start_date,t2.end_date
from cte3 t1
cross apply
(select top 1 *
from cte4 t2
where t1.date < t2.date) t2
result

TSQL getting max and min date with a seperate but not unique record

example table:
test_date | test_result | unique_ID
12/25/15 | 100 | 50
12/01/15 | 150 | 75
10/01/15 | 135 | 75
09/22/14 | 99 | 50
04/10/13 | 125 | 50
I need to find the first and last test date as well as the test result to match said date by user. So, I can group by ID, but not test result.
SELECT MAX(test_date)[need matching test_result],
MIN(test_date) [need matching test_result],
unique_id
from [table]
group by unique_id
THANKS!
Create TABLE #t
(
test_date date ,
Test_results int,
Unique_id int
)
INSERT INTO #t
VALUES ( '12/25/15',100,50 ),
( '12/01/15',150,75 ),
( '10/01/15',135,75 ),
( '09/22/14',99,50 ),
( '04/10/13',125,50 )
select 'MinTestDate' as Type, a.test_date, a.Test_results, a.Unique_id
from #t a inner join (
select min(test_date) as test_datemin, max(test_date) as test_datemax, unique_id from #t
group by unique_ID) b
on a.test_date = b.test_datemin
union all
select 'MaxTestDate' as Type, a.test_date, a.Test_results, a.Unique_id from #t a
inner join (
select min(test_date) as test_datemin, max(test_date) as test_datemax, unique_id from #t
group by unique_ID) b
on a.test_date = b.test_datemax
I would recommend window functions. The following returns the information on 2 rows per id:
select t.*
from (select t.*,
row_number() over (partition by unique_id order by test_date) as seqnum_asc,
row_number() over (partition by unique_id order by test_date desc) as seqnum_desc
from table t
) t;
For one row, use conditional aggregation (or pivot if you prefer):
select unique_id,
min(test_date), max(case when seqnum_asc = 1 then test_result end),
max(test_date), max(case when seqnum_desc = 1 then test_result end)
from (select t.*,
row_number() over (partition by unique_id order by test_date) as seqnum_asc,
row_number() over (partition by unique_id order by test_date desc) as seqnum_desc
from table t
) t
group by unique_id;
Consider using a combination of self-joins and derived tables:
SELECT t1.unique_id, minTable.MinOftest_date, t1.test_result As Mintestdate_result,
maxTable.MaxOftest_date, t2.test_result As Maxtestdate_result
FROM TestTable AS t1
INNER JOIN
(
SELECT Min(TestTable.test_date) AS MinOftest_date,
TestTable.unique_ID
FROM TestTable
GROUP BY TestTable.unique_ID
) As minTable
ON (t1.test_date = minTable.MinOftest_date
AND t1.unique_id = minTable.unique_id)
INNER JOIN TestTable As t2
INNER JOIN
(
SELECT Max(TestTable.test_date) AS MaxOftest_date,
TestTable.unique_ID
FROM TestTable
GROUP BY TestTable.unique_ID
) AS maxTable
ON t2.test_date = maxTable.MaxOftest_date
AND t2.unique_ID = maxTable.unique_ID
ON minTable.unique_id = maxTable.unique_id;
OUTPUT
unique_id MinOftest_date Mintestdate_result MaxOftest_date Maxtestdate_result
50 4/10/2013 125 12/25/2015 100
75 10/1/2015 135 12/1/2015 150

sql server merge rows and get latest value

I have a table like this,
Id A B C D touchedwhen
1 NULL yes NULL yes 2015-02-26 14:10:01.870
1 NULL NULL no no 2015-02-26 14:10:40.370
and need to merge them in to one row like this,
Id A B C D touchedwhen
1 NULL yes no no 2015-02-26 14:10:40.370.
Note : if value is present in both rows take the latest one by date..
Tried this query:
select id,
max(a),
max(b),
max(c),
max(d), -- data in both rows hence take the latest
max(touchedwhen)
from
[dbo].[Table_1]
group by id;
If you were just looking for the last value per id, you could use:
last_value(a) over(
partition by id
order by touchedwhen desc
rows between unbounded preceding and unbounded following) as a
But you're looking for the last value per id that is not null. The only thing I can come up with is a four-part join, with each subquery calculating the latest non-null value for a, b, c, d:
select ids.id
, a.a
, b.b
, c.c
, d.d
, ids.tw
from (
select id
, max(touchedwhen) as tw
from YourTable
group by
id
) ids
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, a
, id
from YourTable
where a is not null
) a
on a.id = ids.id
and a.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, b
, id
from YourTable
where b is not null
) b
on b.id = ids.id
and b.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, c
, id
from YourTable
where c is not null
) c
on c.id = ids.id
and c.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, d
, id
from YourTable
where d is not null
) d
on d.id = ids.id
and d.rn = 1
-- Get all latest values in one row for each primary key
WITH CTE(row_num, Id, A, B, C, D, touchedwhen) AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Id ORDER BY touchedwhen DESC), Id, A, B, C, D FROM Table_1)
UPDATE CTE SET
A = (SELECT TOP 1 t.A FROM Table_1 t WHERE t.A IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
B = (SELECT TOP 1 t.B FROM Table_1 t WHERE t.B IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
C = (SELECT TOP 1 t.C FROM Table_1 t WHERE t.C IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
D = (SELECT TOP 1 t.D FROM Table_1 t WHERE t.D IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC)
WHERE row_num = 1
-- Delete extra rows per primary key after copying latest values to one row
WITH CTE(row_num) AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Id ORDER BY touchedwhen DESC) FROM Table_1)
DELETE FROM CTE WHERE row_num > 1

Query for Retrieving

I need to build a query which should give me below mentioned output.
My table structure Required output
Depth | Name Level A | Level B | Level C
1 |A A | B | C
2 |B A1 | B1 | C1
3 |C A1 | B1 | C2
1 |A1 A1 | B2 | C3
2 |B1 A1 | B2 | C4
3 |C1
3 |C2
2 |B2
3 |C3
3 |C4
Thanks in advance
Given your particular data, you can come close with:
select a.name as levela, b.name as levelb, c.name as levelc
from (select name, row_number() over (order by id) as seqnum from table where depth = 1
) a full outer join
(select name, row_number() over (order by id) as seqnum from table where depth = 2
) b full outer join
on b.seqnum = a.seqnum
(select name, row_number() over (order by id) as seqnum from table where depth = 3
) c
on c.seqnum = coalesce(a.seqnum, b.seqnum);
This inserts NULLs instead of repeating the final values for the three columns. If you want the final values, this should work:
select coalesce(a.name, maxes.a) as levela,
coalesce(b.name, maxes.b) as levelb,
coalesce(c.name, maxes.c) as levelc
from (select name, row_number() over (order by id) as seqnum from table where depth = 1
) a full outer join
(select name, row_number() over (order by id) as seqnum from table where depth = 2
) b full outer join
on b.seqnum = a.seqnum
(select name, row_number() over (order by id) as seqnum from table where depth = 3
) c
on c.seqnum = coalesce(a.seqnum, b.seqnum) cross join
(select max(case when depth = 1 and id = maxid then name end) as max_a,
max(case when depth = 2 and id = maxid then name end) as max_b,
max(case when depth = 3 and id = maxid then name end) as max_c
from (select t.*,
max(id) over (partition by depth) as maxid
from t
) t
) maxes
Since the relationship between levelA,levelB,levelC is not clear.I have assumed you want to return the max(name) in case the corresponding value is not available.The Sql Fiddle here.You can replace
order by my_table.name into order by unique_seq_column and also max(name) name by the name value in max(unique_seq_column) if it suits your requirement
--Get the max count and max name for each level
with cnt_max1 as (select max(name) name ,count(1) cnt from my_table where depth=1)
,cnt_max2 as (select max(name) name ,count(1) cnt from my_table where depth=2)
,cnt_max3 as (select max(name) name ,count(1) cnt from my_table where depth=3)
--find out the total rows required
,greatest_cnt as (select greatest(cnt_max1.cnt,cnt_max2.cnt,cnt_max3.cnt) cnt from cnt_max1,cnt_max2,cnt_max3)
--Establish relationship between levelA,levelB,levelC using sublevel column
,level_A as (select * from (select rownum sublevel, my_table.name as levela from my_table where depth=1 order by my_table.name)
union
select level+cnt_max1.cnt sublevel,cnt_max1.name levela
from cnt_max1,greatest_cnt connect by level <=(greatest_cnt.cnt - cnt_max1.cnt))
,level_B as (select * from (select rownum sublevel, my_table.name as levelb from my_table where depth=2 order by my_table.name)
union
select level+cnt_max2.cnt sublevel,cnt_max2.name levelb
from cnt_max2,greatest_cnt connect by level <=(greatest_cnt.cnt - cnt_max2.cnt))
,level_C as (select * from (select rownum sublevel, my_table.name as levelc from my_table where depth=3 order by my_table.name)
union
select level+cnt_max3.cnt sublevel,cnt_max3.name levelc
from cnt_max3,greatest_cnt connect by level <=(greatest_cnt.cnt - cnt_max3.cnt))
--Display the data
select levela,levelb,levelc
from level_A join level_b
on level_A.sublevel=level_B.sublevel
join level_c on level_C.sublevel=level_b.sublevel

Any other alternative to write this SQL query

I need to select data base upon three conditions
Find the latest date (StorageDate Column) from the table for each record
See if there is more then one entry for date (StorageDate Column) found in first step for same ID (ID Column)
and then see if DuplicateID is = 2
So if table has following data:
ID |StorageDate | DuplicateTypeID
1 |2014-10-22 | 1
1 |2014-10-22 | 2
1 |2014-10-18 | 1
2 |2014-10-12 | 1
3 |2014-10-11 | 1
4 |2014-09-02 | 1
4 |2014-09-02 | 2
Then I should get following results
ID
1
4
I have written following query but it is really slow, I was wondering if anyone has better way to write it.
SELECT DISTINCT(TD.RecordID)
FROM dbo.MyTable TD
JOIN (
SELECT T1.RecordID, T2.MaxDate,COUNT(*) AS RecordCount
FROM MyTable T1 WITH (nolock)
JOIN (
SELECT RecordID, MAX(StorageDate) AS MaxDate
FROM MyTable WITH (nolock)
GROUP BY RecordID)T2
ON T1.RecordID = T2.RecordID AND T1.StorageDate = T2.MaxDate
GROUP BY T1.RecordID, T2.MaxDate
HAVING COUNT(*) > 1
)PT ON TD.RecordID = PT.RecordID AND TD.StorageDate = PT.MaxDate
WHERE TD.DuplicateTypeID = 2
Try this and see how the performance goes:
;WITH
tmp AS
(
SELECT *,
RANK() OVER (PARTITION BY ID ORDER BY StorageDate DESC) AS StorageDateRank,
COUNT(ID) OVER (PARTITION BY ID, StorageDate) AS StorageDateCount
FROM MyTable
)
SELECT DISTINCT ID
FROM tmp
WHERE StorageDateRank = 1 -- latest date for each ID
AND StorageDateCount > 1 -- more than 1 entry for date
AND DuplicateTypeID = 2 -- DuplicateTypeID = 2
You can use analytic function rank , can you try this query ?
Select recordId from
(
select *, rank() over ( partition by recordId order by [StorageDate] desc) as rn
from mytable
) T
where rn =1
group by recordId
having count(*) >1
and sum( case when duplicatetypeid =2 then 1 else 0 end) >=1