T SQL Cursor is only updating row with same value - sql-server

I'm using MS SQL Server and I have the below table 'tmp_AVG_Weekly_Sales':
RowID SKU Shop Week Avg_Value LAMBDA PMF Value
1 ABC 200 2 1 2 0.13 NULL
2 DEF 250 2 2 4 0.018 NULL
3 XYZ 300 3 3 6 0.0024 NULL
I need to work out the Value field based on the below logic - I am using a Cursor and Loop:
DECLARE #CUMULATIVE AS FLOAT = 0;
DECLARE #COUNT AS INT = 0;
DECLARE #LAMBDA AS FLOAT;
DECLARE #RowID AS INT;
DECLARE #PoissonCursor AS CURSOR;
DECLARE #THRESHOLD AS FLOAT = 0.99;
DECLARE #PMF AS FLOAT --= EXP(-#LAMBDA)
SET #PoissonCursor = CURSOR FOR
SELECT RowID
FROM
[tmp_AVG_Weekly_Sales]
OPEN #PoissonCursor;
FETCH NEXT FROM #PoissonCursor INTO #RowID;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #LAMBDA = LAMBDA FROM [tmp_AVG_Weekly_Sales] WHERE RowID = #RowID
SELECT #PMF = PMF FROM [tmp_AVG_Weekly_Sales] WHERE RowID = #RowID
WHILE (#CUMULATIVE < #Threshold)
BEGIN
SET #CUMULATIVE += #PMF
SET #COUNT += 1
SET #PMF = #PMF * (#LAMBDA / #COUNT)
END
UPDATE [tmp_AVG_Weekly_Sales] SET [Value] = #COUNT - 1 WHERE RowID = #RowID
FETCH NEXT FROM #PoissonCursor INTO #RowID;
END
However, the above is just populating the Value field with the same value:
RowID SKU Shop Week Avg_Value LAMBDA PMF Value
1 ABC 200 2 1 2 0.13 6
2 DEF 250 2 2 4 0.018 6
3 XYZ 300 3 3 6 0.0024 6
When I am expecting the below:
RowID SKU Shop Week Avg_Value LAMBDA PMF Value
1 ABC 200 2 1 2 0.13 6
2 DEF 250 2 2 4 0.018 9
3 XYZ 300 3 3 6 0.0024 12
Where am I going wrong?

You never reset #CUMULATIVE or increase #Threshold, so the block of SET calls are only executed the first go through and each subsequent UPDATE just uses those original values.

Related

Grouping between two datetimes

I have a bunch of production orders and I'm trying to group by within a datetime range, then count the quantity within that range. For example, I want to group from 2230 to 2230 each day.
PT.ActualFinish is datetime (eg. if PT.ActualFinish is 2020-05-25 23:52:30 then it would be counted on the 26th May instead of the 25th)
Currently it's grouped by date (midnight to midnight) as opposed to the desired 2230 to 2230.
GROUP BY CAST(PT.ActualFinish AS DATE)
I've been trying to reconcile some DATEADD with the GROUP without success. Is it possible?
Just add 1.5 hours (90 minutes) and then extract the date:
group by convert(date, dateadd(minute, 90, pt.acctualfinish))
For this kind of thing you can use a function I created called NGroupRangeAB (code below) which can be used to create groups over values with an upper and lower bound.
Note that this:
SELECT f.*
FROM core.NGroupRangeAB(0,1440,12) AS f
ORDER BY f.RN;
Returns:
RN GroupNumber Low High
--- ------------ ------ -------
0 1 0 120
1 2 121 240
2 3 241 360
3 4 361 480
4 5 481 600
5 6 601 720
6 7 721 840
7 8 841 960
8 9 961 1080
9 10 1081 1200
10 11 1201 1320
11 12 1321 1440
This:
SELECT
f.GroupNumber,
L = DATEADD(MINUTE,f.[Low]-SIGN(f.[Low]),CAST('00:00:00.0000000' AS TIME)),
H = DATEADD(MINUTE,f.[High]-1,CAST('00:00:00.0000000' AS TIME))
FROM core.NGroupRangeAB(0,1440,12) AS f
ORDER BY f.RN;
Returns:
GroupNumber L H
------------- ---------------- ----------------
1 00:00:00.0000000 01:59:00.0000000
2 02:00:00.0000000 03:59:00.0000000
3 04:00:00.0000000 05:59:00.0000000
4 06:00:00.0000000 07:59:00.0000000
5 08:00:00.0000000 09:59:00.0000000
6 10:00:00.0000000 11:59:00.0000000
7 12:00:00.0000000 13:59:00.0000000
8 14:00:00.0000000 15:59:00.0000000
9 16:00:00.0000000 17:59:00.0000000
10 18:00:00.0000000 19:59:00.0000000
11 20:00:00.0000000 21:59:00.0000000
12 22:00:00.0000000 23:59:00.0000000
Now for a real-life example that may help you:
-- Sample Date
DECLARE #table TABLE (tm TIME);
INSERT #table VALUES ('00:15'),('11:20'),('21:44'),('09:50'),('02:15'),('02:25'),
('02:31'),('23:31'),('23:54');
-- Solution:
SELECT
GroupNbr = f.GroupNumber,
TimeLow = f2.L,
TimeHigh = f2.H,
Total = COUNT(t.tm)
FROM core.NGroupRangeAB(0,1440,12) AS f
CROSS APPLY (VALUES(
DATEADD(MINUTE,f.[Low]-SIGN(f.[Low]),CAST('00:00:00.0000000' AS TIME)),
DATEADD(MINUTE,f.[High]-1,CAST('00:00:00.0000000' AS TIME)))) AS f2(L,H)
LEFT JOIN #table AS t
ON t.tm BETWEEN f2.L AND f2.H
GROUP BY f.GroupNumber, f2.L, f2.H;
Returns:
GroupNbr TimeLow TimeHigh Total
-------------------- ---------------- ---------------- -----------
1 00:00:00.0000000 01:59:00.0000000 1
2 02:00:00.0000000 03:59:00.0000000 3
3 04:00:00.0000000 05:59:00.0000000 0
4 06:00:00.0000000 07:59:00.0000000 0
5 08:00:00.0000000 09:59:00.0000000 1
6 10:00:00.0000000 11:59:00.0000000 1
7 12:00:00.0000000 13:59:00.0000000 0
8 14:00:00.0000000 15:59:00.0000000 0
9 16:00:00.0000000 17:59:00.0000000 0
10 18:00:00.0000000 19:59:00.0000000 0
11 20:00:00.0000000 21:59:00.0000000 1
12 22:00:00.0000000 23:59:00.0000000 2
Note that an inner join will eliminate the 0-count rows.
CREATE FUNCTION core.NGroupRangeAB
(
#min BIGINT, -- Group Number Lower boundary
#max BIGINT, -- Group Number Upper boundary
#groups BIGINT -- Number of groups required
)
/*****************************************************************************************
[Purpose]:
Creates an auxilliary table that allows for grouping based on a given set of rows (#rows)
and requested number of "row groups" (#groups). core.NGroupRangeAB can be thought of as a
set-based, T-SQL version of Oracle's WIDTH_BUCKET, which:
"...lets you construct equiwidth histograms, in which the histogram range is divided into
intervals that have identical size. (Compare with NTILE, which creates equiheight
histograms.)" https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions214.htm
See usage examples for more details.
[Author]:
Alan Burstein
[Compatibility]:
SQL Server 2008+
[Syntax]:
--===== Autonomous
SELECT ng.*
FROM dbo.NGroupRangeAB(#rows,#groups) AS ng;
[Parameters]:
#rows = BIGINT; the number of rows to be "tiled" (have group number assigned to it)
#groups = BIGINT; requested number of tile groups (same as the parameter passed to NTILE)
[Returns]:
Inline Table Valued Function returns:
GroupNumber = BIGINT; a row number beginning with 1 and ending with #rows
Members = BIGINT; Number of possible distinct members in the group
Low = BIGINT; the lower-bound range
High = BIGINT; the Upper-bound range
[Dependencies]:
core.rangeAB (iTVF)
[Developer Notes]:
1. An inline derived tally table using a CTE or subquery WILL NOT WORK. NTally requires
a correctly indexed tally table named dbo.tally; if you have or choose to use a
permanent tally table with a different name or in a different schema make sure to
change the DDL for this function accordingly. The recomended number of rows is
1,000,000; below is the recomended DDL for dbo.tally. Note the "Beginning" and "End"
of tally code.To learn more about tally tables see:
http://www.sqlservercentral.com/articles/T-SQL/62867/
2. For best results a P.O.C. index should exists on the table that you are "tiling". For
more information about P.O.C. indexes see:
http://sqlmag.com/sql-server-2012/sql-server-2012-how-write-t-sql-window-functions-part-3
3. NGroupRangeAB is deterministic; for more about deterministic and nondeterministic functions
see https://msdn.microsoft.com/en-us/library/ms178091.aspx
[Examples]:
-----------------------------------------------------------------------------------------
--===== 1. Basic illustration of the relationship between core.NGroupRangeAB and NTILE.
-- Consider this query which assigns 3 "tile groups" to 10 rows:
DECLARE #rows BIGINT = 7, #tiles BIGINT = 3;
SELECT t.N, t.TileGroup
FROM ( SELECT r.RN, NTILE(#tiles) OVER (ORDER BY r.RN)
FROM core.rangeAB(1,#rows,1,1) AS r) AS t(N,TileGroup);
Results:
N TileGroup
--- ----------
1 1
2 1
3 1
4 2
5 2
6 3
7 3
To pivot these "equiheight histograms" into "equiwidth histograms" we could do this:
DECLARE #rows BIGINT = 7, #tiles BIGINT = 3;
SELECT TileGroup = t.TileGroup,
[Low] = MIN(t.N),
[High] = MAX(t.N),
Members = COUNT(*)
FROM ( SELECT r.RN, NTILE(#tiles) OVER (ORDER BY r.RN)
FROM core.rangeAB(1,#rows,1,1) AS r) AS t(N,TileGroup);
GROUP BY t.TileGroup;
Results:
TileGroup Low High Members
---------- ---- ----- -----------
1 1 3 3
2 4 5 2
3 6 7 2
This will return the same thing at a tiny fraction of the cost:
SELECT TileGroup = ng.GroupNumber,
[Low] = ng.[Low],
[High] = ng.[High],
Members = ng.Members
FROM core.NGroupRangeAB(1,#rows,#tiles) AS ng;
--===== 2.1. Divide 25 Rows into 3 groups
DECLARE #min BIGINT = 1, #max BIGINT = 25, #groups BIGINT = 4;
SELECT ng.GroupNumber, ng.Members, ng.low, ng.high
FROM core.NGroupRangeAB(#min,#max,#groups) AS ng;
--===== 2.2. Assign group membership to another table
DECLARE #min BIGINT = 1, #max BIGINT = 25, #groups BIGINT = 4;
SELECT
ng.GroupNumber, ng.low, ng.high, s.WidgetId, s.Price
FROM (VALUES('a',$12),('b',$22),('c',$9),('d',$2)) AS s(WidgetId,Price)
JOIN core.NGroupRangeAB(#min,#max,#groups) AS ng
ON s.Price BETWEEN ng.[Low] AND ng.[High]
ORDER BY ng.RN;
Results:
GroupNumber low high WidgetId Price
------------ ---- ----- --------- ---------------------
1 1 7 d 2.00
2 8 13 a 12.00
2 8 13 c 9.00
4 20 25 b 22.00
-----------------------------------------------------------------------------------------
[Revision History]:
Rev 00 - 20190128 - Initial Creation; Final Tuning - Alan Burstein
****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT
RN = r.RN, -- Sort Key
GroupNumber = r.N2, -- Bucket (group) number
Members = g.S-ur.N+1, -- Count of members in this group
[Low] = r.RN*g.S+rc.N+ur.N, -- Lower boundary for the group (inclusive)
[High] = r.N2*g.S+rc.N -- Upper boundary for the group (inclusive)
FROM core.rangeAB(0,#groups-1,1,0) AS r -- Range Function
CROSS APPLY (VALUES((#max-#min)/#groups,(#max-#min)%#groups)) AS g(S,U) -- Size, Underflow
CROSS APPLY (VALUES(SIGN(SIGN(r.RN-g.U)-1)+1)) AS ur(N) -- get Underflow
CROSS APPLY (VALUES(#min+r.RN-(ur.N*(r.RN-g.U)))) AS rc(N); -- Running Count
GO

T SQL Cursor Update field based on previous field

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

SQL Server While Loop Insert into different lines

How can I make it possible in SQL Developer 2012 using while loop?
If a value is higher than 1,000,000 (one million) split it into several lines.
For example if the value is 2,400,000 it will be in three lines with the following value 900,000 , 900,000 and 600,000 as shown in table 2 below.
Table 1
ID | Value
1 | 200,000
2 | 300,000
3 | 1,000,000
4 | 2,400,000
Table 2
ID | Value
1 | 200,000
2 | 300,000
3 | 1,000,000
4 | 900,0000
4 | 900,0000
4 | 600,0000
if object_id('tempdb..#test','U') is not null
drop table #test
create table #test (vals int)
insert into #test values (600000),(800000), (2400000)
declare #count int =0 -- psuedo group counter
declare #i int -- used to contain current row value
declare #prevval int -- used to step through values in table
declare #more int = 1 -- used to create infinite while
-- get the first value into #i
select top 1 #i= vals from #test order by vals
while #more = 1
begin
set #prevval = #i
select #count += 1
while (#i - 900000)>0
begin
select #i -= 900000
select #count,900000
end
select #count, #i
select top 1 #i=vals
from #test
where vals > #prevval
order by vals
if ##rowcount =0 -- no more data...
break
end

How can I replace duplicate strings with increasing order in T-SQL?

I have a single row table:
Id | Description
---------------
1 #Hello#, Its 5 am. #Hello#, Its 9 am. #Hello# its 12 pm.
I want to replace these duplicate string #Hello# with an increasing order. I need output like
Id | Description
---------------
1 #Hello#, Its 5 am. #Hello1#, Its 9 am. #Hello2# its 12 pm
Try this one,
DECLARE #V_STR NVARCHAR(1000) = (SELECT [Description] FROM [Table1])
,#V_COUNT INT = 0
,#V_TMP NVARCHAR(100) = '#Hello#'
WHILE ((CHARINDEX(#V_TMP,#V_STR)) > 0)
BEGIN
SELECT #V_STR = STUFF(#V_STR,(CHARINDEX(#V_TMP,#V_STR)),LEN(#V_TMP),'#Hello'+CAST(#V_COUNT AS NVARCHAR)+'#')
SET #V_COUNT += 1
END
SELECT #V_STR

Assign non identity sequence to field based on another column on Update

I have an SQL table in SQL Server 2008 which for this purpose includes
Patient_id (Primary), Custno (Char), Recip_Id (Int)
1 - C01731 - 1
2 - C01731 - 2
3 - C01731 - 3
4 - C01732 - 1
5 - C01732 - 2
6 - C01732 - 3
7 - C01732 - 4
8 - C01733 - 1
9 - C01733 - 2
So the stored procedure I am using to move records which would basically reassign the Custno is as follows...
ALTER PROCEDURE [dbo].[JR_SP_BatchMovePatients]
#IDs tblDeletePatients Readonly,
#CustnoTo varchar(max)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #rownum int
IF EXISTS(SELECT * FROM UsersMailingData WHERE custno = #CustnoTo)
SET #rownum = (select MAX(recip_id)+1 FROM UsersMailingData WHERE custno = #CustnoTo);
ELSE
SET #rownum = 0;
-- Insert statements for procedure here
With Summary AS (
Select UsersMailingData.Patient_id,
UsersMailingData.custno,
UsersMailingData.Recip_id,
row_number() over (order by Custno)+#rownum AS NewNum
FROM UsersMailingData
WHERE Patient_ID IN (Select Patient_id FROM #IDs))
UPDATE Summary
SET Custno = #CustnoTo, Recip_id = NewNum
END
However because I can not use row_number() in this manner I am having a hard time completing this task. So I am passing a table of Patient_id's which match the Patient_id's I want to update and I need to update the Custno field to the #CustnoTo value while reassigning the Recip_id based on the highest Recip_id in the #CustnoTo value.
So if I was moving C01732 to C01731 the results would be as follows...
1 - C01731 - 1
2 - C01731 - 2
3 - C01731 - 3
4 - C01731 - 4
5 - C01731 - 5
6 - C01731 - 6
7 - C01731 - 7
8 - C01733 - 1
9 - C01733 - 2
Any help would be appreciated.
You can try combination of below queries:
update UsersMailingData
set Custno=#CustnoTo
where Custno=#oldCustno
update u
set recip_id=r
(select recip_id,row_number() over (partition by Custno order by Patient_id) as r from UsersMailingData )u
And your Sp should be like
CREATE PROCEDURE [dbo].[JR_SP_BatchMovePatients]
#oldCustno varchar(max),
#CustnoTo varchar(max)
AS
BEGIN
SET NOCOUNT ON;
update UsersMailingData
set Custno=#CustnoTo
where Custno=#oldCustno
update u
set recip_id=r
(select recip_id,row_number() over (partition by Custno order by Patient_id) as r from UsersMailingData )u
END

Resources