T SQL Cursor Update field based on previous field - sql-server

I'm using SQL Server 2016.
I have this table:
RowID SKU Shop Week Prioirty Replen Open_Stk
---------------------------------------------------------------
1 111 100 1 1 400 5000
2 111 200 1 2 400 NULL
3 111 300 1 3 400 NULL
4 111 400 1 4 400 NULL
This is the desired result:
RowID SKU Shop Week Prioirty Replen Open_Stk
---------------------------------------------------------------
1 111 100 1 1 400 5000
2 111 200 1 2 400 4600
3 111 300 1 3 400 4200
4 111 400 1 4 400 3800
The calculation for Open_Stk is based on the previous row:
[Open_Stk] = [Open_Stk]-IIF([Replen]<=IIF([Open_Stk]>=0,[Open_Stk],0),[Replen],0)
I am using the below cursor to update the Open_Stk but nothing happens - what am I missing:
DECLARE #CurrentRow INT;
DECLARE #PreviousRow INT
DECLARE ShopRank CURSOR FOR
SELECT RowID
FROM [tmp_tblTEST]
ORDER BY [SKU], [Week],Priority
OPEN ShopRank
FETCH NEXT FROM ShopRank INTO #CurrentRow
WHILE ##FETCH_STATUS = 0
BEGIN
IF ((SELECT [Open_Stk] FROM [tmp_tblTEST] WHERE RowID = #CurrentRow) IS NULL)
BEGIN
UPDATE [tmp_tblTEST]
SET [Open_Stk] = [Open_Stk] - IIF([Replen] <= IIF([Open_Stk] >= 0, [Open_Stk], 0), [Replen], 0)
WHERE RowID = #PreviousRow
END
SET #PreviousRow = #CurrentRow
FETCH NEXT FROM ShopRank INTO #CurrentRow
END
CLOSE ShopRank
DEALLOCATE ShopRank

There's no need for a CURSOR here at all. This is a little bit of guess work, but I suspect what you are actually after here is something like this:
SELECT V.RowID,
V.SKU,
V.Shop,
V.[Week],
V.Priority,
V.Replen,
FIRST_VALUE(V.Open_Stk) OVER (PARTITION BY V.SKU ORDER BY V.[Week], V.Priority
ROWS UNBOUNDED PRECEDING) -
ISNULL(SUM(V.Replen) OVER (PARTITION BY V.SKU ORDER BY V.[Week], V.Priority
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),0) AS OpenStk
FROM (VALUES (1,111,100,1,1,400,5000),
(2,111,200,1,2,400,NULL),
(3,111,300,1,3,400,NULL),
(4,111,400,1,4,400,NULL))V(RowID,SKU,Shop,[Week],Priority,Replen,Open_Stk)
ORDER BY V.Sku,
V.[Week],
V.Priority;
DB<>Fiddle (using original solution)
FIRST_VALUE does what is says on the tin. The SUM subtracts the values from every prior row from the value of Open_Stk on the first row; making the final result set. It only references the prior rows due to the ROWS BETWEEN clause. ROWS UNBOUNDED means to start at the beginning of the partitioned range, and 1 PRECEDING means the row prior.

WITH result AS
(
SELECT
a.*, ISNULL(NULLIF(a.Open_Stk, 0), 0) AS Output
FROM
table1 a
JOIN
table1 b ON a.Prioirty = b.Prioirty - 1
UNION ALL
SELECT
a.*, output - a.Replen
FROM
table1 a
JOIN
result b ON a.Prioirty = b.Prioirty+1
)
SELECT *
FROM result
WHERE output > 0

Related

How to select distinct rows, but repeat if it has a different row between the equal ones

Having data like this:
id text bit date
1 row 1 2016-11-24
2 row 1 2016-11-25
3 row 0 2016-11-26
4 row 1 2016-11-27
I want to select the data based on where the text and bit columns are distinct, but based on some order, in this case the id, the data changes between two identical rows, it should duplicate this row on the selection.
So, if I use distinct on SQL, I would get rows 1 and 3, but I want to retreive rows 1, 3 and 4, because even 1 and 4 being identical, row 3 is between then when ordering by id.
With a larger dataset, like:
id text bit date
1 row 1 2016-11-24
2 row 1 2016-11-25
3 row 0 2016-11-26
4 row 1 2016-11-27
5 foo 1 2016-11-28
6 bar 1 2016-11-29
7 row 1 2016-11-30
8 row 0 2016-12-01
9 row 0 2016-12-02
10 row 1 2016-12-03
Again, selecting with distinct on text and bit columns, the query would retrieve rows 1,3,5 and 6, but actually I want rows 1,3,4,5,6,7,8 and 10.
;with tb(id,[text],[bit],[date]) AS (
SELECT 1,'row',1,'2016-11-24' union
SELECT 2,'row',1,'2016-11-25' union
SELECT 3,'row',0,'2016-11-26' union
SELECT 4,'row',1,'2016-11-27' union
SELECT 5,'foo',1,'2016-11-28' union
SELECT 6,'bar',1,'2016-11-29' union
SELECT 7,'row',1,'2016-11-30' union
SELECT 8,'row',0,'2016-12-01' union
SELECT 9,'row',0,'2016-12-02' union
SELECT 10,'row',1,'2016-12-03')
select t1.* from tb as t1
OUTER APPLY (select top 1 [text],[bit] from tb as tt where tt.id<t1.id order by id desc ) as t2
where t1.[text]!=isnull(t2.[text],'') or t1.[bit]!=isnull(t2.[bit],1-t1.[bit])
result set:
1 row 1 2016-11-24
3 row 0 2016-11-26
4 row 1 2016-11-27
5 foo 1 2016-11-28
6 bar 1 2016-11-29
7 row 1 2016-11-30
8 row 0 2016-12-01
10 row 1 2016-12-03
It seems that you need a row-by-row operator. You need to know if the new row is the same as the previous one or not. If it is, neglect it, if not, keep it. Here is my solution:
declare #text varchar(100)=(select [text] from Mytable where id = 1)
declare #bit bit = (select [bit] from Mytable where id = 1)
declare #Newtext varchar(100)
declare #Newbit bit
declare #Mytable table(id int, [text] varchar(100), [bit] bit)
Insert into #Mytable select id,text, bit from Mytable where id = 1
declare #counter int =2
while #counter<=(select COUNT(*) from MyTable)
Begin
select #Newtext=(select [text] from Mytable where id = #counter)
select #Newbit=(select [bit] from Mytable where id = #counter)
IF #Newtext!=#text or #Newbit!=#bit
Begin
Insert into #Mytable
select * from Mytable where id = #counter
End
set #text = #Newtext
set #bit = #Newbit;
set #counter = #counter+1
END
select * from #Mytable

Transact-SQL - number rows until condition met

I'm trying to generate the numbers in the "x" column considering the values in field "eq", in a way that it should assign a number for every record until it meets the value "1", and the next row should reset and start counting again. I've tried with row_number, but the problem is that I only have ones and zeros in the column I need to evaluate, and the cases I've seen using row_number were using growing values in a column. Also tried with rank, but I haven't managed to make it work.
nInd Fecha Tipo #Inicio #contador_I #Final #contador_F eq x
1 18/03/2002 I 18/03/2002 1 null null 0 1
2 20/07/2002 F 18/03/2002 1 20/07/2002 1 1 2
3 19/08/2002 I 19/08/2002 2 20/07/2002 1 0 1
4 21/12/2002 F 19/08/2002 2 21/12/2002 2 1 2
5 17/03/2003 I 17/03/2003 3 21/12/2002 2 0 1
6 01/04/2003 I 17/03/2003 4 21/12/2002 2 0 2
7 07/04/2003 I 17/03/2003 5 21/12/2002 2 0 3
8 02/06/2003 F 17/03/2003 5 02/06/2003 3 0 4
9 31/07/2003 F 17/03/2003 5 31/07/2003 4 0 5
10 31/08/2003 F 17/03/2003 5 31/08/2003 5 1 6
11 01/09/2005 I 01/09/2005 6 31/08/2003 5 0 1
12 05/09/2005 I 01/09/2005 7 31/08/2003 5 0 2
13 31/12/2005 F 01/09/2005 7 31/12/2005 6 0 3
14 14/01/2006 F 01/09/2005 7 14/01/2006 7 1 4
There is another solution available:
select
nind, eq, row_number() over (partition by s order by s)
from (
select
nind, eq, coalesce((
select sum(eq) +1 from mytable pre where pre.nInd < mytable.nInd)
,1) s --this is the sum of eq!
from mytable) g
The inner subquery creates groups sequentially for each occurrence of 1 in eq. Then we can use row_number() over partition to get our counter.
Here is an example using Sql Server
I have two answers here. One is based off of the ROW_NUMBER() and the other is based off of what appears to be your index (nInd). I wasn't sure if there would be a gap in your index so I made the ROW_NUMBER() as well.
My table format was as follows -
myIndex int identity(1,1) NOT NULL
number int NOT NULL
First one is ROW_NUMBER()...
WITH rn AS (SELECT *, ROW_NUMBER() OVER (ORDER BY myIndex) AS rn, COUNT(*) AS max
FROM counting c GROUP BY c.myIndex, c.number)
,cte (myIndex, number, level, row) AS (
SELECT r.myIndex, r.number, 1, r.rn + 1 FROM rn r WHERE r.rn = 1
UNION ALL
SELECT r1.myIndex, r1.number,
CASE WHEN r1.number = 0 AND r2.number = 1 THEN 1
ELSE c.level + 1
END,
row + 1
FROM cte c
JOIN rn r1
ON c.row = r1.rn
JOIN rn r2
ON c.row - 1 = r2.rn
)
SELECT c.myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);
Now the index...
WITH cte (myIndex, number, level) AS (
SELECT c.myIndex + 1, c.number, 1 FROM counting c WHERE c.myIndex = 1
UNION ALL
SELECT c1.myIndex + 1, c1.number,
CASE WHEN c1.number = 0 AND c2.number = 1 THEN 1
ELSE c.level + 1
END
FROM cte c
JOIN counting c1
ON c.myIndex = c1.myIndex
JOIN counting c2
ON c.myIndex - 1 = c2.myIndex
)
SELECT c.myIndex - 1 AS myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);
The answer that I have now is via using
Cursor
I know if there is another solution without cursor it will be better for performance aspects
here is a quick demo of my solution:
-- Create DBTest
use master
Go
Create Database DBTest
Go
use DBTest
GO
-- Create table
Create table Tabletest
(nInd int , eq int)
Go
-- insert dummy data
insert into Tabletest (nInd,eq)
values (1,0),
(2,1),
(3,0),
(4,1),
(5,0),
(6,0),
(7,0),
(8,0),
(9,1),
(8,0),
(9,1)
Create table #Tabletest (nInd int ,eq int ,x int )
go
DECLARE #nInd int , #eq int , #x int
set #x = 1
DECLARE db_cursor CURSOR FOR
SELECT nInd , eq
FROM Tabletest
order by nInd
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #nInd , #eq
WHILE ##FETCH_STATUS = 0
BEGIN
if (#eq = 0)
begin
insert into #Tabletest (nInd ,eq ,x) values (#nInd , #eq , #x)
set #x = #x +1
end
else if (#eq = 1)
begin
insert into #Tabletest (nInd ,eq ,x) values (#nInd , #eq , #x)
set #x = 1
end
FETCH NEXT FROM db_cursor INTO #nInd , #eq
END
CLOSE db_cursor
DEALLOCATE db_cursor
select * from #Tabletest
The end result set will be as following:
Hope it helps.
Looking at this a slightly different way (which might not be true, but eliminates the need for cursors of recursive CTEs), it looks like you building ordered groups within your dataset. So, start by finding those groups, then determining the ordering of each of them.
The real key is to determine the rules to find the correcting grouping. Based on your description and comments, I'm guessing the grouping is from the start (ordered by the nInd column) ending at each row with and eq value of 1, so you can do something like:
;with ends(nInd, ord) as (
--Find the ending row for each set
SELECT nInd, row_number() over(order by nInd)
FROM mytable
WHERE eq=1
), ranges(sInd, eInd) as (
--Find the previous ending row for each ending row, forming a range for the group
SELECT coalesce(s.nInd,0), e.nInd
FROM ends s
right join ends e on s.ord=e.ord-1
)
Then, using these group ranges, you can find the final ordering of each:
select t.nInd, t.Fecha, t.eq
,[x] = row_number() over(partition by sInd order by nInd)
from ranges r
join mytable t on r.sInd < t.nInd
and t.nInd <= r.eInd
order by t.nInd

Group By Count and a field in the same query

I have a table (MyTable) with 3 columns; Number, Line, Amount.
If Number is the same in more than 3 records, I want to return these records grouped by Number. Otherwise I want to all other records grouped by Number and Line.
This is what I got so far. It gives an error as Line needs to be in an aggregated function
select * (
select Number, (case when count(Line) > 3 then -1 else Line end) as Line2, sum(Amount)
from MyTable
group by Number, Line2
) as x
order by x.Number
MyTable looks something like this:
Number Line Amount
------------------------
1 1 100
1 1 100
1 2 200
1 2 200
2 1 150
2 1 150
3 1 300
3 2 350
I want the result look something like this:
Number Line2 Amount
------------------------
1 -1 600 <- More than 3 lines
2 1 300 <- Grouped by Line
3 1 300
3 2 350
Try this:
SELECT Number, -1 AS Line, SUM(Amount) AS Amount
FROM mytable
GROUP BY Number
HAVING COUNT(*) > 3
UNION
SELECT Number, Line, SUM(Amount) AS Amount
FROM mytable
WHERE Number NOT IN (SELECT Number
FROM mytable
GROUP BY Number
HAVING COUNT(*) > 3)
GROUP BY Number, Line
You can use CTE like this:
DECLARE #t table
(Number int,Line int,Amount int)
INSERT #t VALUES
(1,1,100),(1,1,100),(1,2,200),
(1,2,200),(2,1,150),(2,1,150),
(3,1,300),(3,2,350)
;WITH CTE as
(
SELECT
Number, Line, Amount,
count(*) over (partition by number) cnt
FROM #t
)
SELECT
Number,
CASE WHEN cnt < 3 THEN Line ELSE -1 END Line2,
SUM(Amount) Amount
FROM CTE
GROUP BY number, CASE WHEN cnt < 3 THEN Line ELSE -1 END
Result:
Number Line Amount
1 -1 600
2 1 300
3 1 300
3 2 350

Trying avoid using cursor

I have been given a query and trying to figure out a way to remove the cursor yet maintaining functionality, because the starting table can get into the millions of rows.
Example of data in table:
ID DollarValue Month RowNumber
1 $10 1/1/2014 1
1 $15 2/1/2014 2
1 -$40 3/1/2014 3
1 $50 4/1/2014 4
2 -$11 1/1/2014 1
2 $11 2/1/2014 2
2 $5 3/1/2014 3
Expected results:
ID DollarValue Month RowNumber TestVal
1 $10 1/1/2014 1 1
1 $15 2/1/2014 2 0
1 -$40 3/1/2014 3 -1
1 $50 4/1/2014 4 1
2 -$11 1/1/2014 1 -1
2 $11 2/1/2014 2 0
2 $5 3/1/2014 3 1
Here is the logic (pseudocode)that happens inside the cursor:
If a #ID <> #LastId AND #Month <> #LastMonth
Set #RunningTotal = #DollarValue
Set #LastMonth = '12/31/2099'
Set #LastID = #ID
Set #TestVal = Sign(#DollarValue)
Else
If Sign(#RunningTotal) = Sign(#RunningTotal + #DollarValue)
Set #TestVal = 0
Else
Set #TestVal = Sign(#DollarValue)
Set #RunningTotal = #RunningTotal + #DollarValue
Any idea how I can change this to set based?
You can use the windowed version of SUM to calculate running totals:
;WITH CTE AS (
SELECT ID, DollarValue, Month, RowNumber,
SUM ( DollarValue ) OVER (PARTITION BY ID ORDER BY RowNumber) as RunningTotal
FROM #mytable
)
SELECT C1.ID, C1.DollarValue, C1.Month, C1.RowNumber,
CASE WHEN C1.RowNumber = 1 THEN SIGN(C1.DollarValue)
WHEN SIGN(C1.RunningTotal) = SIGN(C2.RunningTotal) THEN 0
ELSE SIGN(C1.RunningTotal)
END AS TestVal
FROM CTE AS C1
LEFT JOIN CTE AS C2 ON C1.ID = C2.ID AND C1.RowNumber = C2.RowNumber + 1
Using LEFT JOIN on RowNumber you can get the previous record and compare the current running total with the previous one. Then use a simple CASE to apply rules pertinent to changes in SIGN of running total.
SQL FIDDLE Demo
P.S. It seems the above solution wont work in versions prior to SQL Server 2012. In this case the running total calculation inside the CTE has to be replaced by the "conventional" version.
This is 2008 solution
WITH CTE AS (
SELECT
AA.[ID]
,AA.[Month]
,AA.[RowNumber]
,AA.[DollarValue]
,SIGN(SUM(BB.[DollarValue])) AS RunTotalSign
FROM YourTable AS AA
LEFT JOIN YourTable AS BB
ON (AA.[ID] = BB.[ID] AND BB.[RowNumber] <= AA.[RowNumber])
GROUP BY AA.[ID],AA.[Month],AA.[DollarValue],AA.[RowNumber])
)
SELECT
AA.[ID]
,AA.[Month]
,AA.[RowNumber]
,AA.[DollarValue]
,CASE WHEN AA.RunTotalSign = CC.RunTotalSign Then 0
ELSE AA.RunTotalSign
END
AS TestVal
FROM CTE AS AA
LEFT JOIN CTE AS CC
ON (AA.[ID] = CC.[ID] AND AA.[RowNumber] = CC.[RowNumber]+1)

T-SQL - Incremental update using subquery

I'm trying to write a incremental update statement using SQL Server 2012.
Current Data:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx NULL
3 16 xxx NULL
12 19 yyy 3
13 19 yyy NULL
14 19 yyy NULL
15 19 yyy NULL
Expected result:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx 1
3 16 xxx 0
12 19 yyy 3
13 19 yyy 2
14 19 yyy 1
15 19 yyy 0
However with following approach, I ended up with the result set as below.
UPDATE a
SET a.Revision = (SELECT MIN(b.Revision)
FROM [dbo].[foo] b
WHERE b.item_code = a.item_code
AND b.budget_id = a.budget_id
GROUP BY b.item_code ) -1
FROM [dbo].[foo] a
WHERE a.Revision is NULL
Result:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx 1
3 16 xxx 1
12 19 yyy 3
13 19 yyy 2
14 19 yyy 2
15 19 yyy 2
Can anyone help me to get this right?
Thanks in advance!
Try this:
;with cte as
(select *, row_number() over (partition by budget_id order by rec_no desc) rn from dbo.foo)
update cte
set revision = rn - 1
Basically, since the revision value seems to be decreasing with increase in rec_no, we simply use the row_number() function to get row number of each record within the subset of all records with a particular budget_id, sorted in descending order of rec_no. Since the least possible value of row_number() will be 1, we subtract 1 so that the last record in the partition will have revision set to 0 instead 1.
You may test the code here
I found this example from this link https://stackoverflow.com/a/13629639/1692632
First you select MIN value to some variable and then you can update table by decreasing variable at same time.
DECLARE #table TABLE (ID INT, SomeData VARCHAR(10))
INSERT INTO #table (SomeData, ID) SELECT 'abc', 6 ;
INSERT INTO #table (SomeData) SELECT 'def' ;
INSERT INTO #table (SomeData) SELECT 'ghi' ;
INSERT INTO #table (SomeData) SELECT 'jkl' ;
INSERT INTO #table (SomeData) SELECT 'mno' ;
INSERT INTO #table (SomeData) SELECT 'prs' ;
DECLARE #i INT = (SELECT ISNULL(MIN(ID),0) FROM #table)
UPDATE #table
SET ID = #i, #i = #i - 1
WHERE ID IS NULL
SELECT *
FROM #table
I'm not sure if this will do the trick but you can try with
Update top(1) a
SET a.Revision = (Select MIN(b.Revision)
FROM [dbo].[foo] b where b.item_code = a.item_code and b.budget_id = a.budget_id
group by b.item_code ) -1
FROM [dbo].[foo] a
WHERE a.Revision is NULL
and repeat until there's no changes left
Update Data
set Revision = x.Revision
from
(select RecNo, Budget_ID, Item_Code, case when Revision is null then ROW_NUMBER() over(partition by Budget_ID order by RecNo desc) - 1 else Revision end Revision
from Data
) x
where x.RecNo = data.RecNo
You basically use ROW_NUMBER() to count backwards for each Budget_ID, and use that row number minus 1 where Revision is null. This is basically the same as Shree's answer, just without the CTE.

Resources