Search sequence pattern in SQL Server - sql

I have records like this and I want to search in SQL Server by pattern and sequence like (50,54,50) in value field it should return 02,03,04 any one have idea to do this.
======================================
Id Date Value
01 2020-01-01 50
02 2020-01-02 50
03 2020-01-03 54
04 2020-01-04 50
05 2020-01-05 35
06 2020-01-06 98
07 2020-01-07 13
======================================

There is a request on the user voice site Add support for Row Pattern Recognition in T-SQL (SQL:2016 features R010 and R020) which I believe would allow for this.
In the meantime this should do what you need
WITH T AS
(
SELECT *,
LAG(Id) OVER (ORDER BY Id) AS PrevId,
LAG(value) OVER (ORDER BY Id) AS PrevValue,
LEAD(Id) OVER (ORDER BY Id) AS NextId,
LEAD(value) OVER (ORDER BY Id) AS NextValue
FROM YourTable
)
SELECT PrevId, Id, NextId
FROM T
WHERE PrevValue = 50 AND Value =54 AND NextValue = 50

If you wanted a more flexible approach, you can use cross apply:
select t2.*
from t cross apply
(select string_agg(id, ',') within group (order by date) as ids,
string_agg(value, ',') within group (order by date) as vals
from (select top (3) t2.*
from t t2
where t2.date >= t.date
order by t2.date
) t2
) t2
where vals = '50,54,50';
Here is a db<>fiddle.
If string_agg() were supported as a window function, you could use:
select t.*
from (select t.*,
string_agg(id, ',') within group (order by date) over (order by id rows between current row and 2 following) as ids,
string_agg(value, ',') within group (order by date) over (order by id rows between current row and 2 following) as vals
from t
) t
where vals = '50,54,50';
But alas, it is not.

If I get your requirement correct, yo can try this below logic developed with the help of LAG and LEAD-
DEMO HERE
WITH CTE
AS
(
SELECT Id,Date,
LAG(value,2) OVER(ORDER BY id) lag_2,
LAG(value,1) OVER(ORDER BY id) lag_1,
Value c_val,
LEAD(value,1) OVER(ORDER BY id) lead_1,
LEAD(value,2) OVER(ORDER BY id) lead_2
FROM your_table
)
SELECT Id,Date,
CASE
WHEN (lag_2 = 50 AND lag_1 = 54 AND c_val = 50) OR
(lag_1 = 50 AND c_val = 54 AND lead_1 = 50) OR
(c_val = 50 AND lead_1 = 54 AND lead_2 = 50)
THEN (
CASE
WHEN lead_1 = 54 THEN 02
WHEN c_val = 54 THEN 03
WHEN lag_1 = 54 THEN 04
END
)
ELSE c_val
END
FROM CTE

Related

Alphanumeric sequence in SQL Server

For a single id column, we have sequence number 01, 02, 03 upto 99 repeating twice /thrice.
Example:
ID SEQ_NO
----------
2 01
2 02
2 03
.
.
.
2 99
2 01
2 02
2 99
We have a requirement to add AA prefix to second time when it is looping on seq_no, and for third time it should be BB.
Can anyone explain how to do this?
Try the following using the ROW_NUMBER function:
If you want only to select SEQ_NO as a new column:
WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID, SEQ_NO ORDER BY SEQ_NO) rn
FROM table_name
)
SELECT ID, SEQ_NO,
CASE
WHEN rn>1 THEN
CONCAT(CHAR(rn+63), CHAR(rn+63), SEQ_NO)
ELSE SEQ_NO
END AS new_seq
FROM CTE
WHERE rn <= 27
ORDER BY ID, new_seq
If you want to update the SEQ_NO column:
WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID, SEQ_NO ORDER BY SEQ_NO) rn
FROM table_name
)
UPDATE CTE SET SEQ_NO = CONCAT(CHAR(rn+63), CHAR(rn+63), SEQ_NO)
WHERE rn > 1 AND rn <= 27
See a demo with a set of data where seq (01 - 10) is repeated three times.

How to Rank By Partition with island and gap issue

Is it possible to rank item by partition without use CTE method
Expected Table
item
value
ID
A
10
1
A
20
1
B
30
2
B
40
2
C
50
3
C
60
3
A
70
4
A
80
4
By giving id to the partition to allow agitated function to work the way I want.
item
MIN
MAX
ID
A
10
20
1
B
30
40
2
C
50
60
3
A
70
80
4
SQL Version: Microsoft SQL Sever 2017
Assuming that the value column provides the intended ordering of the records which we see in your question above, we can try using the difference in row numbers method here. Your problem is a type of gaps and islands problem.
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY value) rn1,
ROW_NUMBER() OVER (PARTITION BY item ORDER BY value) rn2
FROM yourTable
)
SELECT item, MIN(value) AS [MIN], MAX(value) AS [MAX], MIN(ID) AS ID
FROM cte
GROUP BY item, rn1 - rn2
ORDER BY MIN(value);
Demo
If you don't want to use a CTE here, for whatever reason, you may simply inline the SQL code in the CTE into the bottom query, as a subquery:
SELECT item, MIN(value) AS [MIN], MAX(value) AS [MAX], MIN(ID) AS ID
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY value) rn1,
ROW_NUMBER() OVER (PARTITION BY item ORDER BY value) rn2
FROM yourTable
) t
GROUP BY item, rn1 - rn2
ORDER BY MIN(value);
You can generate group IDs by analyzing the previous row item value that could be obtained with the LAG function and finally use GROUP BY to get the minimum and maximum value in item groups.
SELECT
item,
MIN(value) AS "min",
MAX(value) AS "max",
group_id + 1 AS id
FROM (
SELECT
*,
SUM(CASE WHEN item = prev_item THEN 0 ELSE 1 END) OVER (ORDER BY value) AS group_id
FROM (
SELECT
*,
LAG(item, 1, item) OVER (ORDER BY value) AS prev_item
FROM t
) items
) groups
GROUP BY item, group_id
Query produces output
item
min
max
id
A
10
20
1
B
30
40
2
C
50
60
3
A
70
80
4
You can check a working demo here

SQL query to find same ID but different names

I have a table like this:
ID | name
45 Alex
98 Diana
32 Peter
98 Daniel
45 Alex
23 Bob
98 Jake
I need to find all rows where is the same ID but different name.
You could use first/last value() window functions here:
with n as (
select *,
First_Value(name) over(partition by id order by (select null)) n1,
Last_Value(name) over(partition by id order by (select null)) n2
from t
)
select Id, Name
from n
where n1 != n2
You can use exists:
select t.*
from mytable t
where exists (select 1
from mytable t2
where t2.id = t.id and t2.name <> t.name
);
A windowed count is usually the most efficient:
SELECT
t.ID,
t.name
FROM (
SELECT *,
c = COUNT() OVER (PARTITION BY t.name)
FROM YourTable t
) t
WHERE c > 1;
SELECT
ID,
name,
CASE WHEN COUNT(ID) OVER(PARTITION BY ID) = 1 THEN 'OK' ELSE 'NOT OK' END AS CountID
FROM Table
GROUP BY ID,name
ORDER BY ID
Example:

SQL Server : create group of N rows each and give group number for each group

I want to create a SQL query that SELECT a ID column and adds an extra column to the query which is a group number as shown in the output below.
Each group consists of 3 rows and should have the MIN(ID) as a GroupID for each group. The order by should be ASC on the ID column.
ID GroupNr
------------
100 100
101 100
102 100
103 103
104 103
105 103
106 106
107 106
108 106
I've tried solutions with ROW_NUMBER() and DENSE_RANK(). And also this query:
SELECT
*, MIN(ID) OVER (ORDER BY ID ASC ROWS 2 PRECEDING) AS Groupnr
FROM
Table
ORDER BY
ID ASC
Use row_number() to enumerate the rows, arithmetic to assign the group and then take the minimum of the id:
SELECT t.*, MIN(ID) OVER (PARTITION BY grp) as groupnumber
FROM (SELECT t.*,
( (ROW_NUMBER() OVER (ORDER BY ID) - 1) / 3) as grp
FROM Table
) t
ORDER BY ID ASC;
It is possible to do this without a subquery, but the logic is rather messy:
select t.*,
(case when row_number() over (order by id) % 3 = 0
then lag(id, 2) over (order by id)
when row_number() over (order by id) % 3 = 2
then lag(id, 1) over (order by id)
else id
end) as groupnumber
from table t
order by id;
Assuming you want the lowest value in the group, and they are always groups of 3, rather than the NTILE (as Saravantn suggests, which splits the data into that many even(ish) groups), you could use a couple of window functions:
WITH Grps AS(
SELECT V.ID,
(ROW_NUMBER() OVER (ORDER BY V.ID) -1) / 3 AS Grp
FROM (VALUES(100),
(101),
(102),
(103),
(104),
(105),
(106),
(107),
(108))V(ID))
SELECT G.ID,
MIN(G.ID) OVER (PARTITION BY G.Grp) AS GroupNr
FROM Grps G;
SELECT T2.ID, T1.ID
FROM (
SELECT MIN(ID) AS ID, GroupNr
FROM
(
SELECT ID, ( Row_number()OVER(ORDER BY ID) - 1 ) / 3 + 1 AS GroupNr
FROM Table
) AS T1
GROUP BY GroupNr
) AS T1
INNER JOIN (
SELECT ID, ( Row_number()OVER(ORDER BY ID) - 1 ) / 3 + 1 AS GroupNr
FROM Table
) T2 ON T1.GroupNr = T2.GroupNr

Adjust overlapping dates without CTE

I am working on to adjust overlapping date in database tables.
Sample data as below
StartDate EndDate UNIT ID
2017-06-09 2017-06-22 1A 21
2017-06-09 2017-06-30 1B 21
2017-07-01 2017-07-31 1B 21
Expected output:
StartDate EndDate UNIT ID
2017-06-09 2017-06-22 1A 21
2017-06-22 2017-06-30 1B 21
2017-07-01 2017-07-31 1B 21
appreciate your help on this.
You can use lead/lag in case of 2012+, since your are using 2008 you can query as below:
;With cte as (
Select *, RowN = Row_Number() over(partition by Id order by EndDate ) from #sampledata
)
Select StartDate = Coalesce (Case when Dateadd(DD, 1, c2.Enddate) = c1.Startdate then c1.Startdate Else c2.Enddate End, c1.StartDate)
,c1.Enddate, c1.Unit, C1.Id
from cte c1 left join cte c2
on c1.RowN = c2.RowN+1
If you still do not want to use cte as above then you can do sub-query as below:
Select StartDate = Coalesce (Case when Dateadd(DD, 1, c2.Enddate) = c1.Startdate then c1.Startdate Else c2.Enddate End, c1.StartDate)
,c1.Enddate, c1.Unit, C1.Id
from (Select *, RowN = Row_Number() over(partition by Id order by EndDate ) from #sampledata ) c1
left join (Select *, RowN = Row_Number() over(partition by Id order by EndDate ) from #sampledata ) c2
on c1.RowN = c2.RowN+1
A little modification to #Kannan's Answer.
Select StartDate = Coalesce (Case when c1.Startdate <= c2.Enddate
then c2.Enddate
Else c1.Startdate
End,
c1.StartDate)
,c1.Enddate, c1.Unit, C1.Id
from
(Select *, RowN = Row_Number() over(partition by Id order by EndDate )
from #sample ) c1
left join
(Select *, RowN = Row_Number() over(partition by Id order by EndDate )
from #sample ) c2
on c1.RowN = c2.RowN+1