Need Help for performing below calculation - sql

I have a certain value say 10 as a base for my calculation. Now the growth for the first period corresponding to value 10 is 5 . The resultant which i want is 10*(1+5/100) which is basically Base * (1+ % of Growth) . The resultant Value for the first period will be the new Base for the next period. Assuming the next growth be 6 , the result for the next period is (10(1+5/100)) * (1+ 6/100) . This is basically running multiplication and can be achieved using many ways. Now someone please suggest the ultimate best way to achieve this calculation.
10 , 5 --> 10 (1 + 5/100) = 10.50
10.50 , 6 --> 10.50 (1 + 6/100) = 11.1300
11.13 , any value and so on
The method I've tried using other data sample but which is running multiplication basically.
CREATE TABLE #t1
(
projection_details_sid INT,
period_sid INT,
growth NUMERIC(22, 6)
)
INSERT INTO #t1
(projection_details_sid,
period_sid,
growth)
VALUES ( 1,601,2 ),
( 1,602,2 ),
( 1,603,2 ),
( 1,604,1 ),
( 1,605,6 ),
( 1,606,3 )
SELECT *,
Exp(Sum(Log(growth))
OVER (
PARTITION BY projection_details_sid
ORDER BY projection_details_sid ROWS UNBOUNDED PRECEDING ))
FROM #t1

Try a recursive query.
The below example is for Oracle, but it can be easily adopted to SQL-Server.
WITH our_recursive_query(projection_details_sid, period_sid, growth, base, our_result)
AS (
select projection_details_sid, period_sid, growth,
10.0 as base,
10 * ( 1 + growth/100) As our_result
from t1 where period_sid = 601
UNION ALL
SELECT a.projection_details_sid, a.period_sid, a.growth,
b.our_result as base,
b.our_result * ( 1 + a.growth/100) As our_result
FROM t1 a
JOIN our_recursive_query b
ON a.period_sid = b.period_sid + 1
)
SELECT * FROM our_recursive_query
and a result is:
PROJECTION_DETAILS_SID PERIOD_SID GROWTH BASE OUR_RESULT
--------------------------------------- ---------- ---------- ------------ ------------
1 601 2 10.00000000 10.20000000
1 602 2 10.20000000 10.40400000
1 603 2 10.40400000 10.61208000
1 604 1 10.61208000 10.71820080
1 605 6 10.71820080 11.36129285
1 606 3 11.36129285 11.70213163
I am assumming that period_sid is increassing by 1, therefore I am using .period_sid = b.period_sid + 1 as a join condition. If this is not true in your real data, you need to modify slighly the query with use of row_number analytic function.
EDIT
#kordiko thanks. is there any other possible to achieve the result
other than recursive cte since recursive cte has a similar performance
of while loop.
Yes. In Oracle you can create your own aggregate function that performs a multiply of a chain of numbers - in the similar way as a built-in sum function does ==> X1+X2+...+Xn, but does X1*X2*....*Xn instead.
An example:
create or replace TYPE MyCumulativeMultiply_type
AS OBJECT (
cumulativeMultiplyResult NUMBER,
STATIC FUNCTION odciaggregateinitialize(ctx IN OUT MyCumulativeMultiply_type) RETURN NUMBER,
MEMBER FUNCTION odciaggregateiterate(self IN OUT MyCumulativeMultiply_type, your_parameter_to_aggregate IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION odciaggregatemerge(self IN OUT MyCumulativeMultiply_type, ctx2 IN MyCumulativeMultiply_type) RETURN NUMBER,
MEMBER FUNCTION odciaggregateterminate(self IN MyCumulativeMultiply_type, returnvalue OUT NUMBER, flags IN NUMBER) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY MyCumulativeMultiply_type
IS
STATIC FUNCTION odciaggregateinitialize(ctx IN OUT MyCumulativeMultiply_type)
RETURN NUMBER
IS
BEGIN
-- instantiate our type, NULL the dummy attribute
ctx := MyCumulativeMultiply_type( 1 );
RETURN odciconst.success;
END odciaggregateinitialize;
MEMBER FUNCTION odciaggregateiterate(self IN OUT MyCumulativeMultiply_type, your_parameter_to_aggregate IN NUMBER)
RETURN NUMBER
IS
BEGIN
self.cumulativeMultiplyResult := self.cumulativeMultiplyResult * your_parameter_to_aggregate;
RETURN odciconst.success;
END odciaggregateiterate;
MEMBER FUNCTION odciaggregatemerge(self IN OUT MyCumulativeMultiply_type, ctx2 IN MyCumulativeMultiply_type)
RETURN NUMBER
IS
BEGIN
self.cumulativeMultiplyResult := self.cumulativeMultiplyResult * ctx2.cumulativeMultiplyResult;
RETURN odciconst.success;
END odciaggregatemerge;
MEMBER FUNCTION odciaggregateterminate(self IN MyCumulativeMultiply_type,
returnvalue OUT NUMBER,
flags IN NUMBER
)
RETURN NUMBER
IS
BEGIN
returnvalue := self.cumulativeMultiplyResult;
RETURN odciconst.success;
END odciaggregateterminate;
END;
/
CREATE OR REPLACE FUNCTION cumulative_multiply(arg NUMBER)
RETURN NUMBER
PARALLEL_ENABLE
AGGREGATE USING MyCumulativeMultiply_type;
/
and now a query is:
select t1.*
, cumulative_multiply( 1 + growth/100 ) OVER (order by period_sid ) as multiplier
, 10 * cumulative_multiply( 1 + growth/100 ) OVER (order by period_sid ) as our_result
from t1;
and a result is:
PROJECTION_DETAILS_SID PERIOD_SID GROWTH MULTIPLIER OUR_RESULT
--------------------------------------- ---------- ---------- ------------ ------------
1 601 2 1.02000000 10.20000000
1 602 2 1.04040000 10.40400000
1 603 2 1.06120800 10.61208000
1 604 1 1.07182008 10.71820080
1 605 6 1.13612928 11.36129285
1 606 3 1.17021316 11.70213163
Unfortunately I don't know it the above approach is possible in SQL-Server.

You can get idea from below query:-
DECLARE #Growth INT = 5
,#Base DECIMAL(18,2) = 10.0;
;WITH Test(Base, Growth)
AS
(
SELECT #Base, #Growth
UNION ALL
SELECT CAST(t.Base * (1 + t.Growth/100.0) AS DECIMAL(18,2)) , t.Growth + 1
FROM Test t
WHERE t.Base < 600000000
)
SELECT *
FROM Test
option (maxrecursion 0)

Related

Dynamic Generate rows without using loops

How to dynamic generate row from max value
for example if i am passing max value 7 Store procedure should return value
1
2
3
4
5
6
7
without using loops
If you are using sql server below query will serve you purpose:
create procedure generate_list
#maxLimit int
as
begin
SELECT DISTINCT n = number
FROM master..[spt_values]
WHERE number BETWEEN 1 AND #maxlimit
end
Then call the store procedure with :
exec generate_list 7
Output:
create PROCEDURE [dbo].GenerateSequence
#MaxLimit int
AS
BEGIN
;with numcte
AS
(
SELECT 1 [Sequence]
UNION all
SELECT [Sequence] + 1 FROM numcte WHERE [Sequence] < #MaxLimit
)
select * from numcte
END

i need postgresql mathematical select

I need idea for sql select on this.
I have a table with this columns:
ID type quan price
1 1 5 6.5
1 1 4 7
1 2 5 10
1 1 5 6
I need to run a query with following condition:
fir i = 1 to 4
if type = 1
rprice = (rprice*rquan + price*quan)/(rquan+quan)
rquan = rquan + quan
else
rquan = rquan - quan
end
next
The type can be 1 or 2
For ID 1 I need as result rquan and rprice
The sumary result must be :
rquan=9 rprice=6.32
select
case when type = 1
then (rprice*rquan + price*quan)/(rquan+quan)
else price
end as "rprice"
,case when type = 1
then rquan + quan
else quan
end as "rquan"
from "table_name"
Assuming you have a column that specifies the ordering of the rows, you can do this with cumulative sums and/or aggregation. For instance, for rquan:
select rquan
from (select t.*,
sum(case when type = 1 then quan else - quan end) over (order by ??) as rquan_row
from table t
) t
order by ?? desc
limit 1;
This can actually be simplified to a single conditional aggregation, but you seem to want this quantity row-by-row.
I don't fully understand the calculation for rprice. I can see what the formula is, but it doesn't make sense. An average price for the data would be 6 not 6.32.
You can write a custom aggregate to do this. The syntax is rather strange in places, though.
create schema so32264410;
create table so32264410.data(seqno serial primary key, id int, type int, quan int, price numeric);
insert into so32264410.data(id, type, quan, price) values(1, 1, 5, 6.5),(1, 1, 4, 7),(1, 2, 5, 10),(1, 1, 5, 6);
-- define a type to hold the aggregation and a function to perform reduction
create type so32264410.calc_type as (quan int, price numeric);
create function so32264410.calc_sfunc(state so32264410.calc_type, type int, quan int, price numeric) returns so32264410.calc_type immutable language plpgsql as $$
declare
rquan int;
rprice numeric;
begin
rquan := state.quan;
rprice := state.price;
if type = 1 then
rprice := (rprice * rquan + price * quan) / (rquan + quan);
rquan := rquan + quan;
else
rquan := rquan - quan;
end if;
state := row(rquan, rprice)::so32264410.calc_type;
return state;
end
$$;
-- bind the reducing function, state type and initial state into an aggregate
create aggregate so32264410.calc(int, int, numeric) (sfunc = so32264410.calc_sfunc, stype = so32264410.calc_type, initcond = '(0,0)');
select so32264410.calc(type, quan, price) from so32264410.data where id = 1;

SQL - positive part function (x)^+

I want to get positive part of a number x in sql. It means that the result is x if x>0 and zero otherwise. I mean to use it after an aggregate function.
select 1 as num, 200 as weight into #table
insert into #table values
(8, 100),
(10, 200),
(11, -300),
(20, -100);
Till now I have been using the following:
select sum(num * weight)/sum(weight) as Result,
IIf(sum(num * weight)/sum(weight)>0, sum(num * weight)/sum(weight), 0) as PositivePartResult
from #table
But it is not clear as the function gets longer. Is there a built-in function to get the same result without repetition of the formula?
Another way of writing same query is:
select Result,
case when Result > 0 Then Result else 0 end as PositivePartResult
from
(
select sum(num * weight)/sum(weight) as Result
from #table
)T
You could either calculate the value inline or, if you'll be doing this frequently, create a user defined function:
create function PositiveValue( #N as Int )
returns Int as
begin
return ( Sign( #N ) + 1 ) / 2 * #N;
end;
go
declare #Samples as Table ( N Int );
insert into #Samples ( N ) values ( -42 ), ( -1 ), ( 0 ), ( 1 ), ( 42 );
select N, ( Sign( N ) + 1 ) / 2 * N as PositiveValue1, dbo.PositiveValue( N ) as PositiveValue2
from #Samples;
-- drop function dbo.PositiveValue;

Create function return integer SQL Server 2008

I was trying to create a function which returns to an integer. However, I got the warning as
"Msg 2715, Level 16, State 3, Procedure median, Line 1
Column, parameter, or variable #0: Cannot find data type Median."
Here is the query. Thanks in advance.
CREATE FUNCTION dbo.median (#score int)
RETURNS Median
AS
BEGIN
DECLARE #MedianScore as Median;
SELECT #MedianScore=
(
(SELECT MAX(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score) AS BottomHalf)
+
(SELECT MIN(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score DESC) AS TopHalf)
) / 2 ;
RETURN #MedianScore;
END;
GO
Just change the return type to integer:
CREATE FUNCTION dbo.median (#score int)
RETURNS integer
AS
BEGIN
DECLARE #MedianScore as integer;
Unless you're intentionally using the Median type for something that you haven't stated.
Since you are calculating Median of some values I would suggest you return a Numeric value instead of Integer as MAX(#score) + MIN(#score)/ 2 can return a decimal number value. so trying to save that value in an INT variable will truncate the Decimal part. which can lead to wrong results.
In the following example I have used NUMERIC(20,2) return value.
CREATE FUNCTION dbo.median (#score int)
RETURNS NUMERIC(20,2)
AS
BEGIN
DECLARE #MedianScore as NUMERIC(20,2);
SELECT #MedianScore=
(
(SELECT MAX(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score) AS BottomHalf)
+
(SELECT MIN(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score DESC) AS TopHalf)
) / 2 ;
RETURN #MedianScore;
END;
GO
or if you do want to return an INTEGER use round function inside the function something like this..
CREATE FUNCTION dbo.median (#score int)
RETURNS INT
AS
BEGIN
DECLARE #MedianScore as INT;
SELECT #MedianScore=ROUND(
(
(SELECT MAX(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score) AS BottomHalf)
+
(SELECT MIN(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score DESC) AS TopHalf)
) / 2, 0) ;
RETURN #MedianScore;
END;
GO
You must declare a datatype on RETURNS. "Median" is not a type.
CREATE FUNCTION dbo.median (#score int)
RETURNS real -- you can use also float(24), numeric(8,3), decimal(8,3)...
AS
BEGIN
DECLARE #MedianScore as real;
SELECT #MedianScore=
(
(SELECT MAX(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score) AS BottomHalf)
+
(SELECT MIN(#score) FROM
(SELECT TOP 50 PERCENT Score FROM t ORDER BY Score DESC) AS TopHalf)
) / 2 ;
RETURN #MedianScore;
END;
GO
create function [dbo].[Sum]
(
#x int,
#y int
)
RETURNS int
AS
BEGIN
return #x+#y
END

Generate a random number in the range 1 - 10

Since my approach for a test query which I worked on in this question did not work out, I'm trying something else now. Is there a way to tell pg's random() function to get me only numbers between 1 and 10?
If by numbers between 1 and 10 you mean any float that is >= 1 and < 10, then it's easy:
select random() * 9 + 1
This can be easily tested with:
# select min(i), max(i) from (
select random() * 9 + 1 as i from generate_series(1,1000000)
) q;
min | max
-----------------+------------------
1.0000083274208 | 9.99999571684748
(1 row)
If you want integers, that are >= 1 and < 10, then it's simple:
select trunc(random() * 9 + 1)
And again, simple test:
# select min(i), max(i) from (
select trunc(random() * 9 + 1) as i from generate_series(1,1000000)
) q;
min | max
-----+-----
1 | 9
(1 row)
To summarize and a bit simplify, you can use:
-- 0 - 9
select floor(random() * 10);
-- 0 - 10
SELECT floor(random() * (10 + 1));
-- 1 - 10
SELECT ceil(random() * 10);
And you can test this like mentioned by #user80168
-- 0 - 9
SELECT min(i), max(i) FROM (SELECT floor(random() * 10) AS i FROM generate_series(0, 100000)) q;
-- 0 - 10
SELECT min(i), max(i) FROM (SELECT floor(random() * (10 + 1)) AS i FROM generate_series(0, 100000)) q;
-- 1 - 10
SELECT min(i), max(i) FROM (SELECT ceil(random() * 10) AS i FROM generate_series(0, 100000)) q;
If you are using SQL Server then correct way to get integer is
SELECT Cast(RAND()*(b-a)+a as int);
Where
'b' is the upper limit
'a' is lower limit
(trunc(random() * 10) % 10) + 1
The correct version of hythlodayr's answer.
-- ERROR: operator does not exist: double precision % integer
-- LINE 1: select (trunc(random() * 10) % 10) + 1
The output from trunc has to be converted to INTEGER. But it can be done without trunc. So it turns out to be simple.
select (random() * 9)::INTEGER + 1
Generates an INTEGER output in range [1, 10] i.e. both 1 & 10 inclusive.
For any number (floats), see user80168's answer. i.e just don't convert it to INTEGER.
Example
SELECT RAND()*(min-max)+max
Actually I don't know you want to this.
try this
INSERT INTO my_table (my_column)
SELECT
(random() * 10) + 1
;
This stored procedure inserts a rand number into a table. Look out, it inserts an endless numbers. Stop executing it when u get enough numbers.
create a table for the cursor:
CREATE TABLE [dbo].[SearchIndex](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Cursor] [nvarchar](255) NULL)
GO
Create a table to contain your numbers:
CREATE TABLE [dbo].[ID](
[IDN] [int] IDENTITY(1,1) NOT NULL,
[ID] [int] NULL)
INSERTING THE SCRIPT :
INSERT INTO [SearchIndex]([Cursor]) SELECT N'INSERT INTO ID SELECT FLOOR(rand() * 9 + 1) SELECT COUNT (ID) FROM ID
CREATING AND EXECUTING THE PROCEDURE:
CREATE PROCEDURE [dbo].[RandNumbers] AS
BEGIN
Declare CURSE CURSOR FOR (SELECT [Cursor] FROM [dbo].[SearchIndex] WHERE [Cursor] IS NOT NULL)
DECLARE #RandNoSscript NVARCHAR (250)
OPEN CURSE
FETCH NEXT FROM CURSE
INTO #RandNoSscript
WHILE ##FETCH_STATUS IS NOT NULL
BEGIN
Print #RandNoSscript
EXEC SP_EXECUTESQL #RandNoSscript;
END
END
GO
Fill your table:
EXEC RandNumbers
Try This:
Select (ROW_NUMBER() OVER (ORDER BY ItemDesc ASC)+15000) as ID, ItemCode, ItemDesc
Using Postgres, here is how to generate random number between any 2 numbers, say, min and max:
Including min and Excluding max,
SELECT floor(random() * (max - min)) + min;
Including both min and max,
SELECT floor(random() * (max - min + 1)) + min;
So to get numbers between 1 and 10 (including 10),
min = 1, max = 10
SELECT floor(random() * (10 - 1 + 1)) + 1;
In general, you can use this formula to get random integer numbers between any min and max numbers.