PL/SQL Function returns to many values - sql

I cant understand why my sql function returns the whole table with the same value.
The task is the following:
Create a PL / SQL CalculationAge (Person) function that returns the difference between the year of death and the year of birth, if the person is already deceased, and the difference between the current year and the year of birth if the person is still alive. The current year should be determined using the DBMS system time.
Example: CalculateAlter ('Andrea') should return 24 as the result, and CalculateAlter ('Albert') should return 49."
Im working with the table below.
Geboren=birth year
Gestorben=year of dead
Geschlecht=gender
gelebt=lived
I´m working with this table.
The Code of the function is the following:
create or replace FUNCTION BerechneAlter(n_Name varchar)
RETURN Integer
is
age_result integer;
v_gestorben integer;
v_geboren integer;
Begin
select gestorben, geboren into v_gestorben, v_geboren
from Family
where Family.name = n_Name;
if v_gestorben is Null
then age_result := (2018 - v_geboren);
else
age_result := v_gestorben - v_geboren;
end if;
return age_result;
end;
At the moment the function returns the right value but it shouldn´t do it for every single line of the table.
result picture

That`s because you are running it in a context of a query, it is running for every row and calculating just Andrea. In order to return just one result you need to run it just once:
select BerechneAlter('Andrea') from dual;
And if you want it to calculate for every row in the table you use:
select BerechneAlter(name) from family;
dual is a system table in Oracle to be used to return just one result/value in a query

#JorgeCampos already explained why you are getting duplicated output rows.
Let me also suggest that the code of your procedure can be greatly simplified, so the whole logic is executed in the query :
use NVL to default the date of decease
use EXTRACT to dynamically compute the current year (instead of hardcoding the value)
Code :
create or replace FUNCTION BerechneAlter(n_Name varchar)
RETURN Integer
IS
age_result integer;
BEGIN
SELECT
NVL(gestorben, EXTRACT(year from sysdate)) - geboren into age_result
FROM Family
WHERE Family.name = n_Name;
RETURN age_result;
end;

Related

How to load query to a variable in SQL function and return it

I`m writing a program with SQL function in PostgreSQL. The purpose of the program is receiving two inputs and return one output in function. I have a table namely 'score' in which 3 columns 1) quid (integer type) 2) sca (integer type) 3) scb (integer type). I send two inputs quid and score. After that inside function two inputs are initialized to new variables. The problem is I cannot load select query to a new variable and return it. When I send two variables it is giving error. I sent variables using this: select sendans(2,'b');
My source code is here:
create or replace function sendans(n integer,m text)
returns integer as $$
declare
num1 integer=n;
num2 text=m;
total integer=0;
begin
if num2='a' then
select total = sca from score where quid=num1;
return total;
end if;
if num2='b' then
select total = scb from score where quid=num1;
return total;
end if;
end;
$$ language plpgsql;
To assign a column value from a query to a variable in PLpgSQL, you have to use the SELECT <column> INTO <variable> syntax. E.g.
SELECT sca INTO total
FROM score
WHERE quid = num1;

How to have the variable be assigned 'null' if no results are being returned?

Currently have a variable called VALUE that needs to be assigned a numeric value so that the stored proc doesn't error out . Currently there is no data in the database for every hour so when it reaches a point where there is no data for the prior hour, the whole procedure errors out (since nothing is being returned). Is there a way I can have null assigned to VALUE if no results are returned? Below is my 'SELECT INTO' code:
SELECT VALUE
INTO v_PRIOR__VALUE
FROM VALUE V
WHERE CODE = rCode
AND TIME = rTIME - 1/24;
Or, you could actually handle such a situation:
declare
v_prior__value value.value%type;
begin
SELECT VALUE
INTO v_PRIOR__VALUE
FROM VALUE V
WHERE CODE = rCode
AND TIME = rTIME - 1/24;
exception
when no_data_found then
null; -- v_prior_Value will remain NULL, as declared in the DECLARE section
when too_many_rows then
v_prior_Value := -1; -- this is just an example; you'd handle it the way you want
end;
If you are expecting 0 or one rows, then I think the simplest method is aggregation:
SELECT MAX(VALUE)
INTO v_PRIOR__VALUE
FROM VALUE V
WHERE CODE = rCode AND TIME = rTIME - 1/24;
An aggregation query with no GROUP BY (or HAVING) always returns exactly one row.

Using between operator for string which stores numbers

I have a column in which numbers are stored as string because of the nature of the column where any kind of data type is expected like date, numbers, alpha numeric,
etc.
Now i need to check if the values in that column is in defined range or not here is sample data for testing
create table test (val varchar2(10));
insert into test values ('0');
insert into test values ('67');
insert into test values ('129');
insert into test values ('200');
insert into test values ('1');
Here expected range in which value should be is 0-128 if values are not in range then i need to filter them out for further processing.
For this i have written some queries but none of then is giving requires output.
select *
from test
where val not between '0' and '128';
select *
from test
to_number(val, '9') not between to_number('0', '9') and to_number('128', '9999');
select * from test where
to_number(val, '9') < TO_NUMBER('0', '9')
or
to_number(val, '999') > TO_NUMBER('128', '999')
;
These above queries are producing desired output !! :(
I ma using DB version --
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
Just leave the format out of to_number():
select *
from test
where to_number(val) not between to_number('0') and to_number('128');
The numeric format is needed for conversion to a character. If you pass it in to to_number(), then it expects a number of that format -- and you might get the number of digits wrong.
Or, better yet:
select *
from test
where to_number(val) not between 0 and 128;
Or, even better yet, change the column to contain a number rather than a string.
EDIT:
If the problem is that your value is not a number (which is quite different from your original question), then test for that. This is one situation where case is appropriate in the where clause (because case guarantees the order of evaluation of its arguments:
where (case when regexp_like(val, '[^-0-9]') then 'bad'
when cast(val as number) < 0 then 'bad'
when cast(val as number) > 128 then 'bad'
else 'good'
end) = 'bad'
#GordonLinoff's answer works with the sample data you've shown, but it will error with ORA-01722 "invalid number" if you have any values which do no represent numbers. Your sample data only has good values, but you said that for your real field "any kind of data type is expected like date, numbers, alpha numeric, etc."
You can get around that with a function that attempts to convert the stored string value to a number, and returns null if it gets that exception. A simple example:
create function safe_to_number (p_str varchar2) return number is
begin
return to_number(p_str);
exception
when value_error then
return null;
end;
/
You can then do
select *
from test
where safe_to_number(val) not between 0 and 128;
VAL
----------
129
200
Anything that can't be converted and causes an ORA-06502 value-error exception will be seen as null, which is neither between nor not between any values you supply.
If you need to check date ranges you can do something similar, but there are more errors possible, and you may have dates in multiple formats; you would need to declare exceptions and initialise them to known error numbersto catch the ones you expect to see. This isn't complete, but you could start with something like:
create function safe_to_date (p_str varchar2) return date is
l_formats sys.odcivarchar2list;
format_ex_1 exception;
format_ex_2 exception;
format_ex_3 exception;
format_ex_4 exception;
format_ex_5 exception;
pragma exception_init(format_ex_1, -1840);
pragma exception_init(format_ex_2, -1841);
pragma exception_init(format_ex_3, -1847);
pragma exception_init(format_ex_4, -1858);
pragma exception_init(format_ex_5, -1861);
-- add any others you might get
begin
-- define all expected formats
l_formats := sys.odcivarchar2list('YYYY-MM-DD', 'DD/MM/YYYY', 'DD-MON-RRRR'); -- add others
for i in 1..l_formats.count loop
begin
return to_date(p_str, l_formats(i));
exception
when format_ex_1 or format_ex_2 or format_ex_3 or format_ex_4 or format_ex_5 then
-- ignore the exception; carry on and try the next format
null;
end;
end loop;
-- did not match any expected formats
return null;
end;
/
select *
from test
where safe_to_date(val) not between date '2016-02-01' and date '2016-02-29';
Although I wouldn't normally use between for dates; if you don't have any with times specified then you'd get away with it here.
You could use when others to catch any exception without having to declare them all, but even for this that's potentially dangerous - if something is breaking in a way you don't expect you want to know about it, not hide it.
Of course, this is an object lesson in why you should store numeric data in NUMBER columns and dates in DATE or TIMESTAMP fields - trying to extract useful information when everything is stored as strings is messy, painful and inefficient.
I think the best approach you can try in this condition is use
TRANSLATE function to eliminate the alphanumeric characters. Once its
done all now is OLD school technique to check the data by using NOT
BETWEEN function Hope this helps.
SELECT B.NM
FROM
(SELECT a.nm
FROM
(SELECT '0' AS nm FROM dual
UNION
SELECT '1' AS nm FROM dual
UNION
SELECT '68' AS nm FROM dual
UNION
SELECT '129' AS nm FROM dual
UNION
SELECT '200' AS nm FROM dual
UNION
SELECT '125a' AS nm FROM dual
)a
WHERE TRANSLATE(a.nm, ' +-.0123456789', ' ') IS NULL
)b
WHERE b.nm NOT BETWEEN 1 AND 128;

SQL MAX expression Function

In a SQL statement I need to select the expression as
SELECT maxValues AS MAX(MIN(100,x),50)
Where x is a result of another complex SQL query. Basically in the place of x I have another SQl select statement.
If I execute the above expression using select statement, I get the following error.
ERROR [42000] ERROR: 'maxValues as max(min(100,x),50)'
nullerror 'EXCEPT' or 'FOR' or 'INTERSECT' or 'ORDER' or 'UNION'
Any help is appreciated.
Use GREATEST and LEAST rather than MAX and MIN
GREATEST and LEAST give you the greatest and least values from a list of values whereas MAX and MIN give you the maximum and minimum values in a column.
You can use a transaction that declares a variable to transfer your value from one query to the next
DECLARE
V_X NUMBER;
V_RESULT NUMBER;
V_SQL_1 CLOB := "_QUERY 1_";
BEGIN
EXECUTE IMMEDIATE V_SQL_1 INTO V_X;
SELECT MAX(MIN(100,V_X),50) INTO V_RESULT FROM DUAL;
END
(This assumes oracle-SQL.)
I'd go with a CASE:
SELECT maxValues AS CASE when x > 100 then 100
when x < 50 then 50
else x end
(If supported... I don't know IBM Neteeza.)

function call within a function return type mismatch postgres

I have done as much research as I can on this I have look at about 20 post here and many more for Google. None seem to answer my question. So here it is:
I have a function the will need the data from cc_getbalancesfordate(theDate date) which returns a table like this:
currency balance
EUR 1.25
USD 0.98
....
currency is a char and balance is numeric. this function returns all the rates from the table currently like 33 row.
I am trying to call this function like this:
SELECT currency , balance, cc_ratecalculation(theDate,balance,currency)
FROM cc_getbalancesfordate(theDate);
its output is the same but it multiplies the balance by a rate found in another table.
both functions work independently. if I call it like this in a function such as
CREATE OR REPLACE FUNCTION cc_getbalancesfordatewitheurs(theDate date)
RETURNS TABLE(currency char, balance numeric(20)) AS $$
SELECT currency , balance, cc_ratecalculation(theDate,balance,currency)
FROM cc_getbalancesfordate(theDate) ORDER BY currency;
$$ LANGUAGE SQL;
SELECT *
FROM cc_getbalancesfordatewitheurs('2014-02-15'::date);
with that I get the error:
ERROR: return type mismatch in function declared to return record
DETAIL: Final statement returns too many columns.
CONTEXT: SQL function "cc_getbalancesfordatewitheurs"
********** Error **********
ERROR: return type mismatch in function declared to return record
SQL state: 42P13
Detail: Final statement returns too many columns.
Context: SQL function "cc_getbalancesfordatewitheurs"
So I try to add a 3rd column and it complains that the last column returns a record. I dont know how to fix this.
basicly I want it to return the data so it is in two columns currency and balance.
here is the code for the other function.
CREATE OR REPLACE FUNCTION cc_ratecalculation(
thedate date, balance numeric(15), fromcurrency char
)
RETURNS TABLE(Curency char, Balance numeric(20)) AS $$
SELECT *
FROM (
SELECT src as Exchange, rate*balance
FROM cc_rates
WHERE date=thedate AND src='KRA' AND "from"=fromcurrency AND "to"='BTC'
UNION
SELECT src as Exchange, rate*balance
FROM cc_rates
WHERE date=thedate AND src='BTE' AND "from"=fromcurrency AND "to"='BTC'
UNION
SELECT src as Exchange, rate*balance
FROM cc_rates
WHERE date=thedate AND src='CRY' AND "from"=fromcurrency AND "to"='BTC'
) a;
$$ LANGUAGE SQL;
and
CREATE OR REPLACE FUNCTION cc_getbalancesfordate(theDate date)
RETURNS TABLE(currency char, balance numeric(20)) AS $$
SELECT currency,sum(amount)
FROM cc_getbalancesfordate_withexchange(theDate) GROUP BY currency ;
$$ LANGUAGE SQL;
and
Here is what I figured out as an answer to my question.
DROP FUNCTION cc_getbalancesfordatewitheurs (date);
CREATE OR REPLACE FUNCTION cc_getbalancesfordatewitheurs(theDate date) RETURNS TABLE(from_currency char, to_currency char, Exchange char, rate numeric(20,10),current_balance numeric(20,10), converted_amount numeric(20,10)) AS $$
(SELECT currency AS from_currency,
(SELECT to_currency FROM cc_getratesfordate(theDate,balance,currency)),
(SELECT exchange FROM cc_getratesfordate(theDate,balance,currency)),
(SELECT rate FROM cc_getratesfordate(theDate,balance,currency)),
balance AS current_balance,
(SELECT final_balance FROM cc_getratesfordate(theDate,balance,currency))
FROM cc_getbalancesfordate(theDate) WHERE currency <> 'BTC' and currency <> 'EUR') ;
$$ LANGUAGE SQL;
SELECT * FROM cc_getbalancesfordatewitheurs('2014-02-15'::date);
its pretty ugly and inefficient but I only need to run this once a day so if it take a few extra milliseconds its not a problem.
Though if someone knows a more efficient way to write this I would sure appreciate the help.
Thanks to anyone who helps.