How to use variables in Oracle PL/SQL Function - oracle

I'm sorry upfront because this question seems to easy.
I have this function:
CREATE OR REPLACE FUNCTION Costs_MK (VIEWNAME IN VARCHAR2 , WHERE_CLAUSE IN VARCHAR2)
RETURN VARCHAR2
IS
v_Costs VARCHAR2 (500);
BEGIN
Select Listagg(Costs, ';' ) WITHIN GROUP (ORDER BY Costs)
into v_Costs
from (select distinct (Costs)
from VIEWNAME
where WHERE_CLAUSE);
RETURN v_Costs;
END Costs_MK;
However I get the Error-Message:
Error(13,30): PL/SQL: ORA-00920: invalid relational operator
I even can't compile it. If I use the exact values for Viewname and Where_clause I get the desired result.
What am I doing wrong?
/edit: Line 13 is
from VIEWNAME
/edit #2:
Thanks guys. You helped me a lot. I didn't thought about dynamic sql in the first step, so thanks for the refresher ;).

I suggest you to add EXCEPTION BLOCK along with EXECUTE IMMEDIATE
I have created a PROCEDURE you can similary create FUNCTION
CREATE OR REPLACE procedure Costs_PK(VIEWNAME IN VARCHAR2 , WHERE_CLAUSE IN VARCHAR2 )
AS
v_Costs VARCHAR2 (500);
sql_stmnt varchar2(2000);
BEGIN
sql_stmnt := 'Select Listagg(Cost, '';'' ) WITHIN GROUP (ORDER BY Cost) from (select distinct (Cost) from ' || VIEWNAME || ' where ' || WHERE_CLAUSE || ' ) ';
--sql_stmnt := 'Select Listagg(Cost, '';'' ) WITHIN GROUP (ORDER BY Cost) from (select distinct (Cost) from cost_tab where cost >=123 ) ';
EXECUTE IMMEDIATE sql_stmnt INTO v_Costs ;
dbms_output.put_line ('query works -- ' || v_costs);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('input :' || VIEWNAME || ' and ' || WHERE_CLAUSE );
dbms_output.put_line (sql_stmnt );
DBMS_OUTPUT.PUT_LINE ('ERROR MESSAGE : ' || sqlCODE || ' ' || SQLERRM );
END;
begin
Costs_PK('cost_tab','cost >= 123');
end;
NOTE: code has been Tested
output:
query works -- 123;456

This is one of the areas in PL/SQL where the most straightforward static SQL solution requires code duplication as there is no way to parametrize the table name in a query. Personally I usually favor duplicate code of static SQL over the increased complexity of dynamic SQL as I like PL/SQL compiler to check my SQL compile time. YMMV.
You don't tell us what kind of where statements the different views are having. In the example below I assume there is 1:1 relation between the view and where parameter(s) so I can easily build static SQL.
create or replace view foo_v (foo_id, cost) as
select level, level*10 from dual connect by level < 10
;
create or replace view bar_v (bar_id, cost) as
select level, level*100 from dual connect by level < 10
;
create or replace function cost_mk(
p_view in varchar2
,p_foo_id in number default null
,p_bar_id in number default null
) return varchar2 is
v_cost varchar2(32767);
begin
case lower(p_view)
when 'foo_v' then
select listagg(cost, ';' ) within group (order by cost)
into v_cost
from (select distinct cost
from foo_v
where foo_id < p_foo_id);
when 'bar_v' then
select listagg(cost, ';' ) within group (order by cost)
into v_cost
from (select distinct cost
from bar_v
where bar_id < p_bar_id);
end case;
return v_cost;
end;
/
show errors
Usage example
select cost_mk(p_view => 'foo_v', p_foo_id => 5) from dual;
select cost_mk(p_view => 'bar_v', p_bar_id => 5) from dual;

You would want to use EXECUTE IMMEDIATE as was hinted by the comments to your question, define a new variable sqlQuery VARCHAR2(200); or similar and rework your sql similar to the following:
sqlQuery := 'Select Listagg(Costs, '';'' ) WITHIN GROUP (ORDER BY Costs) ';
sqlQuery := sqlQuery || 'from (select distinct (Costs) from :1 where :2)';
EXECUTE IMMEDIATE sqlQuery INTO v_Costs USING VIEWNAME, WHERE_CLAUSE;

Related

Step out of a query to get where clauses from a separate table, within a stored procedure

I have a procedure which I'm using to output row counts to a .csv file but some of the where clauses I may want to use are contained in a table. How can I use them to create conditions for the counts?
I've attempted using concatenation pipes to select against the table that holds the where clauses but I'm confused about syntax and where they should go and I believe this is where I need the most help.
These are the columns in the table that contains some of the where clauses I ultimately want to use in the procedure.
SCHEMA, DATABASE, FULL_TABLE, DRIVER_TABLE, MAND_JOIN
And the values may be such as:
PROD, DB1, RLTSHP, BOB.R_ID, A.AR_ID = B.AR_ID
The procedure I have written is as follows:
create or replace procedure PROJECT is
--variables
l_dblink varchar2(100) := 'DB1';
ROW_COUNT number;
file_handle UTL_FILE.file_type;
BEGIN
utl_file.put_line(file_handle, 'OWNER,TABLE_NAME,ROW_COUNT');
--main loop
for rws in (select /*+parallel */ owner, table_name
from dba_tables#DB1 a
where table_name in (select table_name
from meta_table
where driver_table is not null
and additional_joins is null)
and a.owner in (select distinct schema
from meta_table c)
order by table_name)
loop
execute immediate 'select count(*) from ' ||rws.owner||'.'||rws.table_name || '#' || l_dblink into ROW_COUNT;
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
ROW_COUNT);
end loop;
END PROJECT;
/
However, instead of the simple select count(*) reflected in the above, I want to find a way to include data in the meta_table to construct "where" clauses that use table joins to limit the output so that I'm not counting all rows, but rows that meet the criteria in the join I've constructed.
For example, so that the actual count that gets executed will be something like this:
select count(*)
from PROD.RLTSHP#DB1 b,
BOB.R_ID#DB1 a
where A.AR_ID = B.AR_ID;
Essentially I would be constructing the query using the entries in the meta_table. I think I can do this with concat's / pipes but I'm not sure exactly how to.
Can you help?
You need to extend your simple statement to assemble the join criteria as well. The one catch is that you must give the tables aliases which match the aliases used in additional_joins i.e. B for FULL and A for DRIVER. These have to be standard for all rows in your META_TABLE otherwise you will generate invalid SQL.
create or replace procedure PROJECT is
l_dblink varchar2(100) := 'DB1';
ROW_COUNT number;
file_handle UTL_FILE.file_type;
v_sql varchar2(32767);
BEGIN
utl_file.put_line(file_handle, 'OWNER,TABLE_NAME,ROW_COUNT');
<< main_loop >>
for rws in (select mt.*
from dba_tables#DB1 db
join meta_table mt
on mt.driver_table = db.table_name
and mt.owner = db.owner
where mt.db_link = l_dblink
order by mt.table_name)
loop
-- simple query
v_sql := 'select count(*) from ' || rws.owner||'.'||rws.driver_table || '#' || l_dblink;
-- join query
if rws.additional_joins is not null
and rws.full_table is not null then
v_sql := v_sql|| ' b, '|| rws.full_table ||'#'||l_dblink|| ' a where ' ||rws.additional_joins;
end if;
-- uncomment this for debugging
--dbms_output.put_line(v_sql);
execute immediate v_sql into ROW_COUNT;
utl_file.put(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
utl_file.put_line(file_handle, ROW_COUNT);
end loop main_loop;
END PROJECT;
/
Notes
We have to use a variable to assemble the statement because the final SQL is conditional on the contents of a row. This enables efficient debugging because we have something we can display. Dynamic SQL is hard, because it turns compilation errors into runtime errors. Diagnosis is difficult when we can't see the actual executed code.
I have tweaked your driving query to make the joins safer.
The column names you used in the code are not consist with the column names you used for the table structure. So there may be naming bugs which you'll need to fix for yourself.
I have retained the Old Skool implicit join syntax. I was tempted to generate ANSI 92 SQL (inner join ... on) but it's not clear that the additional_joins will contain only join criteria.
Pro tip. Instead of commenting your loops - --main loop - use an actual PL/SQL label - <<main_loop>> so you can link the matching end loop statement, as I have done in this code.
Improvements you may want to add:
validate that FULL_TABLE exists in target database
include FULL_TABLE in UTL_FILE output
validate that columns referenced in ADDITIONAL_JOIN are valid (using DBA_TAB_COLUMNS, but it's trickier because you will have to parse the column names from the text)
worry about whether the content of ADDITIONAL_JOIN is a valid and complete join condition
First of all I don't recommend to use PARALLEL hint. It can kill your db if you will have a lot of queries with PARALLEL hints.
I assume that columns MAND_JOIN means, that always we have value there.
create or replace procedure PROJECT is
lc_sql_template CONSTANT varchar2(4000) :=
'select count(*) ' || CHR(10) ||
' from #TableOwner.#TableName#DB1 b' || CHR(10) ||
' inner join #FullTableName#DB1 a ON #JoinCodition';
lv_row_count number;
lv_file_handle UTL_FILE.file_type;
lv_sql varchar2(32767);
BEGIN
utl_file.put_line(lv_file_handle, 'OWNER,TABLE_NAME,ROW_COUNT');
for rws in (select mt.*
from dba_tables#DB1 db
inner join meta_table mt
on mt.driver_table = db.table_name
and mt.owner = db.owner
where mt.driver_table is not null
and mt.additional_joins is null
order by mt.table_name)
loop
lv_sql := lc_sql_template;
lv_sql := replace(lv_sql, '#TableOwner' , rws.owner);
lv_sql := replace(lv_sql, '#TableName' , rws.driver_table);
lv_sql := replace(lv_sql, '#FullTableName' , rws.full_table);
lv_sql := replace(lv_sql, '#JoinCodition' , rws.mand_join);
$if $$DevMode = true $then -- I even recommand to log this all the time
your_log_package.info(lv_sql);
$end
execute immediate lv_sql into lv_row_count;
utl_file.put(lv_file_handle, rws.OWNER || ',' || rws.TABLE_NAME || ',' || lv_row_count);
end loop main_loop;
exception
when others then
your_log_package.error(lv_sql);
raise;
end PROJECT;

How to return a record set from a Function in Oracle

I am trying to get the record data using function by passing values
please find the below
CREATE TABLE "TEST"
( "TEST_ID" NUMBER(9,0) NOT NULL ENABLE,
"TEST_DESC" VARCHAR2(30 BYTE),
"TEST_DATE" DATE
);
create or replace TYPE TEST_OBJ_TYPE IS OBJECT
(
TEST_ID NUMBER(9),
TEST_DESC VARCHAR(30),
dates date
);
create or replace TYPE TEST_TABTYPE AS TABLE OF TEST_OBJ_TYPE;
Using the above object and table type created the function as follows
create or replace FUNCTION GET_ROWS(dates date)RETURN TEST_TABTYPE
AS
V_Test_Tabtype Test_TabType;
table_name varchar2(30);
q1 varchar2(300);
BEGIN
table_name :='Test';
q1 := 'SELECT TEST_OBJ_TYPE(A.TEST_ID, A.TEST_DESC)FROM' || '
(SELECT TEST_ID, TEST_DESC FROM ' || table_name || ' where
TEST_DATE = '''||dates||''' ) A';
dbms_output.put_line(q1);
EXECUTE IMMEDIATE q1 BULK COLLECT INTO V_Test_TabType ;
RETURN V_Test_TabType;
EXCEPTION
WHEN OTHERS THEN
v_Test_TabType.DELETE;
RETURN v_Test_TabType;
END;
When I execute this the SQL is printing correctly but not giving the record value.
Error as follows:
select (GET_ROWS('01-08-18')) from dual
Error report -
ORA-02315: incorrect number of arguments for default constructor
ORA-06512: at "AMTEL_MIS.GET_ROWS", line 13
SELECT TEST_OBJ_TYPE(A.TEST_ID, A.TEST_DESC) FROM (SELECT TEST_ID, TEST_DESC FROM Test where TEST_DATE = '01-08-18' ) A
Please assist me further
Thanks in advance
Your type TEST_OBJ_TYPE is defined with three attributes: TEST_ID, TEST_DESC, DATES. However, your query populates the constructor with just two columns:
SELECT TEST_OBJ_TYPE(A.TEST_ID, A.TEST_DESC) FROM
You're missing a value for DATES and that's why Oracle hurls ORA-02315.
I have tried as per your suggestion but it's is giving me an error
ORA-00904: "A"."DATES": invalid identifier
Because of the convoluted way your function is written you need to include TEST_DATE (or dates) in both the subquery and the object constructor:
q1 := 'SELECT TEST_OBJ_TYPE(A.TEST_ID, A.TEST_DESC,A.TEST_DATE)FROM' || ' -- here!
(SELECT TEST_ID, TEST_DESC, TEST_DATE FROM ' -- and here!
|| table_name || ' where TEST_DATE = '''||dates||''' ) A';
If you do that your code will work. Here is a LiveSQL demo of your code with the fix. (Free Oracle login required).
As it seems likely that you will want to pass in the table name so here is a version of your code which does that:
create or replace function get_rows(dates date, p_table_name in varchar2)
return test_tabtype
as
v_test_tabtype test_tabtype;
q1 varchar2(300);
begin
q1 := 'select test_obj_type(a.test_id, a.test_desc,a.test_date) from'
|| '(select test_id, test_desc, test_date from '
|| p_table_name
|| ' where test_date = :1 ) a';
dbms_output.put_line(q1);
execute immediate q1
bulk collect into v_test_tabtype
using dates ;
return v_test_tabtype;
exception
when others then
v_test_tabtype.delete;
return v_test_tabtype;
end;
Note how much easier it is to understand the code when it is laid out with consistent use of case and regular indentation. Readability is a feature!

Oracle PL/SQL function

I have a function for confluence 52 columns
create or replace FUNCTION get_one_row(i_code IN integer) RETURN CLOB IS
l_columns VARCHAR2(2000);
l_res CLOB;
BEGIN
SELECT listagg(column_name,' || ') WITHIN GROUP(ORDER BY column_name ASC) AS GRAFIK
INTO l_columns
FROM user_tab_columns
WHERE TABLE_NAME = 'GRAFIK';
EXECUTE IMMEDIATE 'SELECT '||l_columns||' FROM grafik WHERE kod_sotr=:A' INTO l_res USING i_code;
RETURN l_res;
END;
The table Grafik has kod of worker, year and weeks, where
designated their holidays letter y or o
On a exit function displays
2017109909уууууооооо
First, meaning in conclutions is very fused, and not comfortable browse their. How devided the meaning?
You could edit your dynamic SQL to add a separator between the columns; for example:
SELECT listagg(column_name,' || '', '' || ') WITHIN GROUP(ORDER BY column_name ASC) AS GRAFIK
will add a comma between the values of the columns

Cursor Operation in Netezza

CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
P_REC.EMPID ||
',' ||
P_REC.MGRID ||
',' ||
P_REC.EMPNAME ||
',' ||
P_REC.SALARY ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
When I run this:
select SP_NEW_PROCEDURE1()
I get the errors:
ERROR [01000] NOTICE: Error occurred while executing PL/pgSQL function SP_NEW_PROCEDURE1
ERROR [01000] NOTICE: line 24 at execute statement
ERROR [42S22] ERROR: Attribute 'DAN' not found
Can someone help whats wrong ...thanks
This has nothing do with the cursor itself, and everything to do with how you are building your dynamical SQL string.
When building dynamic SQL in a Netezza stored procedure, you can use the quote_ident and quote_literal helper functions to let the system know whether you are passing it a literal, or whether you are passing it an identifier. There is an example in the online documentation here. Essentially all they do is figure out the escaped quotation notation needed.
Since you are trying to put the values stored in the columns of your P_REC record into the VALUES part of an insert statement, you could use quote_literal like this:
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
quote_literal(P_REC.EMPID) ||
',' ||
quote_literal(P_REC.MGRID) ||
',' ||
quote_literal(P_REC.EMPNAME) ||
',' ||
quote_literal(P_REC.SALARY ) ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
That being said, using a cursor to loop over records to insert a row one at a time is horribly inefficient in an MPP database like Netezza. Assuming this question is a follow-on from your question about an alternative to a recursive CTE to explore hierarchies, there's nothing wrong with looping in general, but try to avoid doing it record by record. Here is a version that will exploit the MPP nature of the system. For the record, if you are going to return your result set to a REFTABLE, then your only choice is Dynamic SQL.
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
-- FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
-- LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' select empid, mgrid, empname, salary from employees where mgrid = 7 ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
-- END LOOP;
RETURN REFTABLE;
END;
END_PROC;
I suspect that you are building a query that is meant to insert a literal 'DAN' but which does not include the required quote marks, hence it is referencing DAN -- the optimiser is therefore trying to find an attribute of that name.
So the fix is to include the quotation marks when you build the SQL insert statement, or (preferably) to just use static SQL to insert the values instead of execute immediate.
When in doubt, always look at the data, as this would probably have been obvious to you if you inspected the value of l_conditions.

Dynamically assigning variables oracle sql

I have a table attribute_config with below columns:
table_name column_name key
Let us say is has below 2 rows
account accountphone accountnum
customer customernumber customerid
Key can be only accountnum or customerid.
I have to write code which will accept (i_accountnum,i_customerid) and;
fetch the respective values from columns mentioned in column_name in tables mentioned in table_name using the key in where condition.
For ex: select accountphone from account where accountnum = i_accountnum
select customernumber from customer where customerid = i_customerid
the complete query should be formed dynamically, whether to pass i_accountnum or i_customerid in the query also needs to be decided dynamically. if key - accountnum, i_accountnum will be passed to where condition.
I have been trying on these lines so far, this is not working, i know it is wrong.
declare
v_accountnum varchar2(20);
v_customerid varchar2(20);
v_attribute_value varchar2(20);
v_stmt varchar2(255);
begin
Account_Num := 'TestCustomer'; -- input to the function
v_customer_ref := 'TestAccount'; -- input to the function
for i in (Select * from attribute_config) loop
v_stmt := 'select ' || i.column_name || ' from ' || i.table_name ||' where ' || i.key|| ' = v_' || i.key;
execute immediate v_Stmt into v_attribute_value;
end loop;
end;
This will fix your code, but I do not see any advantage of using dynamic query when your code should accept 2 parameters(i_accountnum,i_customerid) - which is already static situation and fetch the relevant values, perhaps only in learning purposes.
declare
procedure fecth_values(i_accountnum account.accountnum%type,
i_customerid customer.customerid%type) return varchar2 is
v_attribute_value varchar2(20);
begin
for i in (select * from attribute_config) loop
execute immediate 'select ' || i.column_name || ' from ' ||
i.table_name || ' where ' || i.key || ' = ' || case when i.key = 'accountnum' then i_accountnum when i.key = 'customerid' then i_customerid end;
into v_attribute_value;
dbms_output.put_line(v_attribute_value);
end loop;
return null;
end;
begin
fecth_values(1, 1);
end;
Your where clause was wrong the i.key should be compared against the inputed values, not the 'v_' || i.key, which is undeclared when you execute your stmt.

Resources