SQL Server find consecutive failure records - sql

I've look at so many other questions and nothing quite fits my question or gets me the answer I need, maybe I'm just slow today :(
DECLARE #t TABLE (
[InstructionId] INT,
[InstructionDetailId] INT,
[Sequence] INT,
[Status] INT
)
INSERT INTO #t SELECT 222,111,1, 2
INSERT INTO #t SELECT 222,112,2,2
INSERT INTO #t SELECT 222,113,3,4
INSERT INTO #t SELECT 222,114,4,4
INSERT INTO #t SELECT 222,115,5,2
INSERT INTO #t SELECT 222,116,6,4
INSERT INTO #t SELECT 222,117,7,2
INSERT INTO #t SELECT 222,118,8,4
INSERT INTO #t SELECT 222,119,9,4
INSERT INTO #t SELECT 222,120,10,2
INSERT INTO #t SELECT 222,121,11,2
I need to find for which InstructionDetailId's there are consecutive failures (Status = 4) by using the [Sequence] field for checking the order to determine if they are consecutive. So for the above InstructionDetailId 113 and 114 would be consecutive failures as their [Sequence] is 3 & 4, same for InstructionDetailId 118 and 119 would be consecutive failures. I've tried so many row number variation and cte's and I can't quite crack it :( This is for SQL Server 2008 R2 by the way.
Expected output:
InstructionId InstructionDetailId Sequence Status
222 113 3 4
222 114 4 4
222 118 8 4
222 119 9 4
Thanx all!

Perhaps the simplest method is to use lag() and lead():
select t.*
from (select t.*,
lag(t.status) over (partition by t.InstructionId order by t.sequence) as prev_status,
lead(t.status) over (partition by t.InstructionId order by t.sequence) as next_status
from #t t
) t
where status = prev_status or status = next_status;

You can use APPLY :
select t.*
from #t t outer apply
( select top (1) t1.*
from #t t1
where t1.InstructionId = t.InstructionId and
t1.Sequence < t.Sequence
order by t1.Sequence desc
) t1 outer apply
( select top (1) t2.*
from #t t2
where t2.InstructionId = t.InstructionId and
t2.Sequence > t.Sequence
order by t2.Sequence
) t2
where t.status = 4 and (t.status = t1.status or t.status = t2.status);

Could you do something like...
select t1.InstructionID,
t1.InstructionDetailID,
t1.Sequence,
t1.Status,
from #t t1
inner join #t t2 on t1.InstructionID = t2.InstructionID
and t1.Sequence = (t2.Sequence - 1)
and t1.Status = t2.Status
and t1.Status = 4

Related

Define ranges to cover gaps in a number sequence (T-SQL)

Simplifying my problem down - I have 6-digit field which assigns numbers to customers starting from 1 and ending to 999999. Most numbers are sequentially assigned, but numbers can be assigned manually by users, and this feature has been used in an unpredicatable pattern throughout the range.
We now need to identify numbers that have not been assigned (easy) - and then convert this into a number of ranges (seems complex).
For example given the following numbers have been assigned
1,2,3,4,5,
1001,1002,1003,1004,1005,
999101,999102,999103,999104,999105
I need a resulting set of ranges like
Start End
6 1000
1006 999100
999106 999999
My thinking so far is this is probably too complex to write in queries - and best achieved by looping from 1 to 999999, and adding ranges to a temp table.
Interested to hear ideas as I can imagine there are a few approaches. I'm using SQL Server 2008 R2. This is a one-off exercise so even a non-SQL solution might be appropriate, if this were for example easily done in Excel.
Try this
declare #t table (num int)
insert #t values (2),(3),(6),(7),(9),(10),(11)
select
MIN(number) as rangestart,
MAX(number) as rangeend
from
(
select *,
ROW_NUMBER() over (order by number) -
ROW_NUMBER() over (order by num,number) grp
from
(
select number from master..spt_values where type='p' and number between 1 and 15
) numbers
left join #t t
on numbers.number = t.num
) v
where num is null
group by grp
Reference : gaps and islands by itzik ben-gan
To create a numbers query upto 999999
select p1.number + p2.number * 2048 as number
from
(select * from master..spt_values where type='p' ) p1,
(select * from master..spt_values where type='p' and number<489) p2
where p1.number + p2.number * 2048 <=999999
declare #t table (num int)
insert #t values
(2),(3),(4),(5),
(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
;with cte as
(
select num,(ROW_NUMBER() OVER(ORDER BY num)) + 1 as idx from #t
union
select 0 [num],1 [idx] --start boundary
union
select 1000000 [num],COUNT(num) + 2 [idx] from #t --end boundary
)
select c1.num + 1 [Start], c2.num - 1 [End]
from cte c1
inner join cte c2 on c2.idx = c1.idx + 1
where c2.num != c1.num + 1
create table #temp (id int)
insert into #temp (id)
values (1),(2),(3),(1000),(1001),(1002),(2000)
--drop table #temp
with cte as
(
select *, ROW_NUMBER() over(order by id) as rn
from #temp a
)
select a.id + 1, b.id - 1
from cte a join cte b on a.rn = b.rn - 1 and a.id <> b.id -1
it wont include tail ranges, like 2001-9999
Here is SQLFiddle demo
select
case when max(n1)=0 then 1 else max(n1)end,
case when max(n2)=0 then 999999 else max(n2)end
from
(
select t.n+1 as n1,0 n2,
row_number() over(order by t.n)
+isnull((select 0 from t where n=1),1)
rn
from t
left join t t2 on t.n+1=t2.n
where t2.n is null
union all
select 0 n1, t.n-1 as n2 ,
row_number() over(order by t.n) rn
from t
left join t t2 on t.n-1=t2.n
where t2.n is null
and t.n>1
) t3
group by rn
declare #t table(id int)
insert #t values
(1),(2),(3),(4),(5),(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
select t1.id+1 [start], coalesce(t3.[end], 999999) [end]
from #t t1
left join #t t2 on t1.id +1 = t2.id
cross apply
(select min(id)-1 [end] from #t where t1.id < id
) t3
where t2.id is null
if you have a table called "kh" for example with a column "myval" which is your list of integers you could try this SELECT.
SELECT MAX(t1.myval+1) AS 'StartRange',t3.myval-1 AS 'EndRange'
FROM kh t1, kh t3
WHERE t1.myval+1 NOT IN (SELECT myval FROM kh t2 ORDER BY myval)
AND t3.myval-1 NOT IN (SELECT myval FROM kh t4 ORDER BY myval)
AND t1.myval < t3.myval
GROUP BY t3.myval
ORDER BY StartRange

Next/previous record based on current

I have a table which is not sorted by any of column. Is there any way to select next/previous record if I know only Id of current? (I'm using mssql)
Id Label Date
---------------------
1 label1 2011-01-10
7 label2 2011-01-15 -- how to get previous?
5 label3 2011-01-12 -- I know id of this record
10 label10 2011-01-25 -- how to get next?
12 label8 2011-01-13
2 label5 2011-01-29
Thanks in advance!
try this:
VALUES (1, 'label1', '2011-01-10'), (7, 'label2', '2011-01-15'),
(5, 'label3', '2011-01-12'), (10, 'label10', '2011-01-25'),
(12, 'label8', '2011-01-13'), (2, 'label5', '2011-01-29')
select * from table007;
Declare #inptID int=12;
;WITH CTE
as
(
select *, ROW_NUMBER() over (order by (select 0)) as rn
from table007
)
select *
from CTE
where rn in( select rn-1 from CTE where id = #inptID)
union all
select * from CTE where rn in(select rn + 1 from CTE where id = #inptID);
SQL Fiddle Demo
DEMO
If it is not sorted by any column, there is no definitive next or previous record. Data in SQL Server has no order, other than that specified by an ORDER BY clause.
If you really want the previous from the list you enclosed, here is a way.
declare #t table(Id int, Label varchar(10), Date date, s int identity(1,1))
insert #t (id, label, date)
values(1,'label1','2011-01-10'),(7,'label2','2011-01-15'),
(5,'label3','2011-01-12'),(10,'label10','2011-01-25'),
(12,'label8','2011-01-13'),(2,'label5','2011-01-29')
--select the data with a self join
select t1.id as previous_id, t2.id, t2.Label, t2.Date, t3.id, t3.id as next_id
from #t t1
right join
#t t2 on t1.s + 1 = t2.s
left join
#t t3 on t2.s = t3.s - 1

How to do grouping of sets in Sql Server and Filter records

I have a table that looks as under
SALES_ORDER_ID DATEDIFFERENCE FLAG
1 -40 1
1 -20 1
2 40 0
2 -10 1
3 12 0
4 -70 1
5 60 0
5 23 0
//ddl
Declare #t table (sales_order_id int,DateDifference int, Flag int)
Insert Into #t
Select 1,-40,1 Union All Select 1,-20,1 Union All
Select 2,40,0 Union All Select 2,-10,1 Union All
Select 3,12,0 Union All Select 4,-70,1 Union All
Select 5,60,0 Union All Select 5,23,0
Select *
From #t
The output should be
sales_order_id DateDifference Flag
3 12 0
5 60 0
5 23 0
i.e. the group (Sales_Order_Id) where all the items are having the Flag as 0.
I mean Sales_Order_Id = 2 will not appear since at least one item is not zero (2,-10,1)
Please do ask me if the information is not sufficient.
How to do this query?
Edit
I found my answer.. though still looking for a more elegant one
;with cte as(
Select
sales_order_id = Case when x.sales_order_id is null then y.sales_order_id else x.sales_order_id end
,x.CountFor0
,y.CountFor1
From
(Select sales_order_id,CountFor0 = count(*)
from #t
where Flag = 0
group by sales_order_id )x
Full Join
(Select sales_order_id,CountFor1 =count(*)
from #t
where Flag = 1
group by sales_order_id )y
On x.sales_order_id = y.sales_order_id)
Select *
From cte
where CountFor1 is null
Thanks to all
Since I don't have much reputation , so cannot post my answer in answer section
Try this..
SELECT sales_order_id,DateDifference,Flag FROM #t WHERE sales_order_id NOT IN
(SELECT sales_order_id FROM #t WHERE Flag=1)
Good day!
I think that will help:
Select * from #t WHERE FLAG=0;
I just wanted to add that this is a great place to use windows functions:
select sales_order_id, DateDifference, flag
from (select t.*, SUM(flag) over (partition by sales_order_id) as sumflag
from #t t
) t
where sumflag = 0
Ok, now I understand what you are trying to do. You can use NOT EXISTS, as others have suggested (sqlfiddle):
SELECT *
FROM #t T1
WHERE Flag = 0
AND NOT EXISTS (
SELECT *
FROM #t T2
WHERE T1.sales_order_id = T2.sales_order_id
AND T2.Flag <> 0)
You can also use NOT IN, as others have also suggested (sqlfiddle):
SELECT *
FROM #t T1
WHERE Flag = 0
AND sales_order_id NOT IN (
SELECT sales_order_id
FROM #t T2
WHERE T2.Flag <> 0)
And you can even use a JOIN and check the joined table has no row (id is NULL) (sqlfiddle):
SELECT *
FROM #t T1
LEFT OUTER JOIN #t T2 ON T1.sales_order_id = T2.sales_order_id AND T2.Flag <> 0
WHERE T1.Flag = 0
AND T2.sales_order_id IS NULL
Original answer:
You need a WHERE clause to filter the results:
SELECT *
FROM #t
WHERE Flag = 0
Check out the sqlfiddle here
I am assuming you also want the (2,40,0) row in the results, though.
select *
from #T as T1
where not exists(select *
from #T as T2
where T1.sales_order_id = T2.sales_order_id and
T2.Flag = 1)
try this:
Select * From #t
where Flag=0 and sales_order_id not in
(select sales_order_id from #t where Flag !=0)

how to split the content by some keyword by SQL in sqlsever?

how to split the content by some keyword by SQL in sqlsever?
I have two table, t1 and t2, and I want to split some column in t2 by the word in t1.
t1
column1
a
bc
c
z
t2(based)
column1 column2
1 abcrrr
2 cdt
3 e
t2(updated)
column1 column2
1 a
1 bc
1 rrr
2 c
2 dt
3 e
Is it possible to write a SQL sentence to update the t2 like above? I want this performance in SQLserver 2008.
The logic is, if the data in t1.column1 is the subString of some t2.column2, then split t2.column2 to two new row with the same t2.column1, but for the t2.column2 of new rows, one is the subString, other is the rest(with the sutStr removed).
Thanks!
Wawa
declare #t table(column1 int, column2 varchar(100))
insert into #t
select 1, 'ab' union all
select 2, 'cd' union all
select 3, 'e'
select t1.column1,char(t2.number) as columns2 from #t as t1
inner join master..spt_values as t2 on t1.column2 like '%'+char(t2.number)+'%'
where type='p' and number between 97 and 97+25
First create the function
CREATE FUNCTION FN_COL_SPLIT(#COL VARCHAR(200))
RETURNS #TPARTS TABLE ( PART VARCHAR(20) )
AS
BEGIN
;WITH CTE ( RNK ,AUTOID,COLUMN1,COL,SLNO)AS (
SELECT ROW_NUMBER() OVER(ORDER BY T1.AUTOID) AS RNK,T1.AUTOID,T1.COLUMN1,REPLACE(#COL,T1.COLUMN1,'') AS COL,0 FROM T1
WHERE (LEN(REPLACE(#COL,T1.COLUMN1,''))<>LEN(#COL))
UNION ALL
SELECT ROW_NUMBER() OVER(ORDER BY T1.AUTOID) AS RNK,T1.AUTOID,T1.COLUMN1 ,REPLACE(COL,T1.COLUMN1,'') AS COL,SLNO+1 FROM CTE C JOIN T1
ON LEN(REPLACE(COL,T1.COLUMN1,''))<>LEN(COL) )
INSERT INTO #TPARTS
SELECT DISTINCT COLUMN1 FROM CTE WHERE RNK=1
UNION
SELECT COL FROM CTE WHERE LEN(COL)=(SELECT MIN(LEN(COL)) FROM CTE)
IF (SELECT COUNT(*) FROM #TPARTS)=0 INSERT INTO #TPARTS SELECT #COL
RETURN
END
Then use the below select statement to get the result
SELECT T2.COLUMN1,T3.PART FROM T2 CROSS APPLY FN_COL_SPLIT(T2.COLUMN2) T3
You can try this with any set of characters.
declare #t table(column1 int, column2 varchar(100))
insert into #t
select 1, 'abc' union all
select 2, 'cd' union all
select 3, 'eyiuyiu'
select * from #t
;WITH CTE as (
select column1,CASE when len(column2) >= 0 then left(column2,1) else column2 end as column2,RIGHT(column2,len(column2)-1) as rem from #t --where column1 = 1
union all
select t1.column1,CASE when len(rem) >= 0 then left(rem,1) else rem end as column2,RIGHT(rem,len(rem)-1) as rem from CTE c inner join #t t1
on c.column1=t1.column1 and CHARINDEX(c.rem,t1.column2,1) > 0
)
select column1,column2 from cte order by 1
select t2.column1,t1.column1 from t1
inner join t2 on t1.column1 like '%['+ t2.column2 +']%'

SQLite select next and previous row based on a where clause

I want to be able to get the next and previous row using SQLite.
id statusid date
168 1 2010-01-28 16:42:27.167
164 1 2010-01-28 08:52:07.207
163 1 2010-01-28 08:51:20.813
161 1 2010-01-28 07:10:35.373
160 1 2010-01-27 16:09:32.550
46 2 2010-01-30 17:13:45.750
145 2 2010-01-30 17:13:42.607
142 2 2010-01-30 16:11:58.020
140 2 2010-01-30 15:45:00.543
For example:
Given id 46 I would like to return ids 160 (the previous one) and 145 (the next one)
Given id 160 I would like to return ids 161 (the previous one) and 46 (the next one)
etc...
Be aware that the data is ordered by statusId then dateCreated DESC and HAS to work using SQLite.
select * from #t order by statusId, dateCreated desc
Test data created in sql server...
set nocount on; set dateformat ymd;
declare #t table(id int, statusId int, dateCreated datetime)
insert into #t
select 168,1,'2010-01-28 16:42:27.167' union
select 164,1,'2010-01-28 08:52:07.207' union
select 163,1,'2010-01-28 08:51:20.813' union
select 161,1,'2010-01-28 07:10:35.373' union
select 160,1,'2010-01-27 16:09:32.550' union
select 46,2,'2010-01-30 17:13:45.750' union
select 145,2,'2010-01-30 17:13:42.607' union
select 142,2,'2010-01-30 16:11:58.020' union
select 140,2,'2010-01-30 15:45:00.543'
Using SQL server 2005+ this would be fairly trivial!
EDIT:
Here's the same test data script but for SQLite which is the focus of the question.
create table t (id int, statusId int, dateCreated datetime);
insert into t
select 168,1,'2010-01-28 16:42:27.167' union
select 164,1,'2010-01-28 08:52:07.207' union
select 163,1,'2010-01-28 08:51:20.813' union
select 161,1,'2010-01-28 07:10:35.373' union
select 160,1,'2010-01-27 16:09:32.550' union
select 46,2,'2010-01-30 17:13:45.750' union
select 145,2,'2010-01-30 17:13:42.607' union
select 142,2,'2010-01-30 16:11:58.020' union
select 140,2,'2010-01-30 15:45:00.543';
EDIT 2 Please note that the data is not a good example so I have change the id 146 to 46
This problem is a lot more complicated than it first appears. The two order by fields have to be handled separately and then combined with a union and grab the appropriate result. To get both previous and next, we need another union, so we end up with a union with sub-unions.
This works with the supplied data. I tested many inputs and got the right previous/next outputs. When using, make sure you get ALL instances of 146 to replace.
SELECT *
FROM
(
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid = t2.statusid
AND t1.dateCreated >= t2.dateCreated
AND t1.id <> 146
UNION
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid < t2.statusid
ORDER BY
t1.statusid DESC,
t1.dateCreated
LIMIT 1
)
UNION
SELECT *
FROM
(
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid = t2.statusid
AND t1.dateCreated <= t2.dateCreated
AND t1.id <> 146
UNION
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid > t2.statusid
ORDER BY
t1.statusid,
t1.dateCreated DESC
LIMIT 1
)
ORDER BY
statusid,
dateCreated DESC
;
select id from theTable where id>#id order by id desc limit 1
union
select id from theTable where id<#id order by id desc limit 1
You might want to look into using SQLIte primitives such as rowid (https://www.sqlitetutorial.net/sqlite-primary-key/)
OR
creating your own set of rowid's based on the order that you output (possibly based on a sub-select count(*) query).
You can then do a select based on rowid-1 or rowid+1.
EDIT:
Some code. Mostly for my own reference.
-- create a new table with the desired order
DROP TABLE IF EXISTS tt;
CREATE TABLE tt AS
SELECT * FROM t ORDER BY statusId, dateCreated DESC;
-- remove all primary's (rowid's)
VACUUM;
-- create new table from ordered one, and add ascending rowid
DROP TABLE IF EXISTS ttt;
CREATE TABLE ttt AS
SELECT rowid, * FROM tt;
-- create a new table with the row before and after the desired row
-- still need to work out how to do this with a variable for easily changing the id
SELECT t1.*,t3.*,t4.*
FROM ttt t1
LEFT JOIN ttt t3 ON t3.rowid=((SELECT CAST(rowid AS INT) FROM ttt WHERE ttt.id=46)-1)
LEFT JOIN ttt t4 ON t4.rowid=((SELECT CAST(rowid AS INT) FROM ttt WHERE ttt.id=46)+1)
WHERE t1.id=46;