T-SQL Recursive Division - sql

How do I perform recursive division in SQL?
I know the basic premise is
declare #i = 0
declare #testValue bigint = FLOOR(#num/#dem)
while #testValue>0
begin
set #num = #num - #testValue*#dem
set #testValue = FLOOR(#num/#dem)
set #i = #i + 1
end
, but I do not know how to do this in an efficient manner.

Your query can be rewritten as the following:
Declare #Num BigInt = 10000,
#Dem BigInt = 50
;With Cte As
(
Select Floor(#num / #dem) TestValue, 1 As Iteration
Union All
Select Floor((TestValue * #Dem) / #Dem) As TestValue, Iteration + 1
From Cte
Where TestValue > 0
)
Select *
From Cte
But there's a problem with the design of it. It multiplies the value back to the division result, and divides again, resulting in an infinite loop.
I believe this is what you were trying to do:
Declare #Num BigInt = 10000,
#Dem BigInt = 50
;With Cte As
(
Select Floor(#num / #dem) TestValue, 1 As Iteration
Union All
Select Floor(TestValue / #Dem) As TestValue, Iteration + 1
From Cte
Where TestValue > 0
)
Select *
From Cte
Which, in this case gives the following results:
TestValue Iteration
200 1
4 2
0 3

Related

Generating a list of random numbers, summing to a fixed amount using SQL

This question is about generating N random numbers whose sum is M(constant) using SQL.
For example we have a number M=9.754. We need 10 random numbers whose sum is 9.754.
It should be done in SQL Server environment.
Can anybody help me?
While the #Squirrel answer is interesting but numbers here is more random
here is the code:
DECLARE #s INT=1,
#k FLOAT=0,
#final FLOAT=9.917,
#sum FLOAT =0,
#min FLOAT=1,
#max FLOAT=9.917
BEGIN
WHILE (#sum <> #final)
BEGIN
WHILE (#s <= 10)
BEGIN
SET #k =
(
SELECT ROUND(RAND(CHECKSUM(NEWID())) * (#max - #min) + #min,3)
);
PRINT (CONCAT('random: ',#k));
IF(#sum+#k <=#final)
SET #sum+=#k;
SET #max=#final-#sum;
PRINT (CONCAT('computed sum: ',#k));
IF(#max>1) SET #min=1 ELSE SET #min=0;
IF(#sum=#final)
BREAK;
SET #s = #s + 1;
SET #k = #k + 0;
END;
PRINT (CONCAT('final', #final))
PRINT (CONCAT('sum', #sum))
IF(#sum<>#final)--force stop if after 10 try the sum not match with final
BEGIN
PRINT(CONCAT('final random number:',#final-#sum))
SET #sum=#final;
END;
SET #s=0;
IF(#sum=#final)
BEGIN
PRINT('****************************DONE****************************')
BREAK;
END
END;
PRINT ('end');
END;
Interesting requirement.
Query below uses tally table / number table to generate 10 random numbers after that find the ratio. Final query check for case where sum of the numbers is not equal to #m and make the adjustment on the biggest number.
declare #m decimal(10,3) = 9.754,
#n int = 10;
with
-- using recursive CTE to generate a number table
numbers as
(
select n = 1
union all
select n = n + 1
from numbers
where n < #n
),
-- generate random positive numbers using newid()
-- Note : 100 is chosen arbitrary
num as
(
select n = abs(checksum(newid())) % 100
from numbers
),
-- calculate the ratio
ratio as
(
select r,
rn = row_number() over (order by r desc),
sum_r = sum(r) over()
from
(
select r = convert(decimal(10,3), n * 1.0 / sum(n) over() * #m)
from num
) r
)
-- sum(r) may not equal to #m due to rounding
-- find the difference and adjust it to the biggest r
select r, rn, sum_r,
adj_r = r + case when rn = 1 then #m - sum_r else 0 end,
sum_adj_r = sum(r + case when rn = 1 then #m - sum_r else 0 end) over()
from ratio
dbfiddle demo

Split a range of numbers in equal parts SQL Sever

I have following query to split the range of numbers in parts. For ex: i have range of 200 numbers and i have to group the numbers to 95 But Result is coming in reverse order.I Have attached expected result
declare #MinValue bigint = 1
declare #MaxValue bigint = 200;
declare #RowsPerGroup bigint =95
declare #RowsPerGroup1 bigint =(#RowsPerGroup-1)
;with src(val,rm) as (
select #MaxValue, (#MaxValue - #RowsPerGroup1) union all
select rm-1, case when rm-1 > #MinValue + #RowsPerGroup1 then rm-1 - #RowsPerGroup1 else #MinValue end from src where rm-1 >= #MinValue
)
select rm as 'Start', val as 'End',[Difference]=(val-rm)+1 from src order by rm asc
option(maxrecursion 0)
Current Result:
Start End Difference
1 10 10
11 105 95
106 200 95
Expected Result:
Pls let me know where am doing wrong
My variant:
DECLARE
#MinValue bigint = 1,
#MaxValue bigint = 200,
#RowsPerGroup bigint = 95 -- 300
;WITH cte AS(
SELECT #MinValue [Start],IIF(#RowsPerGroup>#MaxValue,#MaxValue,#RowsPerGroup) [End]
UNION ALL
SELECT [End]+1,IIF([End]+#RowsPerGroup>#MaxValue,#MaxValue,[End]+#RowsPerGroup)
FROM cte
WHERE [End]<#MaxValue
)
SELECT [Start],[End],[End]-[Start]+1 [Difference]
FROM cte
You can see the reason in the first line of the common table expression:
select #MaxValue, (#MaxValue - #RowsPerGroup1)
This inserts (200,106) intto src. The second select then counts down from existing rows. To adapt the CTE, exchange minimums with maximums (including for ranges), invert arithmetic, reverse comparisons and any other related exchanges:
select #MinValue, (#MinValue + #RowsPerGroup1) union all
select val+1, case
when val+1 < #MaxValue - #RowsPerGroup1
then val+1 + #RowsPerGroup1
else #MaxValue
end
from src
where val+1 <= #MaxValue
This particular statement can be simplified in parts:
select #MinValue, (#MinValue + #RowsPerGroup - 1) union all
select val + 1, case
when val + #RowsPerGroup < #MaxValue
then val + #RowsPerGroup
else #MaxValue
end
from src
where val < #MaxValue

How to select only armstrong numbers from the list?

I want is to select Armstrong numbers from the list below list I have searched of solution of this question bu unable to find in SQL-Server:
Numbers
121
113
423
153
541
371
I am sure most of you know what's the Armstrong number and how to calculate though I am describing is for the simplicity : sum of the cubes of its digits is equal to the number itself i.e.
1*1*1 + 5*5*5 + 3*3*3 = 153
3*3*3 + 7*7*7 + 1*1*1 = 371
Please help me on this as I am also trying but seeking for quick solution. It will be very helpful to me. Thanks in advance.
Obviously static processing during each query is not correct approach but we can create function like this and
create function dbo.IsArmstrongNumber(#n int)
returns int as
begin
declare #retValue int = 0
declare #sum int = 0
declare #num int = #n
while #num > 0
begin
set #sum += (#num%10) * (#num%10) * (#num%10)
set #num = #num/10
end
IF #sum = #n
set #retValue = 1
return #retValue
end
Pre-processing and selecting in IN clause is better
select * from #Numbers where dbo.IsArmstrongNumber(n) = 1
select 153 x into #temp;
insert #temp values(371);
insert #temp values(541);
with cte as (select x, substring(cast(x as nvarchar(40)) ,1,1) as u, 1 as N FROM #temp
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),3)) = x
drop table #temp;
here is the mark 2 - you can change the #ORDER to explore power of 4,5 etc
declare #order int = 3;
declare #limit int = 50000;
with nos as (select 1 no
union all
select no + 1 from nos where no < #limit),
cte as (select no as x, substring(cast(no as nvarchar(40)) ,1,1) as u, 1 as N FROM nos
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),#order)) = x
option (maxrecursion 0);
This is a quick mod to my sum of digits UDF
Declare #Table table (Numbers int)
Insert into #Table values
(121),
(113),
(423),
(153),
(541),
(371)
Select * from #Table where [dbo].[udf-Stat-Is-Armstrong](Numbers)=1
Returns
Numbers
153
371
The UDF
CREATE Function [dbo].[udf-Stat-Is-Armstrong](#Val bigint)
Returns Bit
As
Begin
Declare #RetVal as bigint
Declare #LenInp as bigint = len(cast(#Val as varchar(25)))
;with i AS (
Select #Val / 10 n, #Val % 10 d
Union ALL
Select n / 10, n % 10
From i
Where n > 0
)
Select #RetVal = IIF(SUM(power(d,#LenInp))=#Val,1,0) FROM i;
Return #RetVal
End
You can use the following to find Armstrong numbers using Sql functions:
WITH Numbers AS(
SELECT 0 AS number UNION ALL SELECT number + 1 FROM Numbers WHERE number < 10000)
SELECT number AS ArmstrongNumber FROM Numbers
WHERE
number = POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),1,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),2,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),3,1),0),3)
OPTION(MAXRECURSION 0)

Complicated SQL while loop

I am trying to create a while loop in SQL and it seems kind of complex. Here's what I need it to achieve:
Iterate through a single VARCHAR string (ex. '123')
If the nth character is in an even position in the string (ex. 2nd, 4th .... letter in the string), it must be added(SUM) to a base variable (Let's assume #z)
If the nth character is in an odd position in the string (ex. 1st, 3rd .... letter in the string), it must be multiplied by 2. If this newly generated value (Let's assume #y) is less than 10, it must be added(SUM) to the base variable (Still the same assumed #z). If #y is greater than 10, we need to subtract 9 from #y before adding(SUM) it to #z
After iterating through the entire string, this should return a numeric value generated by the above process.
Here is what I've done so far, but I'm stuck now (Needless to say, this code does not work yet, but I think I'm heading in the right direction):
DECLARE #x varchar(20) = '12345'
DECLARE #p int = len(#x)
WHILE #p > 0
SELECT #x =
stuff(#x, #p, 1,
case when CONVERT(INT,substring(#x, #p, 1)) % 2 = 0 then CONVERT(INT, #x) + CONVERT(INT,substring(#x, #p, 1))
end), #p -= 1
RETURN #x;
PS. The input will always be 100% numeric values, but it is formatted as VARCHAR when I recieve it.
UPDATE
The expected result for the sample string is 15
You can do this without using a loop. Here is a solution using Tally Table:
DECLARE #x VARCHAR(20) = '12345'
DECLARE #z INT = 0 -- base value
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
CteTally(N) AS(
SELECT TOP(LEN(#x)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
),
CteChars(N, num) AS(
SELECT
t.N, CAST(SUBSTRING(#x, t.N, 1) AS INT)
FROM CteTally t
WHERE t.N <= LEN(#x)
)
SELECT
SUM(
CASE
WHEN N % 2 = 0 THEN num
WHEN num * 2 < 10 THEN num * 2
ELSE (num * 2) - 9
END
) + #z
FROM CteChars
The CTEs up to CteTally generates a list of number from 1 to LEN(#x). CteChars breaks #x character by character into separate rows. Then the final SELECT does a SUM based on the conditions.
OUTPUT : 15
Check below if it helps you
DECLARE #x varchar(20) = '12345'
DECLARE #p int = 1
DECLARE #result bigint=0;
DECLARE #tempval int =0;
WHILE #p <= len(#x)
BEGIN
SET #tempval = CONVERT(INT,substring(#x, #p, 1));
if(#p%2 = 1)
BEGIN
SET #tempval = #tempval * 2;
IF(#tempval >= 10) SET #tempval = #tempval - 9;
END
SET #result = #result + #tempval;
SET #p = #p + 1;
END;
PRINT #result;--This is the result
RETURN #x;
DECLARE #x INT = 12345
DECLARE #p int = len(#x)
DECLARE #z INT =0
PRINT #p%2
SET #x=#x/10
PRINT #x
WHILE #p > 0
BEGIN
IF(#p%2 = 0)
BEGIN
SET #z=#z+#x%10
SET #p=#p-1
SET #x=#x/10
END
ELSE
BEGIN
SET #z=#z+(2*(#x%10))
SET #p=#p-1
SET #x=#x/10
IF(#x>=10)
BEGIN
SET #x=(#x/10+#x%10)
END
END
END
SELECT #z
The while loop does not seem necessary here.
This can be achieved with a CTE that will split the string and a case statement:
DECLARE #x varchar(20) = '12345';
with split(id, v) as (
select 0, cast(0 as tinyint)
union all
select id+1, cast(SUBSTRING(#x, id+1, 1) as tinyint)
From split
Where id+1 <= len(#x)
)
Select Result = SUM(
Case When id % 2 = 0 then v
When v < 5 then v*2
Else (v*2)-9
End
)
From split
output = 15

Cumulative Summation in SQL Server

I'm writing a query, part of Stored Procedure in SQL Server. I need to find cumulative summation in SQL Server.
A variable will hold a integer value say 100. Let's say
Declare #Variable int = 100
Now the #NewVariable will have below formula:
#NewVariable = #Variable * (1 - 0.005)
Hence #NewVariable = 99.5
Now, the #NewestVariable will have below formula;
#NewestVariable = #NewVariable * (1 - 0.005)
Hence the #NewestVariable will have value of 99.00
Likewise this calculation will occur 24 times and all the results will be sum at the end.
Hence final result will be: 100 + 99.5 +99.00 + .... ...
I tried to achieve the desired result using #Count Variable (DECLARE #COUNT INT = 24) and using a While loop, but I'm not sure whether I'm correct or not?
Request your help!
Thanks!
You can do it using a CTE as below:
declare #variable int = 100
;with cte as
(
select convert(numeric(8,2), #variable) as var, 1 as recCount
union all
select convert(numeric(8,2), #variable * (1 - recCount*0.005)) as var, recCount+1 as recCount
from cte
where recCount < 24
)
select sum(var) as total from cte
Working Fiddle Demo
Edit: Adjusted to resolve rounding error as per #peter.petrov 's comment
If you need to get the values of each row before counting, please use this fiddle
Try this code.
declare #Variable decimal(20,10);
set #Variable = 100;
declare #Sum decimal(20,10);
set #Sum = 0;
declare #cnt int;
set #cnt = 1;
while (#cnt <= 24)
begin
set #Sum = #Sum + #Variable;
set #Variable = #Variable * (1.0 - 0.005);
set #cnt = #cnt + 1;
end;
select #Sum;
A simple answer would be like this
DECLARE #Variable INT = 100
SELECT #Variable
DECLARE #counter INT = 1
DECLARE #SumVariable NUMERIC(20,2) = #Variable
DECLARE #NewVariable NUMERIC(20,2) = #Variable
WHILE(#counter<24)
BEGIN
SET #NewVariable = #NewVariable * 0.995
SET #SumVariable = #SumVariable + #NewVariable
SET #counter = #counter+1
END
SELECT #SumVariable
You could also use a "quirky update" to avoid while-loops. If you're not interesed in intermediate resuls, just modify the last code line to select only the max(total).
DECLARE #total DECIMAL(10,3) = 0.000
;WITH "data" AS
(
SELECT CAST( 100 AS DECIMAL(10,3)) AS id
UNION ALL
SELECT CAST( id * ( 1 - 0.005 ) AS DECIMAL(10,3)) FROM "data" WHERE id > 100 * (1 - 21 * 0.005)
)
SELECT
id, total = CAST( 0.000 AS DECIMAL(10,3))
INTO #temp
FROM
"data"
OPTION ( MAXRECURSION 23 );
UPDATE t SET #total = total = #total + id FROM #temp t
SELECT * FROM #temp
See SQL-Fiddle for testing.