Multi identifier error in trigger function - sql-server

I have a trigger function that create for inserted. I have two stockTransaction and StokItem in the code I wanted to display the changes on StockItem and I succeeded it. But now I simply change the column BuyinPrice of my Item table which is third table after updating StokItem table.
Here is my code:
ALTER TRIGGER [dbo].[StockTransactionInserted]
ON [dbo].[StockTransaction]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#IdItem int,
#IdStorage int,
#TransactionType char(1),
#Code char(1),
#Quantity decimal(18, 8),
#UnitPrice decimal(18, 8),
#Discount decimal(18, 8),
#FatAltDiscount decimal(18, 8),
#VAT decimal(18, 8)
SELECT
#IdItem = IdItem,
#IdStorage = IdStorage,
#TransactionType = TransactionType,
#Code = Code,
#Quantity = Quantity,
#UnitPrice = UnitPrice,
#Discount = Discount,
#FatAltDiscount = FatAltDiscount,
#VAT = VAT
FROM inserted
IF NOT EXISTS (SELECT * FROM StockItem WHERE IdItem = #IdItem AND IdStorage = #IdStorage)
BEGIN
INSERT INTO StockItem (IdItem, IdStorage, TOP_GIRIS_MIK, TOP_CIKIS_MIK)
VALUES (#IdItem, #IdStorage, 0, 0)
END
UPDATE StockItem
SET
TOP_GIRIS_MIK = TOP_GIRIS_MIK + CASE WHEN #Code = 'G' THEN #Quantity ELSE 0 END,
TOP_GIRIS_TUT = TOP_GIRIS_TUT + CASE WHEN #Code = 'G' THEN (#UnitPrice * (1- #Discount/100)) * #Quantity ELSE 0 END,
TOP_CIKIS_MIK = TOP_CIKIS_MIK + CASE WHEN #Code = 'C' THEN #Quantity ELSE 0 END,
TOP_CIKIS_TUT = TOP_CIKIS_TUT + CASE WHEN #Code = 'C' THEN (#UnitPrice * (1- #Discount/100)) * #Quantity ELSE 0 END,
ORT_BR_FIAT = CASE WHEN ((TOP_GIRIS_MIK - TOP_CIKIS_MIK) + #Quantity) = 0 THEN (ORT_BR_FIAT + (#UnitPrice * (1- #Discount/100))) / 2 ELSE ((TOP_GIRIS_MIK - TOP_CIKIS_MIK) * ORT_BR_FIAT + (#Quantity * (#UnitPrice * (1- #Discount/100)))) / ((TOP_GIRIS_MIK - TOP_CIKIS_MIK) + #Quantity) END,
SON_GIR_BR_FIAT = (CASE WHEN #UnitPrice=0 OR #TransactionType != 'A' THEN SON_GIR_BR_FIAT ELSE #UnitPrice * (1- #Discount/100) END),
SON_GIR_NET_FIAT = (CASE WHEN #UnitPrice=0 OR #TransactionType != 'A' THEN SON_GIR_NET_FIAT ELSE (#UnitPrice * (1- #Discount/100)) + (#UnitPrice * #VAT/100) END)
WHERE IdItem = #IdItem AND IdStorage = #IdStorage
UPDATE Item
SET BuyingPrice = StockItem.SON_GIR_BR_FIAT where item_id = #IdItem
END
Actually Update StokItem is working it is not problem but when i want to update my Item table in here
UPDATE Item
SET BuyingPrice = StockItem.SON_GIR_BR_FIAT where item_id = #IdItem
It says Multi identifier error for StockItem.SON_GIR_BR_FIAT column.
Can you please help me! Thanks!

Now, it should work!
The table Inserted can contain 0, 1 or multiple rows. So, you need to use a CURSOR with the rows affected in order to UPDATE the table StockItem and Item row by row.
ALTER TRIGGER [dbo].[StockTransactionInserted]
ON [dbo].[StockTransaction]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#IdItem int,
#IdStorage int,
#TransactionType char(1),
#Code char(1),
#Quantity decimal(18, 8),
#UnitPrice decimal(18, 8),
#Discount decimal(18, 8),
#FatAltDiscount decimal(18, 8),
#VAT decimal(18, 8)
DECLARE curStockTransaction CURSOR FOR
SELECT IdItem, IdStorage, TransactionType, Code, Quantity, UnitPrice, Discount, FatAltDiscount, VAT
FROM inserted
OPEN curStockTransaction
FETCH NEXT FROM curStockTransaction INTO #IdItem, #IdStorage, #TransactionType, #Code, #Quantity, #UnitPrice, #Discount, #FatAltDiscount, #VAT
WHILE ##FETCH_STATUS = 0
BEGIN
IF NOT EXISTS (SELECT * FROM StockItem WHERE IdItem = #IdItem AND IdStorage = #IdStorage)
BEGIN
INSERT INTO StockItem (IdItem, IdStorage, TOP_GIRIS_MIK, TOP_CIKIS_MIK)
VALUES (#IdItem, #IdStorage, 0, 0)
END
UPDATE StockItem
SET
TOP_GIRIS_MIK = TOP_GIRIS_MIK + CASE WHEN #Code = 'G' THEN #Quantity ELSE 0 END,
TOP_GIRIS_TUT = TOP_GIRIS_TUT + CASE WHEN #Code = 'G' THEN (#UnitPrice * (1- #Discount/100)) * #Quantity ELSE 0 END,
TOP_CIKIS_MIK = TOP_CIKIS_MIK + CASE WHEN #Code = 'C' THEN #Quantity ELSE 0 END,
TOP_CIKIS_TUT = TOP_CIKIS_TUT + CASE WHEN #Code = 'C' THEN (#UnitPrice * (1- #Discount/100)) * #Quantity ELSE 0 END,
ORT_BR_FIAT = CASE WHEN ((TOP_GIRIS_MIK - TOP_CIKIS_MIK) + #Quantity) = 0 THEN (ORT_BR_FIAT + (#UnitPrice * (1- #Discount/100))) / 2 ELSE ((TOP_GIRIS_MIK - TOP_CIKIS_MIK) * ORT_BR_FIAT + (#Quantity * (#UnitPrice * (1- #Discount/100)))) / ((TOP_GIRIS_MIK - TOP_CIKIS_MIK) + #Quantity) END,
SON_GIR_BR_FIAT = (CASE WHEN #UnitPrice=0 OR #TransactionType != 'A' THEN SON_GIR_BR_FIAT ELSE #UnitPrice * (1- #Discount/100) END),
SON_GIR_NET_FIAT = (CASE WHEN #UnitPrice=0 OR #TransactionType != 'A' THEN SON_GIR_NET_FIAT ELSE (#UnitPrice * (1- #Discount/100)) + (#UnitPrice * #VAT/100) END)
WHERE
IdItem = #IdItem AND IdStorage = #IdStorage
UPDATE Item
SET
BuyingPrice = StockItem.SON_GIR_BR_FIAT
FROM Item
JOIN StockItem ON (StockItem.IdItem = Item.item_id)
where
item_id = #IdItem
FETCH NEXT FROM curStockTransaction INTO #IdItem, #IdStorage, #TransactionType, #Code, #Quantity, #UnitPrice, #Discount, #FatAltDiscount, #VAT
END
CLOSE curStockTransaction
DEALLOCATE curStockTransaction
END

Related

Convert HSL color to RGB and HEX using SQL

The problem started when I needed to apply conditional formatting to a table with smooth color changes in MS SQL Server Reporting Services (SSRS). It is impossible with standard SSRS functionality. But you can use the table data to smoothly change the color with the Lightness parameter in the HSL color model.
The question is, how to convert HSL to usable in SSRS HEX or RGB color codes using SQL.
No answers were found at Stackoverflow or anywhere else, only for other programming languages
I've based the following solution on this article. As mentioned, I use 2 functions here, and I also return a dataset in both (3 columns for RGB, 1 for the hex):
CREATE OR ALTER FUNCTION dbo.HSLtoRGB (#H numeric(3,0),#S numeric(4,3), #L numeric(4,3))
RETURNS table
AS RETURN
SELECT CONVERT(tinyint,ROUND((RGB1.R1+m.m)*255,0)) AS R,
CONVERT(tinyint,ROUND((RGB1.G1+m.m)*255,0)) AS G,
CONVERT(tinyint,ROUND((RGB1.B1+m.m)*255,0)) AS B
FROM (VALUES(#H, #S, #L))HSL(Hue,Saturation,Lightness)
CROSS APPLY(VALUES((1-ABS((2*HSL.Lightness - 1))) * HSL.Saturation)) C(Chroma)
CROSS APPLY(VALUES(HSL.Hue/60,C.Chroma * (1 - ABS((HSL.Hue/60) % 2 - 1))))H([H`],X)
CROSS APPLY(SELECT TOP (1) * --It's unlikely there would be 2 rows, but just incase limit to 1
FROM (VALUES(C.Chroma,H.X,0,0,1),
(H.X,C.Chroma,0,1,2),
(0,C.Chroma,H.X,2,3),
(0,H.X,C.Chroma,3,4),
(H.X,0,C.Chroma,4,5),
(C.Chroma,0,H.X,5,6))V(R1,G1,B1,S,E)
WHERE V.S <= H.[H`] AND H.[H`] <= V.E
ORDER BY V.E DESC) RGB1
CROSS APPLY (VALUES(HSL.Lightness - (C.Chroma / 2)))m(m);
GO
CREATE OR ALTER FUNCTION dbo.HSLtoRGB_HEX (#H numeric(3,0),#S numeric(4,3), #L numeric(4,3))
RETURNS table
AS RETURN
SELECT CONVERT(binary(3),CONCAT(CONVERT(varchar(2),CONVERT(binary(1),CONVERT(tinyint,ROUND((RGB1.R1+m.m)*255,0))),2),
CONVERT(varchar(2),CONVERT(binary(1),CONVERT(tinyint,ROUND((RGB1.G1+m.m)*255,0))),2),
CONVERT(varchar(2),CONVERT(binary(1),CONVERT(tinyint,ROUND((RGB1.B1+m.m)*255,0))),2)),2) AS RGB
FROM (VALUES(#H, #S, #L))HSL(Hue,Saturation,Lightness)
CROSS APPLY(VALUES((1-ABS((2*HSL.Lightness - 1))) * HSL.Saturation)) C(Chroma)
CROSS APPLY(VALUES(HSL.Hue/60,C.Chroma * (1 - ABS((HSL.Hue/60) % 2 - 1))))H([H`],X)
CROSS APPLY(SELECT TOP(1) * --It's unlikely there would be 2 rows, but just incase limit to 1
FROM (VALUES(C.Chroma,H.X,0,0,1),
(H.X,C.Chroma,0,1,2),
(0,C.Chroma,H.X,2,3),
(0,H.X,C.Chroma,3,4),
(H.X,0,C.Chroma,4,5),
(C.Chroma,0,H.X,5,6))V(R1,G1,B1,S,E)
WHERE V.S <= H.[H`] AND H.[H`] <= V.E
ORDER BY V.E DESC) RGB1
CROSS APPLY (VALUES(HSL.Lightness - (C.Chroma / 2)))m(m);
GO
SELECT *
FROM (VALUES(210,.79,.3),
(24,.83,.74),
(360,1,1),
(0,0,0))V(H,S,L)
CROSS APPLY dbo.HSLtoRGB(V.H, V.S, V.L) RGB
CROSS APPLY dbo.HSLtoRGB_Hex(V.H, V.S, V.L) RGBhex;
After all I came to rewriting the VBA function from here to SQL
It has the following parameters:
#HueDegree [0,360]
#Saturation [0,1]
#Lightness [0,1]
#Format ('RGB' or 'HEX')
Result is a color code in chosen format
create function dbo.f_convertHSL (
#HueDegree numeric(3,0),
#Saturation numeric(6,3),
#Lightness numeric(6,3),
#Format varchar(3) )
returns varchar(100)
as
begin
declare #HuePercent numeric(6,3),
#Red numeric(6,3),
#Green numeric(6,3),
#Blue numeric(6,3),
#Temp1 numeric(6,3),
#Temp2 numeric(6,3),
#TempR numeric(6,3),
#TempG numeric(6,3),
#TempB numeric(6,3),
#Result varchar(100);
if #Saturation = 0
begin
select #Red = #Lightness * 255,
#Green = #Lightness * 255,
#Blue = #Lightness * 255;
if #Format = 'RGB'
select #Result = cast(cast(#Red as int) as varchar) + ', '
+ cast(cast(#Green as int) as varchar) + ', '
+ cast(cast(#Blue as int) as varchar);
else if #Format = 'HEX'
select #Result = '#' + convert(varchar(2), convert(varbinary(1), cast(#Red as int)), 2)
+ convert(varchar(2), convert(varbinary(1), cast(#Green as int)), 2)
+ convert(varchar(2), convert(varbinary(1), cast(#Blue as int)), 2);
else select #Result = 'Format should be RGB or HEX';
return #Result;
end;
if #Lightness < 0.5
select #Temp1 = #Lightness * (1 + #Saturation);
else
select #Temp1 = #Lightness + #Saturation - #Lightness * #Saturation;
select #Temp2 = 2 * #Lightness - #Temp1
, #HuePercent = #HueDegree / 360.0;
select #TempR = #HuePercent + 0.333
, #TempG = #HuePercent
, #TempB = #HuePercent - 0.333;
if #TempR < 0 select #TempR = #TempR + 1;
if #TempR > 1 select #TempR = #TempR - 1;
if #TempG < 0 select #TempG = #TempG + 1;
if #TempG > 1 select #TempG = #TempG - 1;
if #TempB < 0 select #TempB = #TempB + 1;
if #TempB > 1 select #TempB = #TempB - 1;
if #TempR * 6 < 1 select #Red = #Temp2 + (#Temp1 - #Temp2) * 6 * #TempR
else if #TempR * 2 < 1 select #Red = #Temp1
else if #TempR * 3 < 2 select #Red = #Temp2 + (#Temp1 - #Temp2) * (0.666 - #TempR) * 6
else select #Red = #Temp2;
if #TempG * 6 < 1 select #Green = #Temp2 + (#Temp1 - #Temp2) * 6 * #TempG
else if #TempG * 2 < 1 select #Green = #Temp1
else if #TempG * 3 < 2 select #Green = #Temp2 + (#Temp1 - #Temp2) * (0.666 - #TempG) * 6
else select #Green = #Temp2;
if #TempB * 6 < 1 select #Blue = #Temp2 + (#Temp1 - #Temp2) * 6 * #TempB
else if #TempB * 2 < 1 select #Blue = #Temp1
else if #TempB * 3 < 2 select #Blue = #Temp2 + (#Temp1 - #Temp2) * (0.666 - #TempB) * 6
else select #Blue = #Temp2;
select #Red = round(#Red * 255, 0),
#Green = round(#Green * 255, 0),
#Blue = round(#Blue * 255, 0);
if #Format = 'RGB'
select #Result = cast(cast(#Red as int) as varchar) + ', '
+ cast(cast(#Green as int) as varchar) + ', '
+ cast(cast(#Blue as int) as varchar);
else if #Format = 'HEX'
select #Result = '#' + convert(varchar(2), convert(varbinary(1), cast(#Red as int)), 2)
+ convert(varchar(2), convert(varbinary(1), cast(#Green as int)), 2)
+ convert(varchar(2), convert(varbinary(1), cast(#Blue as int)), 2);
else select #Result = 'Format should be RGB or HEX';
return #Result;
end;
Usage examples:
select dbo.f_convertHSL(24, 0.83, 0.74, 'RGB')
result: 244, 178, 134
select dbo.f_convertHSL(24, 0.83, 0.74, 'HEX')
result: #F4B286
Result can be confirmed for example here

How to update the column without loop in SQL Server?

Due to performance perspective I just need to remove loop and using some joins or other solution to update the data in #Result table and get the same result which return by loop.
Scalar function:
CREATE FUNCTION [MultiplyerScl]
(#a INT, #b INT)
RETURNS INT
AS
BEGIN
DECLARE #i AS INT = 2
DECLARE #Value AS INT
IF (#b % #a = 0)
BEGIN
SET #Value = #b
END
ELSE
BEGIN
WHILE (1=1)
BEGIN
IF((#b * #i) % #a = 0)
BEGIN
SET #Value = #b * #i
BREAK;
END
ELSE
BEGIN
SET #i = #i + 1
END
END
END
RETURN #Value
END
Table design and its value.
CREATE TABLE #NUM (Groupid INT, GroupValue INT)
INSERT INTO #NUM
VALUES (1, 8), (1, 9), (1, 23), (2, 5), (2, 5), (2, 10)
Main for loop logic.
SELECT
Groupid,
GroupValue,
MaxValue = MAX(GroupValue) OVER (PARTITION BY Groupid),
MaxCount = COUNT(1) OVER(),
RID = ROW_NUMBER() OVER (ORDER BY groupid)
INTO
#Result
FROM
#NUM
DECLARE #i AS INT = 1
DECLARE #RawCnt AS INT = (SELECT MAX(MaxCount) FROM #Result)
DECLARE #iGroupid INT
DECLARE #iGroupvalue INT
DECLARE #iMaxValue INT
WHILE(#i <= #RawCnt)
BEGIN
SELECT
#iGroupid = Groupid,
#iGroupvalue = Groupvalue,
#iMaxValue = MaxValue
FROM
#Result
WHERE
RID = #i
UPDATE #Result
SET MaxValue = dbo.[MultiplyerScl](#iGroupvalue, #iMaxValue)
WHERE Groupid = #iGroupid
SET #i = #i + 1
END
SELECT * FROM #Result
Try this out
SELECT
Groupid,
GroupValue,
MaxValue = MAX(GroupValue) OVER (PARTITION BY Groupid),
MaxCount = COUNT(1) OVER(),
RID = ROW_NUMBER() OVER (ORDER BY groupid)
INTO
#Result
FROM
#NUM
;WITH Res AS
(
SELECT Groupid, e.Groupvalue, dbo.[MultiplyerScl](Groupvalue, e.MaxValue) AS
MaxValue, 1 AS i
FROM #Result e
UNION ALL
--recursive execution
SELECT e.Groupid, m.Groupvalue, dbo.[MultiplyerScl](e.Groupvalue, m.MaxValue) AS MaxValue, m.i + 1 AS i
FROM #Result e
INNER JOIN Res m ON e.Groupid = m.Groupid
WHERE dbo.[MultiplyerScl](e.Groupvalue, m.MaxValue) > m.MaxValue
)
SELECT Groupid, MAX(MaxValue) AS MaxValue
INTO #FinalResult
FROM Res
GROUP BY Groupid
UPDATE re
SET re.MaxValue = ire.MaxValue
FROM #FinalResult ire
INNER JOIN #Result re ON re.Groupid = ire.Groupid
SELECT * FROM #Result

Split column value using delimiter and insert into different table

I have one table with only one column. That one column contains strings with comma-delimited values. I am trying to select each string from the table > split the string by the comma's > and then insert the results into a different table.
My first table look like this:
DataString
abc,def,gh,i,jkl
mnop,qr,stu,v,wxyz
I would like the data to look like this:
Value1 Value2 Value3 Value4 Value5
abc def gh i jkl
mnop qr stu v wxyz
This is the code I have so far:
DECLARE #valueList varchar(100)
DECLARE #pos INT
DECLARE #len INT
DECLARE #value varchar(100)
Select #valueList = DataString from [dbo].[rawdata]
set #pos = 0
set #len = 0
WHILE CHARINDEX(',', #valueList, #pos+1)>0
BEGIN
set #len = CHARINDEX(',', #valueList, #pos+1) - #pos
set #value = SUBSTRING(#valueList, #pos, #len)
insert into [dbo].[tempTable] (Value1) Values (#value)
set #pos = CHARINDEX(',', #valueList, #pos+#len) +1
END
I am having two problems with this code.
Problem 1:
- my select statement to pull in the column DataString into the variable #valueList is only pulling the last row in the table instead of selecting all of them.
Problem 2:
- my insert into statement can only insert the values as new rows into the table. I cant figure out how to enter the values into their corresponding columns.
Any help on how to fix either of my problems would be greatly appreciated.
So your first issue is that assigning a variable to a column like that in a table is only going to pull one row from that column in table. If you want to loop through each row in a column like you're currently doing, you would need to use a CURSOR or some variant like that to go through each row.
The second issue you have is that your while statement is incorrect. You're missing the last value from it because you stop when there are no more commas in your datastring. There are no more commas at the last value, so it skips the last value. There's a way around this using your current method, but I would recommend an alternative method of splitting your string.
DECLARE #myTable TABLE (datastring VARCHAR(4000));
INSERT #myTable(datastring)
VALUES ('abc,def,gh,i,jkl'),('mnop,qr,stu,v,wxyz');
DECLARE #valueList VARCHAR(4000) = '', #i INT = 1, #pos INT = 0, #len INT = 0, #value VARCHAR(100) = '';
IF OBJECT_ID('tempTable', 'U') IS NOT NULL DROP TABLE tempTable;
CREATE TABLE tempTable (OriginalDataString VARCHAR(4000), stringVal VARCHAR(255), valNum INT);
DECLARE curs CURSOR FOR
SELECT datastring
FROM #myTable;
OPEN curs;
FETCH NEXT FROM curs INTO #valueList;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #pos = 0, #len = 0, #i = 1;
WHILE 1 = 1
BEGIN
SET #len = ISNULL(NULLIF(CHARINDEX(',', #valueList, #pos+1), 0) - #pos, LEN(#valueList));
SET #value = SUBSTRING(#valueList, #pos, #len);
INSERT tempTable(OriginalDataString, stringVal, valNum)
VALUES (#valueList, #value, #i);
IF CHARINDEX(',', #valueList, #pos+1) = 0
BREAK;
SET #pos = CHARINDEX(',', #valueList, #pos+#len) + 1;
SET #i += 1;
END
FETCH NEXT FROM curs INTO #valueList;
END
CLOSE curs;
DEALLOCATE curs;
SELECT MAX(CASE WHEN valNum = 1 THEN stringVal END) val1
, MAX(CASE WHEN valNum = 2 THEN stringVal END) val2
, MAX(CASE WHEN valNum = 3 THEN stringVal END) val3
, MAX(CASE WHEN valNum = 4 THEN stringVal END) val4
, MAX(CASE WHEN valNum = 5 THEN stringVal END) val5
FROM tempTable
GROUP BY OriginalDataString;
This uses a cursor to get each datastring in your table, puts each value in a cursor, loops through them to get the value (the loop breaks when you reach the end of the string) and then selects val1, val2, val3, val4, val5 from the resulting table.
But, rather than using a cursor and a while loop, I would recommend the much simpler use of a recursive CTE (or even better, a split function you already have built in).
For example,
DECLARE #myTable TABLE (datastring VARCHAR(4000));
INSERT #myTable(datastring)
VALUES ('abc,def,gh,i,jkl'),('mnop,qr,stu,v,wxyz');
WITH CTE AS (
SELECT datastring
, SUBSTRING(datastring, 1, ISNULL(NULLIF(CHARINDEX(',', datastring), 0) - 1, LEN(datastring))) sString
, NULLIF(CHARINDEX(',', datastring), 0) cIndex
, 1 Lvl
FROM #myTable T
UNION ALL
SELECT datastring
, SUBSTRING(datastring, cIndex + 1, ISNULL(NULLIF(CHARINDEX(',', datastring, cIndex + 1), 0) - 1 - cIndex, LEN(datastring)))
, NULLIF(CHARINDEX(',', datastring, cIndex + 1), 0)
, Lvl + 1
FROM CTE
WHERE cIndex IS NOT NULL)
SELECT MAX(CASE WHEN Lvl = 1 THEN sString END) val1
, MAX(CASE WHEN Lvl = 2 THEN sString END) val2
, MAX(CASE WHEN Lvl = 3 THEN sString END) val3
, MAX(CASE WHEN Lvl = 4 THEN sString END) val4
, MAX(CASE WHEN Lvl = 5 THEN sString END) val5
--, datastring OriginalDataString
FROM CTE
GROUP BY datastring;
Declare #YourTable table (DataString varchar(250))
Insert Into #YourTable values
('abc,def,gh,i,jkl'),
('mnop,qr,stu,v,wxyz')
Select A.*
,Value1=B.Pos1
,Value2=B.Pos2
,Value3=B.Pos3
,Value4=B.Pos4
,Value5=B.Pos5
From #YourTable A
Cross Apply (Select * from [dbo].[udf-Str-Parse-Row](A.DataString,',')) B
Returns
DataString Value1 Value2 Value3 Value4 Value5
abc,def,gh,i,jkl abc def gh i jkl
mnop,qr,stu,v,wxyz mnop qr stu v wxyz
Then UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
-- Select * from [dbo].[udf-Str-Parse-Row]('id26,id46|id658,id967','|')
Returns Table
As
Return (
SELECT Pos1 = xDim.value('/x[1]','varchar(250)')
,Pos2 = xDim.value('/x[2]','varchar(250)')
,Pos3 = xDim.value('/x[3]','varchar(250)')
,Pos4 = xDim.value('/x[4]','varchar(250)')
,Pos5 = xDim.value('/x[5]','varchar(250)')
,Pos6 = xDim.value('/x[6]','varchar(250)')
,Pos7 = xDim.value('/x[7]','varchar(250)')
,Pos8 = xDim.value('/x[8]','varchar(250)')
,Pos9 = xDim.value('/x[9]','varchar(250)')
FROM (Select Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML) as xDim) A
)

Improve or replace a complex, slow SQL cursor with set statements

I have a cursor that uses two tables. The logic behind it is for travelling routes for a business application. Lets say you have four destinations - City A, City B, City C and City D. Sometimes you drive from A to B and back again. But other times you drive from A to B to C to D and then back to A - this would be called a Rounder.
In the business application out and back (back and forth from A to B) is priced differently then if you did a Rounder. In one table there are the routes - A to B , C to D etc. In the other table there is the costing estimates. If they just went A to B - use this price, for a Rounder use this price.
Call it a Route table, with the routes driven, and a Costing table, with the prices for all the routes and rounders.
I need to loop through all the trips and look to see if there is a matching '2nd half' of a Rounder. They aren't necessarily in order - they might go C to D then A to B - but this still counts as a Rounder. Or they might go A to B, X to Y, then C to D. The A to B and the subsequent C to D count as a Rounder.
The way I do is to start with the first row in the Route table and loop through the table see if any of the rows match with the 2nd half of a Rounder. If there is no match then, it is just a simple out and back. I use a cursor to do this - and it is SLOW - but I can't think of any way to turn this into a set based SQL statement.
Here's my code if you are up for a re-write to remove a cursor challenge.....
create table #RouteLegs (
RowNbr int,
RouteID varchar(20) INDEX IDX_RouteID NONCLUSTERED,
TripDate datetime INDEX IDX_TripDate CLUSTERED,
WaybillNbr varchar(16) INDEX IDX_WaybillNbr NONCLUSTERED,
MatchedRowNbr int,
CostingModelID int,
Legs varchar(5),
RounderCode varchar(100)
)
insert into #RouteLegs
select ROW_NUMBER() over (order by actual.TripDate) as RowNbr,
actual.RouteID,
cast(actual.TripDate as date) as trip_date,
actual.Waybill_Number,
0, 0, '', ''
from staging.RouteSoureTable
declare #NbrMatches int,
#CurrRouteID varchar(20),
#CurrRowNbr int,
#CurrTripDate datetime,
#CurrWaybillNbr varchar(16),
#MatchRouteID varchar(20),
#MatchRowNbr int,
#MatchTripDate datetime,
#CostingModelID int,
#MatchFound int = 0,
#FrontLegs varchar(10),
#BackLegs varchar(10)
declare csrRouteLeg cursor for
select RouteID, RowNbr, TripDate, WaybillNbr
from #RouteLegs
order by RowNbr
open csrRouteLeg
fetch next from csrRouteLeg
into #CurrRouteID, #CurrRowNbr, #CurrTripDate, #CurrWaybillNbr
while ##FETCH_STATUS = 0
begin
-- print 'begin matching' + cast(#CurrRowNbr as varchar(10))
--reset flag and start looking
set #MatchFound = 0
--check if already matched to another route leg
select #MatchFound = MatchedRowNbr from #RouteLegs where RowNbr = #CurrRowNbr
if #MatchFound = 0
begin
if #MatchFound = 0
begin
--otherwise find the correct costing model baased on matching backhauls
declare csrFindMatch cursor for
select RouteID, RowNbr, TripDate
from #RouteLegs
where RowNbr <> #CurrRowNbr
and MatchedRowNbr = 0
order by RowNbr
-- print 'new matching recordset'
open csrFindMatch
fetch next from csrFindMatch
into #MatchRouteID, #MatchRowNbr, #MatchTripDate
while ##FETCH_STATUS = 0
begin
--this is a front haul with a matching backhaul
--print 'DATEDIFF ' + CAST( ABS(DATEDIFF(D,#CurrTripDate,#MatchTripDate)) AS VARCHAR(10)) + ' row ' + cast(#MatchRowNbr as varchar(20))
if #MatchFound = 0 AND ABS(DATEDIFF(D,#CurrTripDate,#MatchTripDate)) < #DayGapBetweenBackHaul
begin
select #MatchFound = coalesce(max(CostingModelID), 0)
from xref_Costing_withBH xr
inner join DimCosting dim
on xr.CostingModelID = dim.CostingModelCode
where FirstLegRouteCode = #CurrRouteID and BackLegRouteCode = #MatchRouteID
--and #CurrTripDate between dim.CostingModelEffectiveDate and dim.CostingModelExpiryDate
select #FrontLegs = max([Costing Model Leg]), #BackLegs = max([BH Legs]) from [staging].[operations_file_CostingModelRouteLinkage] where [Costing Model #] = #MatchFound
if #MatchFound > 0
begin
--print 'Front - ' + cast(#CurrRouteID as varchar(20)) + ' Back - ' + cast( #MatchRouteID as varchar(20)) + ' Row - ' + cast(#CurrRowNbr as varchar(20)) + ' front haul with a matching backhaul'
set #RounderCode = 'Backhaul-' + ltrim(rtrim(cast(#AssetCode as varchar(20)))) + '-' + cast(#MatchFound as varchar(20)) + '-' + cast(#BatchID as varchar(20))+ '-' + cast(#CurrRowNbr as varchar(20))
update #RouteLegs
set MatchedRowNbr = #MatchRowNbr,
Legs = #FrontLegs,
CostingModelID = #MatchFound,
RounderCode = #RounderCode
where RowNbr = #CurrRowNbr
update #RouteLegs
set MatchedRowNbr = #CurrRowNbr,
Legs = #BackLegs,
CostingModelID = #MatchFound,
RounderCode = #RounderCode
where RowNbr = #MatchRowNbr
-- select * from #RouteLegs
--print 'front haul updated'
end
else
--this could be a back haul with a matching front haul - but first check this row hasn't been matched in a previous loop
select #MatchFound = COALESCE(MatchedRowNbr, 0) from #RouteLegs where RowNbr = #CurrRowNbr
if #MatchFound = 0
begin
select #MatchFound = coalesce(max(CostingModelID) , 0)
from xref_Costing_withBH xr
inner join DimCosting dim
on xr.CostingModelID = dim.CostingModelCode
where FirstLegRouteCode = #MatchRouteID and BackLegRouteCode = #CurrRouteID
--and #CurrTripDate between dim.CostingModelEffectiveDate and dim.CostingModelExpiryDate
select #FrontLegs = max([Costing Model Leg]), #BackLegs = max([BH Legs]) from [staging].[operations_file_CostingModelRouteLinkage] where [Costing Model #] = #MatchFound
if #MatchFound > 0 AND ABS(DATEDIFF(D,#CurrTripDate,#MatchTripDate)) < #DayGapBetweenBackHaul
begin
--print 'Front - ' + cast(#MatchRouteID as varchar(20)) + ' Back - ' + cast( #CurrRouteID as varchar(20)) + ' Row - ' + cast(#CurrRowNbr as varchar(20)) + ' this is a back haul with a matching front haul'
set #RounderCode = 'Backhaul-' + ltrim(rtrim(cast(#AssetCode as varchar(20)))) + '-' + cast(#MatchFound as varchar(20)) + '-' + cast(#BatchID as varchar(20))+ '-' + cast(#CurrRowNbr as varchar(20))
update #RouteLegs
set MatchedRowNbr = #MatchRowNbr,
Legs = #BackLegs,
CostingModelID = #MatchFound,
RounderCode = #RounderCode
where RowNbr = #CurrRowNbr
update #RouteLegs
set MatchedRowNbr = #CurrRowNbr,
Legs = #FrontLegs,
CostingModelID = #MatchFound,
RounderCode = #RounderCode
where RowNbr = #MatchRowNbr
--select * from #RouteLegs
--print 'back haul updated'
end
end
end
fetch next from csrFindMatch
into #MatchRouteID, #MatchRowNbr, #MatchTripDate
end
close csrFindMatch
deallocate csrFindMatch
end
fetch next from csrRouteLeg
into #CurrRouteID, #CurrRowNbr, #CurrTripDate, #CurrWaybillNbr
end
fetch next from csrRouteLeg
into #CurrRouteID, #CurrRowNbr, #CurrTripDate, #CurrWaybillNbr
end
close csrRouteLeg
deallocate csrRouteLeg

Change temporary table to table varible using sqlserver 2012

I want to remove the #temp1 from my query and use it as another table varible that work same as it is working using #temp1 table.
Here is my query
DROP TABLE #temp1
;WITH cteSales AS(
SELECT BillDate,
BillNo,
'-' AS NarrationNumber,
TotalAmount,
0 AS ReceivedAmount
FROM SalesMaster
WHERE SalesMaster.ClientID = #ClientID
AND SalesMaster.BillDate BETWEEN #FromDate AND #ToDate
UNION ALL
SELECT [Date] AS BillDate,
SalesAccount.BillNo,
SalesAccount.NarrationNumber,
0 AS TotalAmount,
Amount AS ReceivedAmount
FROM SalesAccount
LEFT JOIN SalesMaster sm
ON sm.BillNo = SalesAccount.BillNO
WHERE sm.ClientID = #ClientID
AND sm.BillDate BETWEEN #FromDate AND #ToDate
), cteFormattedSales AS(
SELECT ROW_NUMBER() OVER(ORDER BY BillNo ASC, BillDate ASC) AS RowNum,
*
FROM cteSales
)
SELECT cfs1.RowNum,
cfs1.BillDate,
cfs1.BillNo,
cfs1.NarrationNumber,
cfs1.TotalAmount,
cfs1.ReceivedAmount,
(cfs1.TotalAmount - cfs1.ReceivedAmount) AS DueAmount INTO #temp1
FROM cteFormattedSales cfs1
DECLARE #BillDate date,
#PrevBillNo INT,
#NextBillNo INT,
#NarrationNumber VARCHAR(15),
#TotalAmount DECIMAL(18, 2),
#ReceivedAmount DECIMAL(18, 2),
#NextDue DECIMAL(18, 2),
#PrevDue DECIMAL(18, 2)
DECLARE #finalTable TABLE(
BillDate date,
BillNo INT,
NarrationNumber NVARCHAR(15),
TotalAmount DECIMAL(18, 2),
ReceivedAmount DECIMAL(18, 2),
DueAmount DECIMAL(18, 2)
)
DECLARE #TotalRecords INT,
#Counter INT = 1,
#CarryOverAmount DECIMAL(18, 2)
SELECT #TotalRecords = COUNT(*)
FROM #temp1
SELECT #BillDate = t.BillDate,
#PrevBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#PrevDue = t.DueAmount
FROM #temp1 t
WHERE t.RowNum = #Counter
WHILE #Counter <= #TotalRecords
BEGIN
SELECT #BillDate = t.BillDate,
#NextBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#NextDue = t.DueAmount
FROM #temp1 AS t
WHERE t.RowNum = #Counter
IF (#Counter = 1)
SET #CarryOverAmount = #TotalAmount
SET #PrevDue = #CarryOverAmount - #ReceivedAmount
IF #PrevBillNo <> #NextBillNo
BEGIN
SET #PrevBillNo = #NextBillNo
SET #CarryOverAmount = #TotalAmount + #PrevDue
SET #PrevDue = 0
END
INSERT INTO #finalTable
(
BillDate,
BillNo,
NarrationNumber,
TotalAmount,
ReceivedAmount,
DueAmount
)
VALUES
(
#BillDate,
#PrevBillNo,
#NarrationNumber,
#TotalAmount,
#ReceivedAmount,
CASE
WHEN #NarrationNumber = '-' THEN #CarryOverAmount
ELSE #PrevDue
END
)
IF #PrevBillNo = #NextBillNo
BEGIN
IF #Counter > 1
BEGIN
SET #CarryOverAmount = CASE
WHEN #NarrationNumber = '-' THEN #CarryOverAmount
+ #PrevDue
ELSE #CarryOverAmount - #ReceivedAmount
END
END
END
SET #Counter = #Counter + 1
END
SELECT *
FROM #finalTable
Here the code that i get error on invalid ReceivedAmount
SELECT #BillDate = t.BillDate,
#PrevBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#PrevDue = t.DueAmount
FROM #Temp1 t
WHERE t.RowNum = #Counter
WHILE #Counter <= #TotalRecords
BEGIN
SELECT #BillDate = t.BillDate,
#NextBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#NextDue = t.DueAmount
FROM #Temp1 AS t
WHERE t.RowNum = #Counter
You need to do just 3 steps:
A. DECLARE table variable
-- correct types of columns
DECLARE #temp1 TABLE(
RowNum int
,BillDate datetime
,BillNo int
,NarrationNumber int
,TotalAmount money
,RecievedAmount money
,DueAmount money
)
B. Rewrite SELECT ... INTO -> INSERT INTO .. SELECT
INSERT INTO #temp1
SELECT cfs1.RowNum,
cfs1.BillDate,
cfs1.BillNo,
cfs1.NarrationNumber,
cfs1.TotalAmount,
cfs1.ReceivedAmount,
(cfs1.TotalAmount - cfs1.ReceivedAmount) AS DueAmount
FROM cteFormattedSales cfs1
... etc ...
C. Replace all #temp1 to #temp1

Resources