patternsqlMinor
Function To Perform Calculations Based Off Variable
Viewed 0 times
functioncalculationsperformbasedvariableoff
Problem
I saw an answer by @ypercubeᵀᴹ that gave a great answer on how to calculate what appeared to be a quantity ordered by 3 and give whole number answers. Which that got me thinking, how would you handle a situation if there was a variable number of times to split?
Meaning this was the OP where you would always split by 3 (Original Poster
dbfiddle here
However, what if instead of always splitting by 3, you had an int variable that stated how many times to split, say same DDL but instead of splitting by 3 you use
And now you split each scenario 5 ways instead of 3. Basically a re-usable function that can "on-the-fly" split based off a variable?
Meaning this was the OP where you would always split by 3 (Original Poster
Create Table #Orders
(
id int IDENTITY(1,1) PRIMARY KEY NOT NULL
,partid varchar(100) NOT NULL
,qtyordered int DEFAULT '0'
,orderedby varchar(100) NOT NULL
,ordereddate date DEFAULT GETDATE()
) ;
Insert Into #Orders (partid, qtyordered, orderedby) VALUES
('SS100', 10, 'James'), ('RR200', 5, 'Bob'), ('NN300', 3, 'Jake'), ('OO400', 5, 'Blue') ;
SELECT
partid,
qtyordered,
[First],
[Second],
[Third]
FROM
#Orders
CROSS APPLY
( SELECT [Third] = (qtyordered) / 3 ) AS q3
CROSS APPLY
( SELECT [Second] = (qtyordered - [Third]) / 2 ) AS q2
CROSS APPLY
( SELECT [First] = (qtyordered - [Third] - [Second]) / 1 ) AS q1;dbfiddle here
However, what if instead of always splitting by 3, you had an int variable that stated how many times to split, say same DDL but instead of splitting by 3 you use
Declare @TTS int = 5And now you split each scenario 5 ways instead of 3. Basically a re-usable function that can "on-the-fly" split based off a variable?
Solution
Basically, we're just looking to spread the remainder from the division out over the values. This procedure does exactly that; it will return a table with the result vals from the split. I assume we do not want to return a variable number of columns (depending on the divisors we receive), and I decided that actually generating the text "First", "Second", etc. wasn't really a part of the question, so I'm returning each value in its own row.
The following:
Yields these results:
Alternately, you could make this an inline table-valued function:
You run the same queries as above thusly:
Results are the same. To answer one comment: this would allow you to pull
EDIT: Changed function name, so function and procedure can co-exist (if you'd want them to, for some reason).
CREATE PROCEDURE split_into_ints(@dividend int, @divisor int)
AS
BEGIN
WITH result_rows AS
(SELECT TOP (@divisor) ROW_NUMBER() OVER (ORDER BY o1.object_id)
as result_num
FROM sys.objects o1 CROSS JOIN sys.objects o2
)
,base_vals AS
(SELECT @dividend / @divisor as base_result
,@dividend % @divisor as remainder
)
SELECT r.result_num
,CASE WHEN r.result_num <= remainder THEN 1 ELSE 0 END
+ v.base_result as result_val
FROM base_vals v CROSS JOIN result_rows r
ORDER BY result_num
END;
GOThe following:
EXECUTE split_into_ints 10,3;
EXECUTE split_into_ints 49,6;
EXECUTE split_into_ints 2000,11;Yields these results:
result_num result_val
-------------------- -----------
1 4
2 3
3 3
result_num result_val
-------------------- -----------
1 9
2 8
3 8
4 8
5 8
6 8
result_num result_val
-------------------- -----------
1 182
2 182
3 182
4 182
5 182
6 182
7 182
8 182
9 182
10 181
11 181Alternately, you could make this an inline table-valued function:
CREATE FUNCTION fn_split_into_ints(@dividend int, @divisor int)
RETURNS TABLE
AS
RETURN
(
WITH result_rows AS
(SELECT TOP (@divisor) ROW_NUMBER() OVER (ORDER BY o1.object_id)
as result_num
FROM sys.objects o1 CROSS JOIN sys.objects o2
)
,base_vals AS
(SELECT @dividend / @divisor as base_result
,@dividend % @divisor as remainder
)
SELECT r.result_num
,CASE WHEN r.result_num <= remainder THEN 1 ELSE 0 END
+ v.base_result as result_val
FROM base_vals v CROSS JOIN result_rows r
);
GOYou run the same queries as above thusly:
SELECT * FROM fn_split_into_ints(10,3);
SELECT * FROM fn_split_into_ints(49,6);
SELECT * FROM fn_split_into_ints(2000,11);Results are the same. To answer one comment: this would allow you to pull
result_val into a variable:SELECT @result_val = result_val
FROM fn_split_into_ints(49,6)
WHERE result_num = 6
;EDIT: Changed function name, so function and procedure can co-exist (if you'd want them to, for some reason).
Code Snippets
CREATE PROCEDURE split_into_ints(@dividend int, @divisor int)
AS
BEGIN
WITH result_rows AS
(SELECT TOP (@divisor) ROW_NUMBER() OVER (ORDER BY o1.object_id)
as result_num
FROM sys.objects o1 CROSS JOIN sys.objects o2
)
,base_vals AS
(SELECT @dividend / @divisor as base_result
,@dividend % @divisor as remainder
)
SELECT r.result_num
,CASE WHEN r.result_num <= remainder THEN 1 ELSE 0 END
+ v.base_result as result_val
FROM base_vals v CROSS JOIN result_rows r
ORDER BY result_num
END;
GOEXECUTE split_into_ints 10,3;
EXECUTE split_into_ints 49,6;
EXECUTE split_into_ints 2000,11;result_num result_val
-------------------- -----------
1 4
2 3
3 3
result_num result_val
-------------------- -----------
1 9
2 8
3 8
4 8
5 8
6 8
result_num result_val
-------------------- -----------
1 182
2 182
3 182
4 182
5 182
6 182
7 182
8 182
9 182
10 181
11 181CREATE FUNCTION fn_split_into_ints(@dividend int, @divisor int)
RETURNS TABLE
AS
RETURN
(
WITH result_rows AS
(SELECT TOP (@divisor) ROW_NUMBER() OVER (ORDER BY o1.object_id)
as result_num
FROM sys.objects o1 CROSS JOIN sys.objects o2
)
,base_vals AS
(SELECT @dividend / @divisor as base_result
,@dividend % @divisor as remainder
)
SELECT r.result_num
,CASE WHEN r.result_num <= remainder THEN 1 ELSE 0 END
+ v.base_result as result_val
FROM base_vals v CROSS JOIN result_rows r
);
GOSELECT * FROM fn_split_into_ints(10,3);
SELECT * FROM fn_split_into_ints(49,6);
SELECT * FROM fn_split_into_ints(2000,11);Context
StackExchange Database Administrators Q#170730, answer score: 5
Revisions (0)
No revisions yet.