I have an Oracle function which accepts few IN parameters of number and varchar2 datatype and also it return a number datatype. Within the function, I'm selecting from a table with the IN parameters are applied in the 'WHERE' clause. By doing this, this filters is not applied to the select query instead it is skipping that condition. I need to explicitly declare a variables to store the values from IN parameter and then I need to use these variables in the WHERE clause. Not sure why this is required. Could you please help to resolve this without using the additional variables.
Below is the function, which I am using.
create or replace FUNCTION AGG_NC8B_FN (
FACTORY_ID NUMBER,
REPORTING_PERIOD NUMBER,
GRANULARITY_TYPE_TXT VARCHAR2,
GRANULAROTY_LEVEL_TXT VARCHAR2,
CONDITION_ID NUMBER)
RETURN NUMBER
IS
CAL_VALUE OCT_DMA_MOMIS.FCT_KSTACK_AGGREGATION.AGGREGATE_VALUE_DEC%TYPE;
-- temporary variables --not sure why this is required?
A number;
B number;
C varchar2(100);
D varchar2(100);
E number;
BEGIN
A:=FACTORY_ID;
B:=REPORTING_PERIOD;
C:=GRANULARITY_TYPE_TXT;
D:=GRANULAROTY_LEVEL_TXT;
E:=CONDITION_ID;
SELECT DECODE(B.AGGREGATE_VALUE_DEC,0,0,((A.AGGREGATE_VALUE_DEC/B.AGGREGATE_VALUE_DEC)*100))
INTO CAL_VALUE
FROM (SELECT AGGREGATE_VALUE_DEC
FROM OCT_DMA_MOMIS.FCT_KSTACK_AGGREGATION
WHERE FK_FIGURE_ID =30121
AND FK_FACTORY_ID =A
AND LK_REPORTING_PERIOD =B
AND GRANULARITY_TYPE_TXT=C
AND GRANULAROTY_LEVEL_TXT=D
AND FK_CONDITION_ID =E) A ,
(SELECT AGGREGATE_VALUE_DEC
FROM OCT_DMA_MOMIS.FCT_KSTACK_AGGREGATION
WHERE FK_FIGURE_ID =30122
AND FK_FACTORY_ID =A
AND LK_REPORTING_PERIOD =B
AND GRANULARITY_TYPE_TXT=C
AND GRANULAROTY_LEVEL_TXT=D
AND FK_CONDITION_ID =E
) B ;
RETURN CAL_VALUE;
END AGG_NC8B_FN;
Your function parameters and columns have same names (GRANULARITY_TYPE_TXT for example). Use different names or qualify function parameters with the name of the function:
-- wrong:
declare
function f(object_name in varchar2) return number is
l_id number;
begin
select object_id
into l_id
from all_objects
where object_name = object_name -- equals to (object name is not null)
and object_type = 'VIEW';
return l_id;
end;
begin
dbms_output.put_line(f('USER_OBJECTS'));
end;
> ORA-01422: exact fetch returns more than requested number of rows
-- right way:
declare
function f(object_name in varchar2) return number is
l_id number;
begin
select object_id
into l_id
from all_objects
where object_name = f.object_name -- subprogram_name.parameter_name
and object_type = 'VIEW';
return l_id;
end;
begin
dbms_output.put_line(f('USER_OBJECTS'));
end;
> 12345
Related
I have the following SQL query that will return the name of a column in a specific table. Let's say it return 'USER_PK' as column name when it runs.
the query:
SELECT max(COLUMN_NAME)
FROM ALL_TAB_COLUMNS
WHERE OWNER= 'DW_01'
AND table_name='D_O_USERS'
AND COLUMN_NAME<>'USER_PK';
Now I would like to run the above query as part of a function but instead of running it and storing the value it returns in a variable (using INTO or attribution like initial_sql: = '...', followed by exec ) I would need to have it run inside one line of code as below (see part in bold)... So far I have been unsuccessful as it is interpreted as a string when using quotes ...
CREATE OR REPLACE function DW_01.EXECUTE_AUTO (db_schema IN VARCHAR2, db_table IN VARCHAR2, pk_name IN VARCHAR2, id_pk IN INTEGER) RETURN VARCHAR2
IS
result VARCHAR2(4000);
begin
EXECUTE IMMEDIATE 'select STANDARD_HASH( '|| **SELECT max( COLUMN_NAME) FROM ALL_TAB_COLUMNS WHERE OWNER='' || db_schema || '' AND table_name=''||db_table ||'' AND COLUMN_NAME<>'' ||pk_name ||'** ,''SHA512'' ) from '||db_table||' where '|| pk_name ||'='||id_pk into RESULT ;
return result;
end;
Many thanks in advance for your thoughts!
You need to amend you r code like below -
CREATE OR REPLACE function DW_01.EXECUTE_AUTO (db_schema IN VARCHAR2,
db_table IN VARCHAR2,
pk_name IN VARCHAR2,
id_pk IN INTEGER) RETURN VARCHAR2
IS
result VARCHAR2(4000);
begin
EXECUTE IMMEDIATE 'select STANDARD_HASH( ' || pk_name || ',256 )
from '||db_table||' where '|| pk_name ||'='||id_pk into RESULT;
return result;
end;
/
There are only a few ways to run dynamic SQL in SQL, and they're neither pretty nor fast. The function below uses DBMS_XMLGEN.GETXML to dynamically run a SQL statement.
create or replace function execute_auto(db_schema in varchar2, db_table in varchar2, pk_name in varchar2, id_pk in integer) return varchar2
is
v_column_name varchar2(128);
v_result varchar2(4000);
begin
select standard_hash(to_number(extractValue(xml_results, '/ROWSET/ROW/' || max_column)), 'SHA512') hash_value
into v_result
from
(
--Create a single XML file with the ROWIDs that match the condition.
select max(column_name) max_column, xmltype(dbms_xmlgen.getxml
('
select '||max(column_name)||'
from '||db_schema||'.'||db_table||'
where id = '||id_pk
)) xml_results
from all_tab_columns
where owner = db_schema
and table_name = db_table
and column_name <> pk_name
);
return v_result;
end;
/
For example, let's create this sample table with 100,000 rows:
--drop table test1;
create table test1(id number, a number, b number, constraint pk_test1 primary key(id));
insert into test1
select level, level, level from dual connect by level <= 100000;
commit;
This shows how to use the function;
select execute_auto(user, 'TEST1', 'ID', id) hash
from test1 where id = 1;
HASH
----
A36753F534728ED84A463ECB13750B8E920A7E4D90244258DE77D9800A0F3DAF8CBAD49602E960A2355933C689A23C30377CE10FC4B8E1F197739FF86C791022
In addition to problems with type conversion and SQL injection, the performance is terrible. Selecting all 100,000 rows this way takes 200 seconds on my machine.
select sum(length(execute_auto(user, 'TEST1', 'ID', id))) from test1;
Normally, running everything in a single SELECT statement is a good way to improve performance. But this extreme type of dynamic SQL will never run fast. You probably want to rethink your approach. Instead of trying to optimize the SQL inside a function that is run one-row-at-a-time, try to change the process to process once-per-table
How to create a function to display all employees of one department?
I'd tried this code but it returns only "cursor" value.
CREATE OR REPLACE FUNCTION emp_dept (dept_id IN NUMBER)
RETURN SYS_REFCURSOR
IS
emp_name SYS_REFCURSOR;
BEGIN
OPEN emp_name
FOR SELECT last_name
FROM employees
WHERE department_id = dept_id;
RETURN emp_name;
END emp_dept;
You may use these options to read and display output from the cursor that's returned from your function.
Use a simple select
select emp_dept(10) from dual;
Result
EMP_DEPT(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
LAST_NAME
-------------------------
Whalen
Use DBMS_SQL.RETURN_RESULT ( Oracle 12c and above)
DECLARE
l_cur SYS_REFCURSOR := emp_dept(10) ;
BEGIN
DBMS_SQL.RETURN_RESULt(l_cur);
END;
/
Result
PL/SQL procedure successfully completed.
ResultSet #1
LAST_NAME
-------------------------
Whalen
FETCH from the cursor into a local collection. A slight variation could be also be used to fetch into a scalar variable.
DECLARE
l_cur SYS_REFCURSOR := emp_dept(10) ;
TYPE l_last_name_tab IS TABLE OF employees.last_name%TYPE;
l_lnt l_last_name_tab;
BEGIN
FETCH l_cur BULK COLLECT INTO l_lnt;
for i in 1..l_lnt.count
loop
dbms_output.put_line(l_lnt(i));
end loop;
END;
/
Result
Whalen
PL/SQL procedure successfully completed.
I'm trying to return the number of rows per invoice_id using a function and procedure. Some invoice_id's have more than one row and I'm not sure how to fetch the count when I execute my procedure. As an example invoice_id(7) has just one row, but invoice_id(100) has four rows of information.
Create or replace function return_num_rows_function(invoice_id_text in varchar2)
Return varchar2
Is inv_id varchar2(20);
Begin
Select count(*)invoice_id into inv_id from invoice_line_items where invoice_id=invoice_id_text;
Return inv_id;
End;
Create or replace procedure return_num_rows (invoice_id_text in varchar2)
Is inv_id varchar(20);
line_item_desc invoice_line_items.line_item_description%type;
Begin
inv_id := return_num_rows_function(invoice_id_text);
If inv_id is not null then
Select count(*)invoice_id, line_item_description into inv_id,line_item_desc
From invoice_line_items where invoice_id = inv_id;
dbms_output.put_line('The number of rows returned:'|| inv_id);
dbms_output.put_line('Item description(s):'|| line_item_desc);
End if;
End;
set serveroutput on;
execute return_num_rows(7);
First of all do not use a string type variable for a numeric one
(invoice_id_text).
For your case it's better to use a procedure instead of called
function ( return_num_rows_function ), since you need two out
arguments returned.
A SQL Select statement cannot be used without Group By with aggegated and non-aggregated columns together ( i.e. don't use this one :
Select count(*) invoice_id, line_item_description
into inv_id,line_item_desc
From invoice_line_items
Where invoice_id = inv_id;
)
So, Try to create below procedures :
SQL> CREATE OR REPLACE Procedure
return_num_rows_proc(
i_invoice_id invoice_line_items.invoice_id%type,
inv_id out pls_integer,
line_item_desc out invoice_line_items.line_item_description%type
) Is
Begin
for c in
(
Select line_item_description
into line_item_desc
From invoice_line_items
Where invoice_id = i_invoice_id
)
loop
line_item_desc := line_item_desc||' '||c.line_item_description;
inv_id := nvl(inv_id,0) + 1;
end loop;
End;
/
SQL> CREATE OR REPLACE Procedure
return_num_rows(
i_invoice_id pls_integer
) Is
inv_id pls_integer;
line_item_desc invoice_line_items.line_item_description%type;
Begin
return_num_rows_proc(i_invoice_id,inv_id,line_item_desc);
If inv_id is not null then
dbms_output.put_line('The number of rows returned:' || inv_id);
dbms_output.put_line('Item description(s):' || line_item_desc);
End if;
End;
/
and call as in your case :
SQL> set serveroutput on;
SQL> execute return_num_rows(7);
Replace inv_id varchar2(20) with inv_id number;
and also if you want to get two outputs from procedure better to use refcursor.
I want to write a function to return the row count of a table whose name is passed in as a variable. Here's my code:
create or replace function get_table_count (table_name IN varchar2)
return number
is
tbl_nm varchar(100) := table_name;
table_count number;
begin
select count(*)
into table_count
from tbl_nm;
dbms_output.put_line(table_count);
return table_count;
end;
I get this error:
FUNCTION GET_TABLE_COUNT compiled
Errors: check compiler log
Error(7,5): PL/SQL: SQL Statement ignored
Error(9,8): PL/SQL: ORA-00942: table or view does not exist
I understand that tbl_nm is being interpreted as a value and not a reference and I'm not sure how to escape that.
You can use dynamic SQL:
create or replace function get_table_count (table_name IN varchar2)
return number
is
table_count number;
begin
execute immediate 'select count(*) from ' || table_name into table_count;
dbms_output.put_line(table_count);
return table_count;
end;
There is also an indirect way to get number of rows (using system views):
create or replace function get_table_count (table_name IN varchar2)
return number
is
table_count number;
begin
select num_rows
into table_count
from user_tables
where table_name = table_name;
return table_count;
end;
The second way works only if you had gathered statistics on table before invoking this function.
I have an oracle Procedure and I want to set the default parameter to the result of a select statement such as below.
procedure foo( startID number : = max(x.id) from xtable x )
but the code above is illegal syntax.
You can use an otherwise unvalid value for this meaning, for example NULL:
PROCEDURE foo (p_id NUMBER DEFAULT NULL) IS
l_startID NUMBER := p_id;
BEGIN
IF p_id IS NULL THEN
SELECT max(id) INTO l_startID FROM xtable;
END IF;
...
END;
If your function/procedure is in a package, you could also overload it:
PROCEDURE foo (p_id NUMBER)
[...]
PROCEDURE foo IS
l_startID NUMBER;
BEGIN
SELECT max(id) INTO l_startID FROM xtable;
foo(l_startID);
END;