sql how to get consecutive appearance of value - sql

suppose I have a column 'value', which can appear multiple times in a table with another column 'result' which can be either 1 or 0. I would like to search for consecutive 1s (ie result = 1) until the count reaches 4, then I can select value. given the result sets below:
-result set a)
value Result
----- ------
A 1
A 1
A 1
A 0
-result set b)
value Result
----- ------
A 1
A 1
A 1
A 1
result set b meets the condition and therefore value A is selected. How do I go about this ? Thanks.

This is the query: (usually this query is to detect double record in a table, but probably meet your demand).
select value, result, count(value) as [Result Sum]
from #temp
where result = 1
group by value, result
having count(value) >3
This is the Result
value result Result Sum
----- ----------- -----------
A 1 4
UPDATED:
This is the data example in my temporary table (#temp)
value result
----- -----------
A 1
A 1
A 1
A 0
A 1
D 1
D 1
D 1
D 1
B 1
B 1
C 1
C 1
C 1
C 1
From The example data C and D are the valid values
Declare #temp2 table
(
value nvarchar(5)
)
declare #value nvarchar(5), #result int, #total int, #flag bit, #tempValue nvarchar(5)
DECLARE myCursor CURSOR FOR
SELECT value, result
FROM #temp
set #flag = 1
set #tempValue = ''
OPEN myCursor;
FETCH NEXT FROM myCursor into #value, #result;
WHILE ##FETCH_STATUS = 0
BEGIN
--logic here
if (#tempValue <> #value and #result = 1) or #flag = 1
begin
set #tempValue = #value
set #total = 1
set #flag = 0
end
else --#tempvalue = #value
begin
if #result = 1
set #total = #total + 1
else --#result = 0
set #flag = 1
if #total >3 --valid value has reached 4 consecutive result =1
begin
set #flag = 1
insert into #temp2 values (#value)
end
end
FETCH NEXT FROM myCursor into #value, #result;
END;
CLOSE myCursor;
DEALLOCATE myCursor;
select * from #temp2
This is the Result of the loop (table #temp2)
value
-----
D
C
(2 row(s) affected)

You can do this in a select statement. You can find groups of items in a row by using row_number() assuming you have an id. SQL tables are inherently unordered, so you need an id or creation date or something to specify the ordering. Here is the SQL:
select value
from (select t,
(row_number() over (partition by value order by id) -
row_number() over (partition by value, results order by id)
) as grp
from table t
) t
group by value, result, grp
having count(*) > 3 and result = 1;

Related

How to get records that between m records back and n records forward from a reference row - Non-sequential data

My scenario is as follows:
I have a reference record, say, ProductId = 1
The records each have a non-unique ItemTypeId
I would like to fetch records that exists between the following points
START POINT being 2 records BACKWARDS of type ItemTypeId = 1, from record of ProductId =1
END POINT being 3 records FORWARDS of type ItemTypeId = 1, from record of ProductId = 1
The query should get ALL data between the two points, inclusively
Here's a picture that illustrates this better than my words:
How would I structure my query to do this?
Any better way to do it without temp tables?
Thank-you!
Note that for this to work at all, you need that record ID to be an actual column in the table. Rows have no inherent order in a table.
With that in place, you can use LAG and LEAD to get what you want:
CREATE TABLE #t
(
RecordId INT IDENTITY(1,1),
ProductId INT,
ItemType INT
);
INSERT INTO #t(ProductId, ItemType)
VALUES
(5,1),(3,1),(7,3),(6,1),(2,7),
(1,1),(7,3),(8,1),(10,3),(9,5),
(11,1),(19,1),(17,4),(13,3);
WITH c1 AS
(
SELECT ProductId,
RecordId,
LAG(RecordId,2) OVER (ORDER BY RecordId) AS Back2,
LEAD(RecordId,3) OVER (ORDER BY RecordId) AS Forward3
FROM #t
WHERE ItemType = (SELECT ItemType FROM #t WHERE ProductId = 1)
),c2 AS
(
SELECT c1.Back2, c1.Forward3 FROM c1
WHERE c1.ProductId = 1
)
SELECT #t.*
FROM #t
INNER JOIN c2 ON #t.RecordId BETWEEN c2.Back2 AND c2.Forward3;
If you wanna do without using temp tables as you ask, the following solution work.
But it is not very nice i agree.
Well this is what i done :
CREATE DATABASE TEST;
USE TEST
CREATE TABLE PRODUCT
(
ProductId INT,
ItemType INT
)
INSERT INTO PRODUCT
VALUES
(5,1),
(3,1),
(7,3),
(6,1),
(2,7),
(1,1),
(7,3),
(8,1),
(10,3),
(9,5),
(11,1),
(19,1),
(17,4),
(13,3)
DECLARE product_cursor CURSOR FOR
SELECT * FROM PRODUCT;
OPEN product_cursor
DECLARE
#ProductId INT,
#ItemId INT,
#END_FETCH INT,
#countFrom INT,
#countTo INT
DECLARE #TableResult TABLE
(
RProductId INT,
RItemId INT
)
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
SET #END_FETCH = 0
SET #countFrom = 0
SET #countTo = 0
WHILE ##FETCH_STATUS = 0 AND #END_FETCH = 0
BEGIN
IF #ItemId = 1 AND (#countFrom = 0 AND #countTo = 0)
BEGIN
SET #countFrom = 3
SET #countTo = 3
END
ELSE
BEGIN
IF #countFrom > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countFrom -= 1
--SELECT 'CountFrom : ', #countFrom
END
END
ELSE
BEGIN
IF #countTo > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countTo -= 1
--SELECT 'CountTO : ', #countTo
END
END
ELSE
BEGIN
SET #END_FETCH = 1
END
END
END
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
END
CLOSE product_cursor
DEALLOCATE product_cursor
SELECT * FROM #TableResult
And this is the result i got :
RProductId RItemId
3 1
7 3
6 1
2 7
1 1
7 3
8 1
10 3
9 5
11 1
19 1
But i prefer the solution of #James Casey.
By the way, why won't you use temp table ?

How to generate row number based on certain condition?

I have column named type having values 1 and 2. i want to generate the expected results columns.
In this column value 2 should be converted to 0 and for consecutive 1's it should generate the Row number.
Please Refer this image...
Any advise how can we achieve this.
running out of logic.:(
Please try this :
select *,0 as RowNo into #tmp from YourTable
declare #id int
set #id=0
update #tmp
set #id = case typeId when 1 then #id+1 else 0 end,
RowNo = case typeId when 1 then #id else 0 end
select * from #tmp
drop table #tmp
This is the best way to use Cursors
CREATE TABLE [dbo].[Table_1](
[Type] [int] NULL)
Code
Declare #Type int = 0
DECLARE #Test TABLE(
[Type] INT ,
[ExpectedResult] INT
)
DECLARE vendor_cursor CURSOR FOR
SELECT [Type]
FROM [dbo].[Table_1]
OPEN vendor_cursor
FETCH NEXT FROM vendor_cursor
INTO #Type
Declare #ExpectedResult INT = 0
WHILE ##FETCH_STATUS = 0
BEGIN
IF #Type = 2
SET #ExpectedResult = 0
ELSE
SET #ExpectedResult+= 1
INSERT INTO #Test ([Type] ,[ExpectedResult] ) VALUES(#Type , #ExpectedResult)
FETCH NEXT FROM vendor_cursor
INTO #Type
END
CLOSE vendor_cursor;
DEALLOCATE vendor_cursor;
SELECT * FROM #Test
First, you are supposing that your rows have an ordering, but no ordering column is specified. SQL tables represent unordered sets. There is not ordering without such a column.
Let me assume you have one.
Then this is a gaps and islands problem. You want the islands of type = 1 so you can enumerate them. You can identify them by doing a cumulative sum of type = 2 -- this cumulative sum defines the grouping of adjacent type = 1 records. The rest is just row_number():
select t.*,
(case when type = 2 then 0
else row_number() over (partition by type, grp order by <ordering col>)
end) as expected_result
from (select t.*,
sum(case when type = 2 then 1 else 0 end) over (order by <ordering col>) as grp
from t
) t;
Here is a db<>fiddle.

SQL Grouping with condition

I want to sum rows in table. The algorithm is rather simple in theory but hard (at least for me) when I need to build a query.
Generally, I want to sum "values" of a "sub-group". Sub-group is defined as a range of elements starting with first row where type=0 and finishing with last row where type=1. the sub-group should contain only one (first) row with type=0.
The sample below presents correct (left) and incorrect (right) behavior.
I tried several approaches including grouping and partitioning. Unfortunately w/o any success. Anybody had similar problem?
I used MS SQL Server (so T-SQL 'magic' is allowed)
EDIT:
The results I want:
"ab",6
"cdef",20
"ghi",10
"kl",8
You can identify the groups by doing a cumulative sum of zeros. Then use aggregation or window functions.
Note that SQL tables represent unordered sets, so you need a column to specify the ordering. The code below assumes that this column is id.
select min(id), max(id), sum(value)
from (select t.*,
sum(case when type = 0 then 1 else 0 end) over (order by id) as grp
from t
) t
group by grp
order by min(id);
You can use window function with cumulative approach :
select t.*, sum(value) over (partition by grp)
from (select t.*, sum(case when type = 0 then 1 else 0 end) over (order by id) as grp
from table t
) t
where grp > 0;
Solution with a cursor and output-table.
As Gordon wrote it is not defined how the set will be ordered, so ID is also used here.
declare #output as table (
ID_sum nvarchar(max)
,value_sum int
)
DECLARE #ID as nvarchar(1)
,#value as int
,#type as int
,#ID_sum as nvarchar(max)
,#value_sum as int
,#last_type as int
DECLARE group_cursor CURSOR FOR
SELECT [ID],[value],[type]
FROM [t]
ORDER BY ID
OPEN group_cursor
FETCH NEXT FROM group_cursor
INTO #ID, #value,#type
WHILE ##FETCH_STATUS = 0
BEGIN
if (#last_type is null and #type = 0)
begin
set #ID_sum = #ID
set #value_sum = #value
end
if (#last_type in(0,1) and #type = 1)
begin
set #ID_sum += #ID
set #value_sum += #value
end
if (#last_type = 1 and #type = 0)
begin
insert into #output values (#ID_sum, #value_sum)
set #ID_sum = #ID
set #value_sum = #value
end
if (#last_type = 0 and #type = 0)
begin
set #ID_sum = #ID
set #value_sum = #value
end
set #last_type = #type
FETCH NEXT FROM group_cursor
INTO #ID, #value,#type
END
CLOSE group_cursor;
DEALLOCATE group_cursor;
if (#last_type = 1)
begin
insert into #output values (#ID_sum, #value_sum)
end
select *
from #output

SQL Server: how to update a column with a value that is in that column when another number in another column is >1

I have a table with the following data:
Part Comp level item_nbr
-------------------------------
abc ab 1 1
null cd 2 2
null ef 3 3
cde gh 1 4
null ij 2 5
null kl 3 6
null mn 4 7
I would like to update the nulls to the value in each level 1, so every level that is >1 is updated with the level one value.
Part Comp level
---------------------
abc ab 1
abc cd 2
abc ef 3
cde gh 1
cde ij 2
cde kl 3
cde mn 4
I am at a loss as to how to achieve this on a very large dataset. Any help would be greatly appreciated!
To explain another way,
part level
abc 1
2
3
Then the next row is populated with another part
efg 1
2
2
etc.
Further clarification:
I need the string"abc" to be filled down with the string "abc" while the column fields below are null. The next row has a string of efg and the following column fields below are null, again, those fields should be filled down with the value "efg" and so on.
The level field = 1 will always have a part number, but all the other levels report up to the level 1 part, so should be populated identically. And repeat.
Hope this makes sense.
Use an updatable CTE with window functions:
with toupdate as (
select t.*,
max(part) over (partition by itm_nbr_not_null) as new_part
from (select t.*,
max(case when part is not null then item_nbr end) over (order by item_nbr) as itm_nbr_not_null
from t
) t
)
update toupdate
set part = new_part
where part is null;
You can run the CTE to see what is happening.
well, from your question what I understand is, you need to update the null column's value until you get a not null value. and you want to continue it up to the last row of the table.
for that scenario, I created a stored procedure, where I read the value of every n-th cell if it is null I changing it with the prev. cell's value, when the cell was not null.
Approach:
create a temporary table/ table variable.
add an extra column, which is basically identity, which will help to rank the column.
iterate a loop until the maximum row is reached.
in each iteration, read the cell value for the i-th row
4.1 if it is not null put it in a temporary variable.
4.2 else, replace/update the i-th cell's value with the temporary variable
continue it, until you reached up to the last row of the table/table variable.
look at my following snippets:
create proc DemoPost
as
begin
declare #table table(serial_no int identity(1,1), name varchar(30), text varchar(30), level int)
insert #table
select Name, Text, Level from Demo
declare #max as int = (select max(serial_no) from #table)
--select #max
declare #i as int =0
declare #temp as varchar(30)
declare #text as varchar(30)
while #i < #max
begin
set #i = #i +1
set #temp = (select name from #table where serial_no = #i)
-- if #temp is not null, fetch its value, otherwise, update/replace it with
-- previously gotten not-null cell's value.
if #temp is not null
begin
set #text = (select name from #table where serial_no = #i)
end
else
begin
update #table
set name = #text where serial_no = #i
end
end
select name, text, level from #table
end
You can update it using temporary table according to the given scenario i thought item_nbr is unique in row Hope this will help
SELECT *
INTO #TEMP
FROM URTablehere
DECLARE #PRev VARCHAR(MAX)
WHILE ( SELECT COUNT(*)
FROM URTablehere
) > 0
BEGIN
DECLARE #ID INT
DECLARE #Part VARCHAR(MAX)
DECLARE #Num INT
SELECT TOP ( 1 )
#ID = level ,
#Part = Part ,
#Num = item_nbr
FROM #TEMP
IF ( #ID = 1 )
BEGIN
SELECT #PRev = #Part
END
IF ( #ID > 1
AND #Part IS NULL
)
BEGIN
UPDATE URTablehere
SET Part = #PRev
WHERE item_nbr = #Num
END
DELETE
FROM #TEMP WHERE item_nbr=#Num
END

sql for counting null in between and inserting number

I have a table with column c1.I need c2 like this:
C1 C2
(somevalue) 3
(somevalue) 3
NULL 3
(somevalue) 2
NULL 2
(somevalue) 3
NULL 3
NULL 3
(somevalue) 2
NULL 2
(somevalue) 1
Logic:count rows that are not null until a null is found followed by a not null value, including the nulls, but without the following value. Start counting again when null is crossed until next null is found.
The following is the answer to the original question where the OP wanted to count NULL values.
C1 C2
NULL 3
NULL 3
(somevalue) 3
NULL 4
NULL 4
NULL 4
(somevalue) 4
NULL 2
(somevalue) 2
Assuming the columns are to be ordered by an id column, this might be what you want:
select id, c1, x, y,
count(1) over (partition by y) z
from(
select id, c1, x,
LAG(x,1,x) over (order by id) y
from (
select
id,
c1,
count(c1) over (order by id) x
from test
)
)
order by id;
Of course you can remove the superfluous columns from the outermost select statement.
There is a Sql Fiddle for it.
An answer for the current version can be obtained by replacing C1 in the answer with decode(c1,null, 'x', null) and possibly some minor adjustments for border cases.
Here is another approach with a cursor (assuming table is ordered by an id column):
declare #id int, #C1 varchar(15), #C2 int, #counter int
select #counter = 0
DECLARE cursor1 CURSOR FOR
SELECT id, C1, C2 FROM tableName order by id
OPEN cursor1;
FETCH NEXT FROM cursor1 INTO #id, #C1, #C2;
WHILE ##FETCH_STATUS = 0
BEGIN
if (#C1 is NULL)
select #counter = #counter + 1
else
begin
select #counter = #counter + 1
update tableName set C2 = #counter where id = #id
select #id, #C1, #C2, #counter
select #counter = 0
end
FETCH NEXT FROM cursor1 INTO #id, #C1, #C2;
END
CLOSE cursor1;
DEALLOCATE cursor1;
Happy coding!