i need postgresql mathematical select - sql

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;

Related

Snowflake SQL UDF - Unsupported Subquery Error

I am creating a Snowflake SQL UDF. I keep running into SQL compilation error: Unsupported subquery type cannot be evaluated. I have tried to do several things to go around the issue, this being my latest try.
How can I make this break out of the subquery'ing error?
The UDF should allow one to input their preferred year. Thinking to create a solution by if a year is not provided, the default would be the present year.
create or replace function new_value(PRICE float, TYPE varchar, YR_CREATED int, YEAR int)
returns float
as
$$
with AGE_OF_PRODUCT as (
select any_value((YEAR - YR_CREATED)) as AGE ),
FORMULA as (
select any_value(AGE) as AGE,
any_value(case
when AGE <= 1 then 1
else 2
end) as FUNCTION
from AGE_OF_PRODUCT
)
select
any_value(case
when F.FUNCTION = 1 then (PRICE - (PRICE * R.R1))
else (PRICE * (1 - (R.R1))) * pow((1-(R.R2)), ((F.AGE - YR_CREATED)-1))
end) as VALUE
from FORMULA as F, RATES as R
where TYPE = R.TYPE_OF_PRODUCT
$$;
So the main problem is you are likely using the function like:
select v.*,
new_value(v.price, v.type, v.yr_create, v.year) as awesome
from table_with_values as v
also your UDF can be rewritten as it stands as:
create or replace function new_value(
PRICE float,
TYPE varchar,
YR_CREATED int,
YEAR int)
returns float
as
$$
select
YEAR - YR_CREATED as age,
case age <= 1
when true then (PRICE - (PRICE * r.r1))
else (PRICE * (1 - (r.r1))) * pow((1-(r.r2)), ((age - YR_CREATED)-1))
end as value
from rates as r
where TYPE = r.type_of_product
$$;
but if we move the join to rates outside the UDF
create or replace function new_value(
PRICE float,
YR_CREATED int,
YEAR int,
rate1 float,
rate2 float)
returns float
as
$$
select
case (YEAR - YR_CREATED) <= 1
when true then (PRICE - (PRICE * r.r1))
else (PRICE * (1 - (rate1))) * pow((1-(rate2)), (((YEAR - YR_CREATED) - YR_CREATED)-1))
end as value;
$$;
then we can call it like:
select v.*,
new_value(v.price, v.yr_create, v.year, r.r1, r.r2) as awesome
from table_with_values as v
join rates as r
on v.type = r.type_of_product

T-SQL: Efficient way to add up column values

Now I'm sure this has been asked and superbly been answered on here. However, I am unable to find the answer since it touches many keywords.
I basically want to replace a table of the form:
Type amount param note
7 2 str1 NULL
42 12 str2 NULL
128 7 str3 samplenote
42 12 NULL NULL
101 4 str4 NULL
42 12 NULL NULL
7 1 str1 samplenote
128 2 str5 NULL
with a table like:
Type amount param note
7 3 str1 combined
42 36 NULL combined
128 9 NULL combined
101 4 str4 combined
In words, I seek to sum up the amount parameter based on its type while declaring param = NULL for all "unclear" fields. (param should be NULL when the param values of combined Types have more than one different content; else, param should have the original content.)
With my python background, I tackled this task with a for loop approach, iterating through the types, adding a new row for every type with summed up amount and note = 'combined', to then delete the remaining rows (see below). There has to be a more efficient way with some JOIN statement I'm sure. But how would that look like?
FYI, this is the solution I am working on (not functioning yet!):
/*** dbo.sourcetable holds all possible Type values ***/
CREATE PROCEDURE [sumup]
AS
BEGIN
DECLARE #i int = (SELECT TOP (1) Type FROM [dbo].[sourcetable] ORDER BY Type)
DECLARE #MaxType int = (SELECT TOP (1) Type FROM [dbo].[sourcetable] ORDER BY Type DESC)
DECLARE #sum int
BEGIN TRY
WHILE #i <= #MaxType
BEGIN
IF EXISTS (SELECT * FROM [dbo].[worktable] WHERE Type = #i)
BEGIN
SET #sum = (SELECT SUM(amount) FROM [dbo].[worktable] WHERE Type = #i)
BEGIN
WITH cte AS (SELECT * FROM [dbo].[worktable] WHERE Type = #i)
INSERT INTO [dbo].[worktable]
([Type]
,[amount]
,[param]
,[note]
SELECT
cte.Type
,#sum
,cte.param
,'combined'
FROM cte
END
DELETE FROM [dbo].[worktable] WHERE Type = #i AND ISNULL([note],'') <> 'combined'
END
SET #i = #i + 1
END
END TRY
BEGIN CATCH
-- some errorlogging code
END CATCH
END
GO
This can be achieved with a single select statement.
If you require your combined flag to only apply to where more than one row has been combined, add another case expression checking the result of either a count(1) for rows combined or count(distinct param) for unique param values combined:
declare #t as table(type int, amount int, param varchar(15), note varchar(15));
insert into #t values (7,2,'str1',NULL),(42,12,'str2',NULL),(128,7,'str3','samplenote'),(42,12,NULL,NULL),(101,4,'str4',NULL),(42,12,NULL,NULL),(7,1,'str1','samplenote'),(128,2,'str5',NULL);
select type
,sum(amount) as amount
,case when count(distinct isnull(param,'')) = 1
then max(param)
else null
end as param
,'combined' as note
from #t
group by type
order by type;
Output:
+------+--------+-------+----------+
| type | amount | param | note |
+------+--------+-------+----------+
| 7 | 3 | str1 | combined |
| 42 | 36 | NULL | combined |
| 101 | 4 | str4 | combined |
| 128 | 9 | NULL | combined |
+------+--------+-------+----------+
I am doing this way from keyboard, but this may work or be close to what you want
Select type , amount , iif( dc=1,p,null) param, 'combined' note
from
(
Select type, sum(amount) amount,
count(distinct Param) dc,max(Param) p
From ....
Group by type
) x
Here is a possible solution:
declare #tbl as table (
type int
,amount int
,param varchar(15)
,note varchar(15)
)
insert into #tbl values (7,2,'str1',NULL)
insert into #tbl values (42,12,'str2',NULL)
insert into #tbl values (128,7,'str3','samplenote')
insert into #tbl values (42,12,NULL,NULL)
insert into #tbl values (101,4,'str4',NULL)
insert into #tbl values (42,12,NULL,NULL)
insert into #tbl values (7,1,'str1','samplenote')
insert into #tbl values (128,2,'str5',NULL)
;WITH CTE AS (
SELECT
type
,SUM(AMOUNT) AS amount
,COUNT(DISTINCT ISNULL(param, 'dummy value')) AS ParamNo
,MAX(Param) AS Param
FROM #tbl
GROUP BY type
) SELECT
type
,amount
,CASE WHEN ParamNo = 1 THEN Param ELSE NULL END AS Param
,'combined' AS note
FROM CTE
This should work:
Select Type, sum(amount) as amount, count(distinct param)
, case when count(distinct param) = 1 then max(param) end as param,
'Combined' as note
From
mytable
Group By Type

Need Help for performing below calculation

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)

getting average of average depending on a column?

I have a query where I get the average in each row and showing the employee.
I would like it to show the average for EACH employee. meaning I would like to average all the row with the same employee.
How would I be able to accomplish this?
This is my current query:
SELECT
(
SELECT AVG(rating)
FROM (VALUES
(cast(c.rating1 as Float)),
(cast(c.rating2 as Float)),
(cast(c.rating3 as Float)),
(cast(c.rating4 as Float)),
(cast(c.rating5 as Float))
) AS v(rating)
WHERE v.rating > 0
) avg_rating, employee
From CSEReduxResponses c
Where
month(c.approveddate)= 6
AND year(c.approveddate)=2014
Below I have some sample data I created:
create table CSEReduxResponses (rating1 int, rating2 int, rating3 int, rating4 int, rating5 int,
approveddate datetime,employee int)
insert into CSEReduxResponses (rating1 , rating2 ,rating3 , rating4 , rating5 ,
approveddate, employee )
values
(5,4,5,1,4,'2014-06-18',1),
(5,4,5,1,4,'2014-06-18',1),
(5,4,5,1,0,'2014-06-18',1),
(5,4,0,1,4,'2014-06-18',2),
(5,4,5,1,4,'2014-06-18',2),
(5,4,0,1,4,'2014-06-18',3),
(5,0,5,4,4,'2014-06-18',3),
(5,4,5,0,0,'2014-06-18',3);
How about something like this?
select employee,
avg(case when n.n = 1 and rating1 > 0 then rating1
when n.n = 2 and rating2 > 0 then rating2
when n.n = 3 and rating3 > 0 then rating3
when n.n = 4 and rating4 > 0 then rating4
when n.n = 5 and rating5 > 0 then rating5
end)
from CSEReduxResponses c cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all select 5
) n
where month(c.approveddate)= 6 and year(c.approveddate)=2014
group by employee;
I would recommend rewriting the where clause as:
where c.approveddate >= '2014-06-01' and c.approveddate < '2014-07-01'
This would allow the SQL engine to use an index on approveddate.
select
(sum(rating1)+sum(rating2)+sum(rating3)+sum(rating4)+sum(rating5))
/
(count(nullif(rating1,0))+count(nullif(rating2,0))+count(nullif(rating3,0))+count(nullif(rating4,0))+count(nullif(rating5,0)))
as avg_rating,
count(*) as number_of_responses, employee
From CSEReduxResponses where month(approveddate)= 6 AND year(approveddate)=2014 group by employee ;
I have also come come up with a slightly slicker version, using a UDF. I prefer this one, as the average function might come in useful for other queries...
DELIMITER //
DROP FUNCTION IF EXISTS cca_wip.avg_ignore0//
CREATE FUNCTION cca_wip.avg_ignore0(
str VARCHAR(500)
) RETURNS double
COMMENT 'values separated by a coma, that are to be averaged. 0 will be treated as NULL'
BEGIN
DECLARE ss TEXT;
DECLARE sum, count double;
IF length(str)=0 or str not regexp '[0-9]' then RETURN 0;
end if;
IF str regexp '[a-z]' then RETURN NULL;
end if;
SET str=replace(str,'NULL','0');
SET sum =0;
SET count =0;
WHILE length(str)>0 DO
set ss=substring_index(str,',',1);
SET sum = sum + ss;
IF ss>0 THEN SET count = count+1;
END IF;
set str=trim(trim(trim(',') from trim(trim(ss from str))));
END WHILE;
RETURN (sum/count);
END//
DELIMITER ;
select
avg_ignore0(group_concat(concat_ws(',',rating1,rating2,rating3,rating4,rating5))),
count(*) as number_of_responses,
employee
From CSEReduxResponses
where
month(approveddate)= 6 AND year(approveddate)=2014
group by employee ;

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