Dynamic Function- Oracle - oracle

I am trying to create a function by giving 2 variables as inputs. These 2 variables are used dynamically in the function. Am using a select statement in the beginning of the loop to find the primary key column in a particular table. This column is assign to a variable value1. Am using this value1 variable as a sequence variable.
create FUNCTION Test(schemaname in varchar2, tablename in varchar2)
return number
IS cnpParmId NUMBER;
good VARCHAR(1) := 'F';
exist VARCHAR(1) := 'F';
value1 varchar2(500);
begin
good := 'F';
exist := 'F';
loop
SELECT cols.column_name into value1
FROM all_constraints cons, all_cons_columns cols
WHERE cols.TABLE_NAME= 'tablename'
And cols.OWNER='schemaname'
And cons.constraint_type = 'P'
AND cons.constraint_name = cols.constraint_name
AND cons.owner = cols.owner
ORDER BY cols.table_name, cols.position;
select schemaname.value1_seq.nextval into cnpParmId from dual;
begin
select 'T' into good from dual
where cnpParmId not in
(select value1 from schemaname.tablename);
exception when NO_DATA_FOUND then good := 'F';
end;
exit when good = 'T';
end loop;
return cnpParmId;
end;
/
Test(XYZ,ABC);
but am getting the following errors:
Error(21,11): PLS-00487: Invalid reference to variable 'SCHEMANAME'
Error(21,22): PL/SQL: ORA-02289: sequence does not exist
Error(23,7): PL/SQL: SQL Statement ignored
Error(25,38): PL/SQL: ORA-00942: table or view does not exist

When you introduce variables as table or column names you need to do a dynamic query using EXECUTE IMMEDIATE.
Your sequence query should be:
execute immediate 'select ' || schemaname || '.nextval from dual' into cnpParmId;
And your "get the return value" query should be:
execute immediate
'select ''T'' from dual where cnpParmId not in ' ||
'(select value1 from ' || schemaname || '.' || tablename || ')' into good;
Also note that your query to get value1 is looking for the literal values schemaname and tablename:
SELECT cols.column_name into value1
FROM all_constraints cons, all_cons_columns cols
WHERE cols.TABLE_NAME= 'tablename'
And cols.OWNER='schemaname'
... and so on
You can use variables to represent values, so just get rid of the single quotes around the variable names:
SELECT cols.column_name into value1
FROM all_constraints cons, all_cons_columns cols
WHERE cols.TABLE_NAME= tablename
And cols.OWNER=schemaname
... and so on

Related

Can execute immediate be used with JSON_TABLE

I need to convert JSON into data table (key value columns) in Oracle 12c v12.1.0.2
So for example there is a JSON string like
{"ID": 10, "Description": "TestJSON", "status":"New"}
I need this converted to :
Column1 Column2
------------------------------------
ID 10
Description TestJSON
status New
Now my JSON string could change the number of attributes and hence I require to keep the conversion dynamic.
I tried using execute immediate :
set serveroutput on;
declare
sqlsmt VARCHAR2(200);
t3 varchar2(50);
begin
sqlsmt := 'SELECT * '||
'FROM json_table( ( select jsonstr from mytable where ID= 10) , ''$[*]'' '||
'COLUMNS ( :t1 PATH ''$.''|| '':t2'' ))';
execute immediate sqlsmt into t3 using 'desc' , '$.Description' ;
DBMS_OUTPUT.PUT_LINE( 'Output Variable: ' || t3);
END;
However, I get the following error:
ORA-00904: : invalid identifier
ORA-06512: at line 8
00904. 00000 - "%s: invalid identifier"
Please help. I have Oracle 12c V1. But I really need to pull columns dynamically from JSON.
There are a couple of things that can help with dynamic SQL (assuming you really need to use it). The first is to use dbms_output to show the generated statement before you try to execute it; so in your case:
...
dbms_output.put_line(sqlsmt);
execute immediate sqlsmt into t3;
--using 'descr' , '$.Description' ;
DBMS_OUTPUT.PUT_LINE( 'Output Variable: ' || t3);
END;
/
with your code that shows:
SELECT * FROM json_table( ( select jsonstr from mytable where ID= 10) , '$[*]' COLUMNS ( :t1 PATH '$.'|| ':t2' ))
The most obvious issue there is in '$.'|| ':t2', where :t2 shouldn't be in quotes; that isn't causing the error but would stop it being bound to your variable as you expect as it's a literal value. You also have the $. part in that bit and in your variable value, but again it isn't getting that far.
In common with all dynamic SQL, you can only supply values for variables in the using clause. You're trying to pass the column name as a bind variable, which isn't allowed; so it's trying to use :t1 as the output column name, not desc; and :t1 isn't a valid name. (Neither is desc as that's a reserved word - but either gets the same error.) So, you have to concatenate the column name in rather than binding it.
It looks like you would be able to use :t2 for the path though; but you you can't do that either, not as a dynamic SQL restriction but as a SQL/JSON one - if you got that far, with a valid variable value, you'd still get "ORA-40454: path expression not a literal". You have to concatenate the path into the statement too.
Finally the $[*] doesn't allow you to match the Description... which leads to the second hint about dynamic SQL; get a static query working properly first, then make that dynamic.
So putting that together, you could do:
declare
sqlsmt varchar2(200);
t1 varchar2(30) := 'descr';
t2 varchar2(30) := 'Description';
t3 varchar2(50);
begin
sqlsmt := 'SELECT * '||
'FROM json_table( ( select jsonstr from mytable where ID= 10) , ''$'' '||
'COLUMNS ( ' || t1 || ' PATH ''$.' || t2 || '''))';
dbms_output.put_line(sqlsmt);
execute immediate sqlsmt into t3;
dbms_output.put_line( 'Output Variable: ' || t3);
end;
/
which with your example data outputs:
SELECT * FROM json_table( ( select jsonstr from mytable where ID= 10) , '$' COLUMNS ( descr PATH '$.Description'))
Output Variable: TestJSON
It's a bit odd that the only thing you are allowed to pass as a variable, the 10, is hard-coded. But I get this is an experiment.
You could also write the statement as:
select j.*
from mytable t
cross join json_table ( t.jsonstr, '$' columns ( descr path '$.Description' )) j
where t.id = 10;
which you could do dynamically as:
declare
sqlsmt varchar2(200);
id number := 10;
t1 varchar2(30) := 'descr';
t2 varchar2(30) := 'Description';
t3 varchar2(50);
begin
sqlsmt := 'select j.*'
|| ' from mytable t'
|| q'^ cross join json_table ( t.jsonstr, '$' columns ( ^'
|| t1
|| q'^ path '$.^'
|| t2
|| q'^' )) j^'
|| ' where t.id = :id';
dbms_output.put_line(sqlsmt);
execute immediate sqlsmt into t3 using id;
dbms_output.put_line( 'Output Variable: ' || t3);
end;
/
I've used the alternative quoting mechanism to avoid having to double-up the quotes within the statement, but that's optional. With the same data that outputs:
select j.* from mytable t cross join json_table ( t.jsonstr, '$' columns ( descr path '$.Description' )) j where t.id = :id
Output Variable: TestJSON
db<>fiddle

How to store mutiple column output in a for loop

Designing a function to show column_name and count of null values
Expected o/p
Col_name,total count
abc 45
def 30
fgh 10
enter code here
CREATE OR REPLACE FUNCTION gtr_ops.f_column_validation()
RETURNS TABLE
(
column_name character varying(50),
total_blank_records bigint
)
AS
$BODY$
DECLARE
i record;
ecode CHARACTER VARYING(1000);
vsql text;
BEGIN
raise notice 'entering for loop';
for i IN (select column_name from information_schema.columns where table_schema='xyz'
and table_name='xyz')
LOOP
--RAISE NOTICE '%', i.column_name;
column_name := i.column_name;
vsql := 'select count(*) from schema.tablename
where '|| i.column_name||' is null
group by '|| i.column_name;
RAISE NOTICE '%', vsql;
EXECUTE vsql into total_blank_records;
RETURN NEXT;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
ecode := SQLSTATE||' Error Message:'|| SQLERRM;
raise notice 'Err Msg: %',ecode;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Error
Err Msg: 42703 Error Message:record "i" has no field "column_name"
Need guidance to solve the error
Well, I have tried your code and got a bit different error:
Err Msg: 42702 Error Message:column reference "column_name" is ambiguous
Indeed, you use column_name in return type and in select (select column_name from...). When I changed to select columns.column_name from... it worked.
But then, you have to replace schema.tablename with proper value (just like you do with i.column_name):
First:
for i IN (select columns.table_schema, columns.table_name, columns.column_name from information_schema.columns where ...
Then:
vsql := 'select count(*) from ' || i.table_schema || '.' || i.table_name || '
But remember, that you have hard-coded table_schema and table_name to be xyz.xyz in your query in for loop.
I was testing with PostgreSQL 9.5.3.

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

oracle read column names from select statement

I wonder how read column names in oracle. Ok, I know that there is table named USER_TAB_COLUMNS which gives info about it, but if I have 2 or 3 level nested query and I don't know column names. Or I just have simple query with join statement and i want to get column names. How to do that? any idey?
select * from person a
join person_details b where a.person_id = b.person_id
thanks
I would go for:
select 'select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from T1'
from user_tab_columns
where table_name = 'T1';
to get a query from database. To get columns with types to fill map you can use just:
select column_name , data_type
from user_tab_columns
where table_name = 'T1';
I assume you are looking for this:
DECLARE
sqlStr VARCHAR2(1000);
cur INTEGER;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB2;
BEGIN
sqlStr := 'SELECT a.*, b.*, SYSDATE as "Customized column name"
FROM person a JOIN person_details b
WHERE a.person_id = b.person_id';
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur, columnCount, describeColumns);
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE ( describeColumns(i).COL_NAME );
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cur);
END;

How to trim all columns in all rows in all tables of type string?

In Oracle 10g, is there a way to do the following in PL/SQL?
for each table in database
for each row in table
for each column in row
if column is of type 'varchar2'
column = trim(column)
Thanks!
Of course, doing large-scale dynamic updates is potentially dangerous and time-consuming. But here's how you can generate the commands you want. This is for a single schema, and will just build the commands and output them. You could copy them into a script and review them before running. Or, you could change dbms_output.put_line( ... ) to EXECUTE IMMEDIATE ... to have this script execute all the statements as they are generated.
SET SERVEROUTPUT ON
BEGIN
FOR c IN
(SELECT t.table_name, c.column_name
FROM user_tables t, user_tab_columns c
WHERE c.table_name = t.table_name
AND data_type='VARCHAR2')
LOOP
dbms_output.put_line(
'UPDATE '||c.table_name||
' SET '||c.column_name||' = TRIM('||c.column_name||') WHERE '||
c.column_name||' <> TRIM('||c.column_name||') OR ('||
c.column_name||' IS NOT NULL AND TRIM('||c.column_name||') IS NULL)'
);
END LOOP;
END;
Presumably you want to do this for every column in a schema, not in the database. Trying to do this to the dictionary tables would be a bad idea...
declare
v_schema varchar2(30) := 'YOUR_SCHEMA_NAME';
cursor cur_tables (p_schema_name varchar2) is
select owner, table_name, column_name
from all_tables at,
inner join all_tab_columns atc
on at.owner = atc.owner
and at.table_name = atc.table_name
where atc.data_type = 'VARCHAR2'
and at.owner = p_schema;
begin
for r_table in cur_tables loop
execute immediate 'update ' || r.owner || '.' || r.table_name
|| ' set ' || r.column_name || ' = trim(' || r.column_name ||');';
end loop;
end;
This will only work for fields that are VARCHAR2s in the first place. If your database contains CHAR fields, then you're out of luck, because CHAR fields are always padded to their maximum length.

Resources