Syntax Errors in User Defined Function in SQL - sql

I'm unable to understand why this code to calculate the Volume of a cube results in the Error Procedure or function 'CubicVolume' expects parameter '#CubeLength', which was not supplied..
CREATE FUNCTION CubicVolume
-- Input dimensions in centimeters
(
#CubeLength decimal(4,1),
#CubeWidth decimal(4,1),
#CubeHeight decimal(4,1)
)
RETURNS decimal(12,3)
AS
BEGIN
RETURN(#CubeLength * #CubeWidth * #CubeHeight)
END
I then try to Execute it using EXEC CubicVolume, which is how one would Execute a Stored Procedure.
I know some Syntax is wrong, but I'm unable to tell where.
Thank You

I'm assuming SQL Server.
You defined a scalar function, but are trying to invoke it like a stored procedure. The two are not the same. Stored procedures are basically batches of SQL statement that execute sequentially, and optionally send resultsets back to the client. Scalar functions behave like built-in functions (e.g., LEN(), CHARINDEX(), ABS(), etc.). That is, they belong in an expression inside a SELECT statement, or moral equivalent. So you need to use your function in a SELECT statement where you'd use a column or an expression. Examples:
SELECT dbo.CubicVolume(12, 5, 3) AS vol
-- result is: 180
SELECT CubeName, l, w, h FROM dbo.SomeTable WHERE dbo.CubicVolume(l, w, h) > 200
-- result could be something like: MyCube, 10, 10, 10 etc.
Basically, it's equivalent to using a mathematical expression based on columns or constants. You cannot use EXEC on it.

Related

Issue While Creating Product of All Values Of Column (UDF in Snowflake)

I was trying to create a Snowflake SQL UDF
Where it computes the Values of the all values and will return the result to the user.
So firstly, i have tried the following approach
# The UDF that Returns the Result.
CREATE OR REPLACE FUNCTION PRODUCT_OF_COL_VAL()
RETURNS FLOAT
LANGUAGE SQL
AS
$$
SELECT EXP(SUM(LN(COL))) AS RESULT FROM SCHEMA.SAMPLE_TABLE
$$
The above code executes perfectly fine....
if you could see above (i have hardcoded the TABLE_NAME and COLUMN_VALUE) which is not i acutally want..
So, i have tried the following approach, by passing the column name dynamically..
create or replace function (COL VARCHAR)
RETURNS FLOAT
LANGUAGE SQL
AS
$$
SELECT EXP(SUM(LN(COL))) AS RESULT from SCHEMA.SAMPLE_TABLE
$$
But it throws the following issue...
Numeric Value 'Col' is not recognized
To elaborate more the Data type of the Column that i am passing is NUMBER(38,6)
and in the background its doing the following work..
EXP(SUM(LN(TO_DOUBLE(COL))))
Does anyone have any idea why this is running fine in Scenario 1 and not in Scenario 2 ?
Hopefully we will be able to have this kind of UDFs one day, in the meantime consider this answer using ARRAY_AGG() and a Python UDF:
Sample usage:
select count(*) how_many, multimy(array_agg(score)) multiplied, tags[0] tag
from stack_questions
where score > 0
group by tag
limit 100
The UDF in Python - which also protects against numbers beyond float's limits:
create or replace function multimy (x array)
returns float
language python
handler = 'x'
runtime_version = '3.8'
as
$$
import math
def x(x):
res = math.prod(x)
return res if math.log10(res)<308 else 'NaN'
$$
;
The parameter you defined in SQL UDF will be evaluated as a literal:
When you call the function like PRODUCT_OF_COL_VAL('Col'), the SQL statement you execute becomes:
SELECT EXP(SUM(LN('Col'))) AS RESULT from SCHEMA.SAMPLE_TABLE
What you want to do is to generate a new SQL based on parameters, and it's only possible using "stored procedures". Check this one:
Dynamic SQL in a Snowflake SQL Stored Procedure

Get length of oracle.sql.array

On an Oracle DB I have a table with SDO_GEOMETRY objects. I would like to query the database for those polygons with less than x edges. In theory this would be easy with a query like
SELECT * FROM myTable t WHERE LENGTH(t.geometry.sdo_ordinates) < x
Obviously the LENGTH funtion is defined for char and the type of
t.geometry.sdo_ordinates is oracle.sql.ARRAY so that doesn't work. Shouldn't there be a trivial way to SELECT the length or an array in Oracle? Somehow I'm unable to get the syntax right.
PS: I kind of solved my search with the following query, still the original questerion remains, isn't there an array size/length function?
SELECT * FROM myTable t WHERE LENGTH(t.geomety.Get_WKT()) < (x * c)
No, there is no simple sql function that counts the elements of an array.
However as mentioned here, another idea is a PL/SQL script.
create or replace function get_count(ar in SDO_ORDINATE_ARRAY) return number is
begin
return ar.count;
end get_count;
t.geometry.sdo_ordinates.COUNT is a PL/SQL attribute that can be used within functions/procedures. Thus that is not a function useable in plain SQL.
Attribute:
value.someAttribute
Function:
doSomething(value)
Clarification: Functions have return values, procedures don't. Source

Convert numeric to string inside a user-defined function

I am trying to call/convert a numeric variable into string inside a user-defined function. I was thinking about using to_char, but it didn't pass.
My function is like this:
create or replace function ntile_loop(x numeric)
returns setof numeric as
$$
select
max("billed") as _____(to_char($1,'99')||"%"???) from
(select "billed", "id","cm",ntile(100)
over (partition by "id","cm" order by "billed")
as "percentile" from "table_all") where "percentile"=$1
group by "id","cm","percentile";
$$
language sql;
My purpose is to define a new variable "x%" as its name, with x varying as the function input. In context, x is numeric and will be called again later in the function as a numeric (this part of code wasn't included in the sample above).
What I want to return:
I simply want to return a block of code so that every time I change the percentile number, I don't have to run this block of code again and again. I'd like to calculate 5, 10, 20, 30, ....90th percentile and display all of them in the same table for each id+cm group.
That's why I was thinking about macro or function, but didn't find any solutions I like.
Thank you for your answers. Yes, I will definitely read basics while I am learning. Today's my second day to use SQL, but have to generate some results immediately.
Converting numeric to text is the least of your problems.
My purpose is to define a new variable "x%" as its name, with x
varying as the function input.
First of all: there are no variables in an SQL function. SQL functions are just wrappers for valid SQL statements. Input and output parameters can be named, but names are static, not dynamic.
You may be thinking of a PL/pgSQL function, where you have procedural elements including variables. Parameter names are still static, though. There are no dynamic variable names in plpgsql. You can execute dynamic SQL with EXECUTE but that's something different entirely.
While it is possible to declare a static variable with a name like "123%" it is really exceptionally uncommon to do so. Maybe for deliberately obfuscating code? Other than that: Don't. Use proper, simple, legal, lower case variable names without the need to double-quote and without the potential to do something unexpected after a typo.
Since the window function ntile() returns integer and you run an equality check on the result, the input parameter should be integer, not numeric.
To assign a variable in plpgsql you can use the assignment operator := for a single variable or SELECT INTO for any number of variables. Either way, you want the query to return a single row or you have to loop.
If you want the maximum billed from the chosen percentile, you don't GROUP BY x, y. That might return multiple rows and does not do what you seem to want. Use plain max(billed) without GROUP BY to get a single row.
You don't need to double quote perfectly legal column names.
A valid function might look like this. It's not exactly what you were trying to do, which cannot be done. But it may get you closer to what you actually need.
CREATE OR REPLACE FUNCTION ntile_loop(x integer)
RETURNS SETOF numeric as
$func$
DECLARE
myvar text;
BEGIN
SELECT INTO myvar max(billed)
FROM (
SELECT billed, id, cm
,ntile(100) OVER (PARTITION BY id, cm ORDER BY billed) AS tile
FROM table_all
) sub
WHERE sub.tile = $1;
-- do something with myvar, depending on the value of $1 ...
END
$func$ LANGUAGE plpgsql;
Long story short, you need to study the basics before you try to create sophisticated functions.
Plain SQL
After Q update:
I'd like to calculate 5, 10, 20, 30, ....90th percentile and display
all of them in the same table for each id+cm group.
This simple query should do it all:
SELECT id, cm, tile, max(billed) AS max_billed
FROM (
SELECT billed, id, cm
,ntile(100) OVER (PARTITION BY id, cm ORDER BY billed) AS tile
FROM table_all
) sub
WHERE (tile%10 = 0 OR tile = 5)
AND tile <= 90
GROUP BY 1,2,3
ORDER BY 1,2,3;
% .. modulo operator
GROUP BY 1,2,3 .. positional parameter
It looks like you're looking for return query execute, returning the result from a dynamic SQL statement:
http://www.postgresql.org/docs/current/static/plpgsql-control-structures.html
http://www.postgresql.org/docs/current/static/plpgsql-statements.html

SQL Server - evaluate a function in a dynamic query

I have a piece of dynamic SQL inside part of which retrieves a function dependent on other results from the query, but also uses these results to evaluate this function. I know eval() does not exist in SQL so what do I use?
A very simplified version
select reading, functiontype, #result = eval(f.functionformula)
from readingstables r
join functiontable f on (r.functiontype = f.functiontype)
So basically (note these are only example formulae) I want to use the functionformula which is related to a set of readings via the formulatype
if f.functiontype == 'A' then f.functionformula = reading * reading
if f.functiontype == 'B' then f.functionformula = reading * costant / anothervalue
//etc etc
The real version is a huge piece of dynamic SQL in a stored procedure that drives a cursor. I would prefer to do it in one query but suspect I might have to compromise and have a second dynamic query driven from the first.
Why not simply use the POWER function:
Case functionType
When 'A' Then Power( reading, 2 )
When 'B' Then Power( reading, 3 )
...
End
You could even get super fancy like so:
Power( reading, Ascii( functionType ) - Ascii('A') + 2 )
Edit
Given your change to your OP, beyond dynamic SQL, there is no way to dynamically execute a function call. You could create a UDF which takes the function type parameter and executes the correct expression however the UDF itself would need to be a large Case expression.
Create Function FunctionTypeExpression( #FunctionType char(1) )
Returns float
As
Return Case #FunctionType
When 'A' Then ..expression 1
When 'B' Then ..expression 2
...
One note in this, you will need to make the return value of the function compatible with any possible return type from the expressions. Hopefully, they are all numeric. If they are not all numeric (or all text), then a more detailed explanation for why this is not the case would be needed.

Does calling a scalar function in a select statement multiple times run the function multiple times, and how to get around that if so

If I have a select statement with a scalar function in it used in various calculations, does that scalar function get called multiple times? If it does, is there a way to optimize this so it only calls the funciton once per select, as in my real query it will be called thousands of times, X 6 times per select.
For example:
SELECT
[dbo].[fn_Days](#Account) + u.[DayRate],
[dbo].[fn_Days](#Account) / u.[WorkDays]
FROM [dbo].[tblUnit] u
All fn_days does is return an int of days worked.
Yes the scalar gets called multiple times the way that you have coded it. One way to make it work would be to wrap it into a subquery like this:
SELECT t.[days] + t.[DayRate],
t.[days] / t.[WorkDays]
FROM (
SELECT
[dbo].[fn_Days](#Account) as days,
u.[DayRate],
u.[WorkDays]
FROM [dbo].[tblUnit] u) as t
This way fn_Days only gets called once per row, rather than twice, or six times like you mentioned.
Hope this helps.
Functions are deterministic which means that it will always return the same value for a given parameter. You are using a variable as the parameter so you can call the function once before executing the query and use the result in the query instead of calling the function.
DECLARE #Days int
SET #Days = [dbo].[fn_Days](#Account)
SELECT
#Days + u.[DayRate],
#Days / u.[WorkDays]
FROM [dbo].[tblUnit] u