sql for counting null in between and inserting number - sql

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!

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.

Insert into table using a select statement and a while loop

I need to insert values into a table from another table. I also want to use a while loop to update a row in my table at the same time. Below you can see my query.
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from SAMPLES
select #id = #id + 1
end
This works but not as i was expecting. Instead of giving me three rows affected, it gives me three rows affected, three times. So i end up with nine new rows instead of the desired three i want.
ID_PRODUCT PRODUCTID PRODUCTDESC COUNT
35746 136559 Desc1 1
35747 276732 Desc2 1
35748 259910 Desc3 1
35746 136559 Desc1 2
35747 276732 Desc2 2
35748 259910 Desc3 2
35746 136559 Desc1 3
35747 276732 Desc2 3
35748 259910 Desc3 3
What i want to acheive is this :
ID_PRODUCT PRODUCTID PRODUCTDESC COUNT
35746 136559 Desc1 1
35747 276732 Desc2 2
35748 259910 Desc3 3
Can anyone see what im doing wrong?
The SELECT part of Insert statement always returns same records
#id does not change the selected rows, only inserts same data set with different #id values
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from SAMPLES
You are inserting three times same list of rows with different #id.
I guess you actually mean this:
;with DistinctSampleValues as
(
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC
from SAMPLES
)
insert into [dbo].[TEST] ([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT])
select
ID_PRODUCT,PRODUCTID,PRODUCTDESC,
ROW_NUMBER() OVER(ORDER BY ID_PRODUCT) RN
from DistinctSampleValues
one time insert all distinct values with additional "row number".
You are close. Add top 1 and where clause like ID_PRODUCT not in Test table
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
SELECT DISTINCT TOP 1
ID_PRODUCT,
PRODUCTID,
PRODUCTDESC,
#id
from
SAMPLES S
WHERE
S.ID_PRODUCT NOT IN
(
SELECT T.ID_PRODUCT FROM [TEST] T
)
select #id = #id + 1
end
Eralper and Ivan Starostin both answered correctly and it is right solution for you.
To run your code correctly, you need to add WHERE clause.
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from
SAMPLES s
WHERE Count = #id
AND NOT EXISTS(SELECT 1 FROM Test t WHERE t.ID_PRODUCT = s.ID_PRODUCT
AND t.PRODUCTID = s.PRODUCTID)
select #id = #id + 1
end

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x

sql how to get consecutive appearance of value

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;