with oracle xmltable
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS contract integer path 'contract',
oper VARCHAR2(20) PATH 'oper' ) u
This is normally what we do.
Now I need to have "COLUMNS" in above query selected from another tables column for Xpath
something like
{
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS (select xpath from xpath_metadeta )) u
}
Please let me know if this is possible and how?
One option that comes to my mind is dynamic sql and ref cursor.
Something like this:
DECLARE
columnParameters SYS.ODCIVARCHAR2LIST :=
SYS.ODCIVARCHAR2LIST(
'TITLE VARCHAR2(1000) PATH ''title''',
'SUMMARY CLOB PATH ''summary''',
'UPDATED VARCHAR2(20) PATH ''updated''',
'PUBLISHED VARCHAR2(20) PATH ''published''',
'LINK VARCHAR2(1000) PATH ''link/#href'''
);
ref_cursor SYS_REFCURSOR;
cursor_id NUMBER;
table_description DBMS_SQL.DESC_TAB;
column_count NUMBER;
string_value VARCHAR2(4000);
clob_value CLOB;
FUNCTION DYNAMIC_XMLTABLE(xml_columns SYS.ODCIVARCHAR2LIST) RETURN SYS_REFCURSOR
IS
result SYS_REFCURSOR;
statementText VARCHAR2(32000) := Q'|SELECT * FROM
XMLTABLE(
XMLNAMESPACES (DEFAULT 'http://www.w3.org/2005/Atom'),
'for $entry in /feed/entry return $entry'
PASSING
HTTPURITYPE('http://stackoverflow.com/feeds/tag?tagnames=oracle&sort=newest').getxml()
COLUMNS
{column_definition}
)|';
BEGIN
SELECT REPLACE(statementText, '{column_definition}', LISTAGG(COLUMN_VALUE, ', ') WITHIN GROUP (ORDER BY ROWNUM)) INTO statementText FROM TABLE(xml_columns);
DBMS_OUTPUT.PUT_LINE('Statement: ' || CHR(10) || statementText);
OPEN result FOR statementText;
RETURN result;
END;
BEGIN
DBMS_OUTPUT.ENABLE(NULL);
ref_cursor := dynamic_xmltable(columnParameters);
cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(ref_cursor);
DBMS_SQL.DESCRIBE_COLUMNS(cursor_id, column_count, table_description);
FOR i IN 1..column_count LOOP
IF table_description(i).col_type = 1 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, string_value, 4000);
ELSIF table_description(i).col_type = 112 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, clob_value);
END IF;
END LOOP;
WHILE DBMS_SQL.FETCH_ROWS(cursor_id) > 0 LOOP
FOR i IN 1..column_count LOOP
DBMS_OUTPUT.PUT_LINE(table_description(i).col_name || ': datatype=' || table_description(i).col_type);
IF (table_description(i).col_type = 1) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, string_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || string_value);
END;
ELSIF (table_description(i).col_type = 112) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, clob_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || clob_value);
END;
-- add other data types
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cursor_id);
END;
I depends how the cursor is consumed. It's much simple if by an application, a bit more difficult if using PL/SQL.
Related
This question is not a duplicate of this question because the given answer works only if the query isn't a variable.
the following query is working but the result is saved in a xml file.
SELECT XMLTYPE.createXML (DBMS_XMLGEN.getxml ('select 2 as a from dual')) FROM DUAL;
It's working but I can use macro only in oracle>19. (because of the macro)
with FUNCTION f_test return varchar2 SQL_MACRO is
query VARCHAR2(100) := 'select 1 a from dual';
ret VARCHAR2(100) := chr(13) || query || chr(13);
BEGIN
RETURN ret;
END;
SELECT JSON_ARRAYagg( json_object(t.*) )
FROM f_test() t
code
I've tried to use dynamic sql with oracle 19
WITH
FUNCTION f
RETURN JSON_ARRAY
IS
query VARCHAR2 (100) := 'select 1 from dual';
l_str VARCHAR2 (1000);
l_cnt JSON_ARRAY;
BEGIN
l_str :=
'with from_dynamic_query as ('
|| query
|| ') SELECT JSON_ARRAYagg( json_object(*) ) from from_dynamic_query';
EXECUTE IMMEDIATE l_str
INTO l_cnt;
RETURN l_cnt;
END;
SELECT
FROM DUAL;
[Error] Execution (20: 8): ORA-06553: PLS-313: 'F' not declared in this scope
ORA-06552: PL/SQL: Item ignored
ORA-06553: PLS-488: 'JSON_ARRAY' must be a type
As I wrote in the comment, the issue may not be related to SQL_MACRO, but inability to process * in json_object (see db<>fiddle in 18c).
But this may also be worked out with Polymorphic Table Functions, which are available in 18c. You need to define new output calculated column with a row value serialized into JSON.
Below is the code example:
create package pkg_ser as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_ser;
/
create package body pkg_ser as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
begin
/*Mark input columns as used for subsequent row processing*/
for i in 1..tab.column.count loop
tab.column(i).for_read := TRUE;
end loop;
/*Declare json output column*/
return dbms_tf.describe_t(
new_columns => dbms_tf.columns_new_t(
1 => dbms_tf.column_metadata_t(
name => 'JSONVAL',
type => dbms_tf.type_varchar2
)
)
);
end;
procedure fetch_rows
/*Process rowset and serialize each row in JSON*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
new_col dbms_tf.tab_varchar2_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for rn in 1..num_rows loop
/*Calculate new column value in the same row*/
new_col(rn) := dbms_tf.row_to_char(
rowset => rowset,
rid => num_rows,
format => dbms_tf.FORMAT_JSON
);
end loop;
/*Put column to output*/
dbms_tf.put_col(
columnid => 1,
collection => new_col
);
end;
end pkg_ser;
/
create function f_serialize_json(tab in table)
/*Function to serialize into JSON using PTF*/
return table pipelined
row polymorphic using pkg_ser;
/
with function f_local_exec (
query in clob
) return varchar2
as
ret varchar2(32000);
begin
/*Translate string to query using EXECUTE IMMEDIATE*/
execute immediate '
with a as (
' || query || '
)
select json_arrayagg(jsonval format json)
from f_serialize_json(a)
' into ret;
return ret;
end;
select f_local_exec(
'select level as id, mod(level, 3) as val from dual connect by level < 10'
) as jsonval
from dual
| JSONVAL |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0}] |
db<>fiddle here
It works if I replace json_arry with clob
WITH
FUNCTION f
RETURN clob
IS
query VARCHAR2 (100) := 'select 1 a from dual';
l_str VARCHAR2 (1000);
l_cnt clob;
BEGIN
l_str :=
'with from_dynamic_query as ('
|| query
|| ') SELECT JSON_ARRAYagg( json_object(*) ) from from_dynamic_query';
EXECUTE IMMEDIATE l_str
INTO l_cnt;
RETURN l_cnt;
END;
SELECT f()
FROM DUAL;
I am trying hard dynamic PL/SQL thing here.
I don't manage to fetch a column dynamic Query.
I am iterating on the name of the column to concatenate a full query in order to be executed on another table.
sql_req := 'select ';
for c in (SELECT name_col from TAB_LISTCOL)
loop
sql_req := sql_req || 'sum(' || c.name_col || '),';
end loop;
sql_req := sql_req || ' from ANOTHER_TAB ';
And when i try to execute it with EXECUTE IMMEDIATE or cursors or INTO/BULK COLLECT thing or just to fetch, i don't manage to iterate on the result.
I tried a lot.
Can you help me plz ? Or maybe it is not possible ?
ps : i know the coma is wrong but my code is more complexe than this : i didn't want to put more things
If you only want to get string columns, you can use listagg
select listagg(name_col, ',') WITHIN GROUP (ORDER BY null) from TAB_LISTCOL
Please see if this helps
In the absence of actual table structure and requirement, I'm creating dummy tables and query to illustrate an example:
SQL> create table another_tab
as
select 10 dummy_value1, 100 dummy_value2, 1000 dummy_value3 from dual union all
select 11 dummy_value1, 101 dummy_value2, 1001 dummy_value3 from dual union all
select 12 dummy_value1, 102 dummy_value2, 1003 dummy_value3 from dual
;
Table created.
SQL> create table tab_listcol
as select column_name from dba_tab_cols where table_name = 'ANOTHER_TAB'
;
Table created.
To reduce complexity in the final block, I'm defining a function to generate the dynamic sql query. This is based on your example and will need changes according to your actual requirement.
SQL> create or replace function gen_col_based_query
return varchar2
as
l_query varchar2(4000);
begin
l_query := 'select ';
for cols in ( select column_name cname from tab_listcol )
loop
l_query := l_query || 'sum(' || cols.cname || '), ' ;
end loop;
l_query := rtrim(l_query,', ') || ' from another_tab';
return l_query;
end;
/
Function created.
Sample output from the function will be as follows
SQL> select gen_col_based_query as query from dual;
QUERY
--------------------------------------------------------------------------------
select sum(DUMMY_VALUE1), sum(DUMMY_VALUE2), sum(DUMMY_VALUE3) from another_tab
Below is a sample block for executing a dynamic cursor using DBMS_SQL. For your ease of understanding, I've added comments wherever possible. More info here.
SQL> set serveroutput on size unlimited
SQL> declare
sql_stmt clob;
src_cur sys_refcursor;
curid number;
desctab dbms_sql.desc_tab; -- collection type
colcnt number;
namevar varchar2 (50);
numvar number;
datevar date;
l_header varchar2 (4000);
l_out_rows varchar2 (4000);
begin
/* Generate dynamic sql from the function defined earlier */
select gen_col_based_query into sql_stmt from dual;
/* Open cursor variable for this dynamic sql */
open src_cur for sql_stmt;
/* To fetch the data, however, you cannot use the cursor variable, since the number of elements fetched is unknown at complile time.
Therefore you use DBMS_SQL.TO_CURSOR_NUMBER to convert a REF CURSOR variable to a SQL cursor number which you can then pass to DBMS_SQL subprograms
*/
curid := dbms_sql.to_cursor_number (src_cur);
/* Use DBMS_SQL.DESCRIBE_COLUMNS to describe columns of your dynamic cursor, returning information about each column in an associative array of records viz., desctab. The no. of columns is returned in colcnt variable.
*/
dbms_sql.describe_columns (curid, colcnt, desctab);
/* Define columns at runtime based on the data type (number, date or varchar2 - you may add to the list)
*/
for indx in 1 .. colcnt
loop
if desctab (indx).col_type = 2 -- number data type
then
dbms_sql.define_column (curid, indx, numvar);
elsif desctab (indx).col_type = 12 -- date data type
then
dbms_sql.define_column (curid, indx, datevar);
else -- assuming string
dbms_sql.define_column (curid, indx, namevar, 100);
end if;
end loop;
/* Print header row */
for i in 1 .. desctab.count loop
l_header := l_header || ' | ' || rpad(desctab(i).col_name,20);
end loop;
l_header := l_header || ' | ' ;
dbms_output.put_line(l_header);
/* Loop to retrieve each row of data identified by the dynamic cursor and print output rows
*/
while dbms_sql.fetch_rows (curid) > 0
loop
for indx in 1 .. colcnt
loop
if (desctab (indx).col_type = 2) -- number data type
then
dbms_sql.column_value (curid, indx, numvar);
l_out_rows := l_out_rows || ' | ' || rpad(numvar,20);
elsif (desctab (indx).col_type = 12) -- date data type
then
dbms_sql.column_value (curid, indx, datevar);
l_out_rows := l_out_rows || ' | ' || rpad(datevar,20);
elsif (desctab (indx).col_type = 1) -- varchar2 data type
then
dbms_sql.column_value (curid, indx, namevar);
l_out_rows := l_out_rows || ' | ' || rpad(namevar,20);
end if;
end loop;
l_out_rows := l_out_rows || ' | ' ;
dbms_output.put_line(l_out_rows);
end loop;
dbms_sql.close_cursor (curid);
end;
/
PL/SQL procedure successfully completed.
Output
| SUM(DUMMY_VALUE1) | SUM(DUMMY_VALUE2) | SUM(DUMMY_VALUE3) |
| 33 | 303 | 3004 |
You have to use EXECUTE IMMEDIATE with BULK COLLECT
Below is an example of the same. For more information refer this link
DECLARE
TYPE name_salary_rt IS RECORD (
name VARCHAR2 (1000),
salary NUMBER
);
TYPE name_salary_aat IS TABLE OF name_salary_rt
INDEX BY PLS_INTEGER;
l_employees name_salary_aat;
BEGIN
EXECUTE IMMEDIATE
q'[select first_name || ' ' || last_name, salary
from hr.employees
order by salary desc]'
BULK COLLECT INTO l_employees;
FOR indx IN 1 .. l_employees.COUNT
LOOP
DBMS_OUTPUT.put_line (l_employees (indx).name);
END LOOP;
END;
If I understand correctly, you want to create a query and execute it and return the result to another function or some calling app. As the resulting query's columns are note before-known, I'd return a ref cursor in this case:
create function get_sums return sys_refcur as
declare
my_cursor sys_refcursor;
v_query varchar2(32757);
begin
select
'select ' ||
listagg('sum(' || name_col || ')', ', ') within group (order by name_col) ||
' from another_tab'
into v_query
from tab_listcol;
open my_cursor for v_query;
return v_query;
end get_sums;
I have Oracle Stored Procedure below and I understood most of the logic inside except for the part where the caller of the procedure will get all the values from different cursors.
Did some reading that SP returns the OUT part in a procedure parameters shown in the sample below. But i fail to get any reference as to how p_returnCode can store resultsets from queries inside the stored procedure.
Procedure retrieveX(p_date date, p_loc varchar2, p_returnCode out integer)
The stored procedure below have 3 cursors cur1, cur2 and cur3. How or where does it store the values? Cur1 contains 2 columns with multiple rows and Cur2 and Cur3 contains one column with multiple rows.
Can anyone clarify this part?
Caller from cgi script
report.retrieveX(p_date,p_loc,p_return)
Full Stored Procedure
PROCEDURE retrieveX(
p_date DATE,
p_loc VARCHAR2,
p_returnCode OUT INTEGER
)
AS
TYPE cur_typ IS REF CURSOR;
cur1 cur_typ;
cur2 cur_typ;
cur3 cur_typ;
query_str VARCHAR2(2000) := '';
query_str2 VARCHAR2(2000) := '';
query_str3 VARCHAR2(2000) := '';
v_an VARCHAR2(20);
v_tn VARCHAR2(20);
v_sOID varchar2(20);
BEGIN
sqlRouteDT := 'AND sp.ROUTE_DT = TO_DATE(''' || TO_CHAR(p_date, 'YYYY/MM/DD') || ''',''YYYY/MM/DD'')';
IF p_loc IS NOT NULL THEN
sqlLocation := 'AND act.location_cd = ''' || p_loc || '''';
END IF;
p_returnCode := 0;
query_str := '
SELECT distinct
sp.ab,
y.track,
FROM ship sp
inner join activ act on sp.soid=act.on
inner join peace y on act.on=y.soid
where
sp.man is not null
' || sqlLocation || '
' || sqlRouteDT || '
ORDER BY sp.ab asc
';
OPEN cur1 FOR query_str1;
LOOP
FETCH cur1
INTO
v_AN,
v_FN
EXIT WHEN cur1%NOTFOUND;
query_str2 := '
SELECT DISTINCT INTER_CD
FROM TBL_INTR
WHERE AF = ''Y''
AND sOID = ''' || v_sOID || '''
ORDER BY INTER_CD
';
OPEN cur2 FOR query_str2;
LOOP
FETCH cur2
INTO v_intr_cd;
EXIT WHEN cur2%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('INTER_CD|' || v_intr_cd);
END LOOP;
CLOSE cur2;
query_str3 := '
SELECT DISTINCT hi_cd
FROM tbl_hi
WHERE AF = ''Y''
AND sOID = ''' || v_sOID || '''
ORDER BY hi_cd
';
OPEN cur3 FOR query_str3;
LOOP
FETCH cur3
INTO v_hi_c;
EXIT WHEN cur3%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('hi_cd|' || v_hi_c);
END LOOP;
CLOSE cur3;
END LOOP;
CLOSE cur1;
EXCEPTION WHEN OTHERS THEN
p_returnCode := 1;
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END retrieveX;
I don't think possible to do that you want.
Maybe you should to considerate the use of pipelined function, please take a look at https://oracle-base.com/articles/misc/pipelined-table-functions
With this technique, you can write a pl/sql code to access to complex data and relationship table and get the result as a simple
select * from StoredProcedure(Parameter_1...);
I try to search a number from multiple columns (datatype number), but get ORA-01722: invalid number error.
My Query:
SELECT *
FROM CAMPAIGN
WHERE 1481125 IN (select column_name
from all_tab_columns
where table_name = 'CAMPAIGN'
AND data_type = 'NUMBER');
What is wrong with it?
You are comparing your number 1481125 with the names of the each column, not the values of each column in your table.
To go from a column's name (from dba_tab_columns) to the values in that column, you need to use some form of dynamic SQL. Here's a relatively simple example:
DECLARE
-- Since I don't have your CAMPAIGN table or data, I'm using DBA_OBJECTS in it's place.
l_table_name VARCHAR2 (30) := 'DBA_OBJECTS';
l_search_number NUMBER := 20; -- 1481125 in your example
l_record dba_objects%ROWTYPE;
l_sql VARCHAR2 (32000);
l_column_number NUMBER := 0;
l_cur SYS_REFCURSOR;
BEGIN
-- First: build dynamic SQL statement of the form:
-- SELECT * FROM table_name WHERE
-- ( ( col_name_a = 20 ) OR ( col_name_b = 20 ) OR ... )
l_sql := 'SELECT * FROM dba_objects WHERE ( ';
FOR r_number_column IN (SELECT column_name
FROM dba_tab_columns
WHERE table_name = l_table_name
AND data_type = 'NUMBER'
ORDER BY column_id) LOOP
IF l_column_number > 0 THEN
l_sql := l_sql || ' OR ';
END IF;
l_column_number := l_column_number + 1;
l_sql := l_sql || '(' || r_number_column.column_name || ' = ' || l_search_number || ')';
END LOOP;
IF l_column_number = 0 THEN
-- No number columns in table, so there should be no matches
l_sql := l_sql || ' 1=0';
END IF;
l_sql := l_sql || ')';
DBMS_OUTPUT.put_line (l_sql);
OPEN l_cur FOR l_sql;
LOOP
FETCH l_cur INTO l_record;
EXIT WHEN l_cur%NOTFOUND;
DBMS_OUTPUT.put_line ('Object Name ' || l_record.object_name || ' has search number ' || l_search_number);
END LOOP;
END;
Your query is:
SELECT * FROM CAMPAIGN WHERE 1481125 IN
(select column_name from all_tab_columns where table_name = 'CAMPAIGN' AND data_type='NUMBER')
Breaking that down we have:
SELECT * FROM CAMPAIGN WHERE 1481125 IN (<a set of numbers>)
and the subquery:
select column_name from all_tab_columns
where table_name = 'CAMPAIGN'
AND data_type='NUMBER'
That subquery is going to return a list of column names e.g.
CAMPAIGN_COUNT
CAMPAIGN_ID
CAMPAIGN_NUMBER_OF_SOMETHINGS
Your query is thus equivalent to:
SELECT * FROM CAMPAIGN WHERE 1481125 IN
('CAMPAIGN_COUNT', 'CAMPAIGN_ID', 'CAMPAIGN_NUMBER_OF_SOMETHINGS')
You can see why you would get the ORA-01722 error there?
You would need to write dynamic SQL to achieve your aim.
I give the SQL few inputs and I need to get all the ID's and their count that doesn't satisfy the required criteria.
I would like to know if there are there any alternatives to using cursor.
DECLARE
v_count INTEGER;
v_output VARCHAR2 (1000);
pc table1%ROWTYPE;
unmarked_ids EXCEPTION;
dynamic_sql VARCHAR (5000);
cur SYS_REFCURSOR;
id pp.id%TYPE;
pos INTEGER;
BEGIN
v_count := 0;
SELECT *
INTO pc
FROM table1
WHERE id = '&ID';
DBMS_OUTPUT.ENABLE;
dynamic_sql :=
'SELECT ID from pp
WHERE ( TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:a, ''yyyy/mm/dd''))
AND aid IN (SELECT aid FROM ppd WHERE TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:b, ''yyyy/mm/dd'')
AND cid = :c )
AND cid <> :d';
OPEN cur FOR dynamic_sql USING pc.cdate, pc.cdate, pc.id, pc.id;
LOOP
FETCH cur INTO id;
EXIT WHEN cur%NOTFOUND;
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE (' Id:' || id);
END LOOP;
CLOSE cur;
IF (v_count > 0)
THEN
DBMS_OUTPUT.PUT_LINE ( 'Count: ' || v_count || ' SQL: ' || dynamic_sql);
RAISE unmarked_ids;
END IF;
DBMS_OUTPUT.PUT_LINE('SQL ended successfully');
EXCEPTION
WHEN unmarked_ids
THEN
DBMS_OUTPUT.put_line (
'Found ID's that not marked with the current id.');
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (
'No data found in table1 with the current id ' || '&ID');
END;
There are bind variables in the query. One of them is date, there are three more.
The count and ID's are required to be shown which will later be reported.
You could store the rowid in a temporary table along with an index value (0...n) and then use a while loop to go through the index values and join to the real table using the rowid.