How to get latest records based on Batch ID - sql

declare #tab table
(
BatchID INT,
Code VARCHAR(20),
CommType INT,
LastStatus VARCHAR(5),
SourceModiifedLastDate varchar(30)
)
INSERT INTO #tab(BatchID, Code, CommType, LastStatus, SourceModiifedLastDate)
VALUES (1, 'A003-3', 3, 'I', '2013-06-17 21:28:01.827'),
(2, 'A004-1', 1, 'I', '2014-06-17 21:28:01.827'),
(6, 'A003-3', 3, 'U', '2015-06-17 21:28:01.827'),
(9, 'A003-3', 3, 'D', '2015-06-17 21:28:01.827'),
(11, 'A004-1', 3, 'D', '2013-06-17 21:28:01.827'),
(12, 'A004-1', 1, 'I', '2015-06-17 21:28:01.827'),
(16, 'A005-3', 3, 'I', '2011-06-17 21:28:01.827'),
(19, 'A005-3', 3, 'D', '2013-0617 21:28:01.827'),
(20, 'A006-3', 3, 'U', '2011-06-17 21:28:01.827'),
(21, 'A006-3', 3, 'I', '2013-0617 21:28:01.827')
Select * from #tab
Here in my sample data I need to get only Laststatus = 'D' records based on latest BatchID.
For example if you see Code = 'A003-3' it got inserted, updated and deleted I need to get this record
If you see code = 'A004-1' it got inserted, deleted and inserted I don't need this record.
Output should be :
BatchID Code CommType LastStatus SourceModiifedLastDate
---------------------------------------------------------------
9 A003-3 3 D 2015-06-17 21:28:01.827
19 A005-3 3 D 2013-06-17 21:28:01.827
I need to get only latest deleted records based on latest BatchID and latest date.
I have tried using MAX condition and GROUP BY to filter records but I'm unable to get what I'm looking for.
Please help me find a solution

select tt.*
from (select t.*
, row_number() over (partition by Code order by BatchId desc) as rn
from #tab t
) tt
where tt.rn = 1
and tt.LastStatus = 'D';

Here is an option using a CTE and filtering for only those BatchIds that have the last action as a delete
;
WITH CTE1 AS (
SELECT *, RANK() OVER (partition by Code ORDER BY BatchID DESC ) [CodeRank]
FROM #tab)
SELECT *
FROM CTE1
WHERE CodeRank = 1 and LastStatus = 'D'

SELECT tab1.BatchId, tab1.Code, tab1.CommType, tab1.LastStatus, tab1.SourceModiifedLastDate
FROM #tab tab1
INNER JOIN (
SELECT Code, MAX(SourceModiifedLastDate) MaxSourceModiifedLastDate
FROM #tab
GROUP BY Code) tab2
ON tab2.Code = tab1.Code
AND tab2.MaxSourceModiifedLastDate = tab1.SourceModiifedLastDate
WHERE tab1.LastStatus = 'D'

A typical way of doing tis uses row_number():
select t.*
from (select t.*,
row_number() over (partition by Code order by BatchId desc) as seqnum
from #tab t
) t
where seqnum = 1;
The old-fashioned way of doing it (before window functions) might look like this:
select t.*
from #tab t
where t.BatchId = (select max(t2.BatchId)
from #tab t2
where t2.Code = t.Code
);

Related

Select the first record of the last group when there are repeating groups

Trying to select the first record of the latest repeating STATUS groups for each POLICY_ID. How can I do this?
Edit/Note: There can be more than two status repetitions as shown in the last three rows.
View of the data:
Desired output:
SQL for data:
--drop table mytable;
create table mytable (ROW_ID Number(5), POLICY_ID Number(5),
CHANGE_NO Number(5), STATUS VARCHAR(50), CHANGE_DATE DATE);
insert into mytable values ( 81, 1, 1, 'A', date '2018-01-01');
insert into mytable values ( 95, 1, 2, 'A', date '2018-01-02');
insert into mytable values ( 100, 1, 3, 'B', date '2018-01-03');
insert into mytable values ( 150, 1, 4, 'C', date '2018-01-04');
insert into mytable values ( 165, 1, 5, 'A', date '2018-01-05');
insert into mytable values ( 175, 1, 6, 'A', date '2018-01-06');
insert into mytable values ( 599, 2, 1, 'S', date '2018-01-11');
insert into mytable values ( 602, 2, 2, 'S', date '2018-01-12');
insert into mytable values ( 611, 2, 3, 'S', date '2018-01-13');
insert into mytable values ( 629, 2, 4, 'T', date '2018-01-14');
insert into mytable values ( 720, 2, 5, 'U', date '2018-01-15');
insert into mytable values ( 790, 2, 6, 'S', date '2018-01-16');
insert into mytable values ( 812, 2, 7, 'S', date '2018-01-17');
insert into mytable values ( 825, 2, 8, 'S', date '2018-01-18');
select * from mytable;
Hmmm . . .
select t.*
from (select t.*,
row_number() over (partition by policy_id order by change_date asc) as seqnum
from t
where not exists (select 1
from t t2
where t2.policy_id = t.policy_id and
t2.status <> t.status and
t2.change_date > t.change_date
)
) t
where seqnum = 1;
The inner subquery finds all rows where -- for a given policy number -- there is no later row with a different status. That defines the last group of records.
It then uses row_number() to enumerate the rows. These outer query selects the first row for each policy_number.
You can use LEAD and LAG functions to identify the rows that begin a "repetition". The condition status <> previous status and status = next status will identify such rows.
SELECT *
FROM (
SELECT cte1.*, ROW_NUMBER() OVER (PARTITION BY POLICY_ID ORDER BY CHANGE_DATE DESC) AS rn
FROM (
SELECT mytable.*, CASE WHEN
STATUS <> LAG(STATUS, 1, '!') OVER (PARTITION BY POLICY_ID ORDER BY CHANGE_DATE) AND
STATUS = LEAD(STATUS) OVER (PARTITION BY POLICY_ID ORDER BY CHANGE_DATE)
THEN 1 END AS toselect
FROM mytable
) cte1
WHERE toselect = 1
) cte2
WHERE rn = 1
If you are using Oracle 12c you could use MATCH_RECOGNIZE:
SELECT ROW_ID, POLICY_ID, CHANGE_NO, STATUS, CHANGE_DATE
FROM mytable
MATCH_RECOGNIZE (
PARTITION BY POLICY_ID
ORDER BY CHANGE_DATE
MEASURES MATCH_NUMBER() m,FIRST(R.ROW_ID) r
ALL ROWS PER MATCH
PATTERN (R+)
DEFINE R AS STATUS=NEXT(STATUS)
) MR
WHERE ROW_ID = R
ORDER BY ROW_NUMBER() OVER(PARTITION BY POLICY_ID ORDER BY M DESC)
FETCH FIRST 1 ROW WITH TIES;
db<>fiddle demo
Alternatively:
SELECT *
FROM mytable
MATCH_RECOGNIZE (
PARTITION BY POLICY_ID
ORDER BY CHANGE_DATE DESC
MEASURES MATCH_NUMBER() m
,LAST(R.ROW_ID) ROW_ID
,LAST(R.STATUS) STATUS
,LAST(R.CHANGE_NO) CHANGE_NO
,LAST(R.CHANGE_DATE) CHANGE_DATE
ONE ROW PER MATCH
PATTERN (R+)
DEFINE R AS STATUS=PREV(STATUS)
) MR
WHERE M = 1
db<>fiddle demo2
Another approach with match_recognize:
select row_id, policy_id, change_no, status, change_date
from mytable
match_recognize (
partition by policy_id
order by change_date
measures
strt.row_id as row_id
, strt.change_no as change_no
, strt.change_date as change_date
, strt.status as status
pattern (strt unchanged* final)
define
unchanged as next(unchanged.status) = prev(unchanged.status)
, final as next(final.status) is null
) mr
order by mr.policy_id;

Selecting minimal dates, or nulls in SQL

This is grossly oversimplified, but:
I have a table, something like the following:
CREATE TABLE Table1
([ID] int, [USER] varchar(5), [DATE] date)
;
INSERT INTO Table1
([ID], [USER], [DATE])
VALUES
(1, 'A', '2018-10-01'),
(2, 'A', '2018-09-01'),
(3, 'A', NULL),
(4, 'B', '2018-05-03'),
(5, 'B', '2017-04-01'),
(6, 'C', NULL)
;
And for each user, I wish to retrieve the whole row of details where the DATE variable is minimal.
SELECT T.USER FROM TABLE1 T
WHERE T.DATE = (SELECT MIN(DATE) FROM TABLE1 T1 WHERE T1.USER = T.USER)
Works great, however in the instance there is no row with a populated DATE field, there will be a row with a NULL, like the final row of my table above, which I also wish to select.
So my ideal output in this case is:
(2, 'A', '2018-09-01'),
(5, 'B', '2017-04-01'),
(6, 'C', NULL)
SQL fiddle: http://www.sqlfiddle.com/#!9/df42b5/6
I think something could be done using an EXCLUDE statement but it gets complex very quickly.
You may try with row_number()
demo
select * from
(select *, row_number() over(partition by [user] order by [user],case when
[date] is null then 0 else 1 end desc,[date]) as rn
from Table1)x where rn=1
use union and and co-related sub-query with min() function
CREATE TABLE Table1 (ID int, usr varchar(50), DATE1 date)
;
INSERT INTO Table1 VALUES
(1, 'A', '2018-10-01'),
(2, 'A', '2018-09-01'),
(3, 'A', NULL),
(4, 'B', '2018-05-03'),
(5, 'B', '2017-04-01'),
(6, 'C', NULL)
;
select * from Table1 t where
DATE1= (select min(date1) from Table1 t1 where t1.usr=t.usr
) and date1 is not null
union
select * from Table1 t where date1 is null
and t.usr not in ( select usr from Table1 where date1 is not null)
DEMO
ID usr DATE1
2 A 01/09/2018 00:00:00
5 B 01/04/2017 00:00:00
6 C
You can use GROUP BY and JOIN to output the desired results.
select t.Id
, x.[User]
, x.[MinDate] as [Date]
from
(select [User]
, min([Date]) as MinDate
from table1
group by [User]) x
inner join table1 t on t.[User] = x.[User] and (t.[Date] = x.[MinDate] or x.[MinDate] is null)
You can use a Common Table Expression:
;WITH chronology AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY [USER]
ORDER BY ISNULL([DATE], '2900-01-01') ASC
) Idx
FROM TABLE1
)
SELECT ID, [USER], [DATE]
FROM chronology
WHERE Idx=1;
Using a CTE in this solution simplifies the query improving its readability, maintainability and extensibility. Furthermore, I expect this approach to be optimal in terms of performance.

Looking for a solution to retrieve either 1 or max of the value from a table in SQL

Table A
Owner row_no category
A 1 U
B 1 T
B 2 T
C 1 U
C 2 T
C 3 U
C 4 U
I'm looking for a solution that stores values into other table which should retrieve
row_no as 1 if the value is 1 and should return max(row_no)-1 if
the value isn't 1.
category should be either T or U or both based on whether an owner
has opted for only T or U or both in TABLE A.
Resultant table should be something like below.
Table B
Owner row_no category
A 1 U
B 1 T
C 3 Both
I tried using the below approach which turns out to be an error.
SELECT * INTO B FROM A
WHERE
ROW_NO LIKE CASE
WHEN ROW_NO=1 then ROW_NO
ELSE max(ROW_NO)-1
END
Haven't figured out yet on retrieving the category!
Could you please help with right approach ?!
NEW EDIT
I think you can do like this:
declare #table table (owner nvarchar(50),row_no int)
insert into #table
values
('A', 1),
('B', 1),
('B', 2),
('C', 1),
('C', 2),
('C', 3),
('C', 4)
select owner,row_no from (
select *, ROW_NUMBER() over(partition by owner order by la desc) as rn from (
select *,LEAD(row_no,1,1) over(partition by owner order by row_no) as la from #table
)X
)z where rn = 1
EDIT UPDATE
With categoryList you can do like this
declare #table table (owner nvarchar(50),row_no int,category nvarchar(50))
insert into #table
values
('A', 1,'U'),
('B', 1,'T'),
('B', 2,'T'),
('C', 1,'U'),
('C', 2,'T'),
('C', 3,'U'),
('C', 4,'U')
;
with category as (
select owner, categoryList = stuff((select N', ' + Category
from (Select distinct owner,category from #table t2
) z
where z.owner = t1.owner
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 2, N'')
from #table t1
group by owner
)
select z.owner,row_no,y.categoryList from (
select *, ROW_NUMBER() over(partition by owner order by la desc) as rn from (
select *,LEAD(row_no,1,1) over(partition by owner order by row_no) as la from #table
)X
)z
inner join category y on z.owner = y.owner
where rn = 1
DBFiddeldemo
You could use:
WITH cte AS(
SELECT *, MAX(row_no)OVER(PARTITION BY owner) AS m
FROM tab
)
SELECT owner, row_no
INTO tab2
FROM cte
WHERE row_no = m-1 OR m=1;
DBFiddle Demo
Warning! I've made an assumption that values in row_no are consecutive.
Without cte/subquery:
SELECT TOP(1) WITH TIES *
INTO tabB
FROM tab
ORDER BY IIF(MAX(row_no)OVER(PARTITION BY owner) IN (row_no+1,1),0,1)
DBFiddle Demo2

How to tag a group of repeating items if the ids are consecutive for n rows?

Building in the test for consecutive ids is proving difficult without breaking it down into parts or using a cursor which I'd like to avoid.
pseudo query -
SELECT all
FROM table with the same description on multiple adjacent rows for >= 4 rows
and set tag = 'y' and order by id
(id,description, tag),
(1, 'xxx', 'n'),
(2, 'xxx', 'n'),
(3, 'xxx', 'n'),
(7, 'xxx', 'n'),
(5, 'xxx', 'n'),
(8, 'xxx', 'n'),
(4, 'xxx', 'n'),
(6, 'zzz', 'n')
desired result
(1, 'xxx', 'y')
(2, 'xxx', 'y')
(3, 'xxx', 'y')
(4, 'xxx', 'y')
(5, 'xxx', 'y')
This is called as gaps and island problem. Something like this should work
;with cte as
(SELECT id,
description,
tag = 'y' ,
cnt = Count(*)over(partition by description, grp)
FROM (SELECT *,
grp = Sum(CASE WHEN prev_description = description THEN 0 ELSE 1 END)Over(Order by id)
FROM (SELECT *,
prev_description = Lag(description) OVER(ORDER BY id)
FROM Yourtable) a) b
GROUP BY id, description, grp
)
Select * from cte
Where cnt >= 4
Another approach using Row_Number
;with cte as
(SELECT id,
description,
tag = 'y' ,
cnt = Count(*)over(partition by description, grp)
FROM (select Grp = row_number()over(order by id) -
row_number()over(partition by description order by id), *
from Yourtable) b
GROUP BY id, description, grp)
Select * from cte
Where cnt >= 4
I think this will do it
select *, 'y' as 'newTag'
from ( select *
, count(*) over (partition by [description], grp) as 'grpSize'
from ( select *
, ( [id] - row_number() over (partition by [description] order by [id]) ) as grp
from [consecutive]
) tt
) ttt
where grpSize >= 4
order by [description], grp, [id]

how to use SQL group to filter rows with maximum date value

I have the following table
CREATE TABLE Test
(`Id` int, `value` varchar(20), `adate` varchar(20))
;
INSERT INTO Test
(`Id`, `value`, `adate`)
VALUES
(1, 100, '2014-01-01'),
(1, 200, '2014-01-02'),
(1, 300, '2014-01-03'),
(2, 200, '2014-01-01'),
(2, 400, '2014-01-02'),
(2, 30 , '2014-01-04'),
(3, 800, '2014-01-01'),
(3, 300, '2014-01-02'),
(3, 60 , '2014-01-04')
;
I want to achieve the result which selects only Id having max value of date. ie
Id ,value ,adate
1, 300,'2014-01-03'
2, 30 ,'2014-01-04'
3, 60 ,'2014-01-04'
how can I achieve this using group by? I have done as follows but it is not working.
Select Id,value,adate
from Test
group by Id,value,adate
having adate = MAX(adate)
Can someone help with the query?
Select the maximum dates for each id.
select id, max(adate) max_date
from test
group by id
Join on that to get the rest of the columns.
select t1.*
from test t1
inner join (select id, max(adate) max_date
from test
group by id) t2
on t1.id = t2.id and t1.adate = t2.max_date;
Please try:
select
*
from
tbl a
where
a.adate=(select MAX(adate) from tbl b where b.Id=a.Id)
If you are using a DBMS that has analytical functions you can use ROW_NUMBER:
SELECT Id, Value, ADate
FROM ( SELECT ID,
Value,
ADate,
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Adate DESC) AS RowNum
FROM Test
) AS T
WHERE RowNum = 1;
Otherwise you will need to use a join to the aggregated max date by Id to filter the results from Test to only those where the date matches the maximum date for that Id
SELECT Test.Id, Test.Value, Test.ADate
FROM Test
INNER JOIN
( SELECT ID, MAX(ADate) AS ADate
FROM Test
GROUP BY ID
) AS MaxT
ON MaxT.ID = Test.ID
AND MaxT.ADate = Test.ADate;
I would try something like this
Select t1.Id, t1.value, t1.adate
from Test as t1
where t1.adate = (select max(t2.adate)
from Test as t2
where t2.id = t1.id)