JSON_TABLE dynamic path expression - oracle

What is the best practice to select data using JSON_TABLE with dynamic path expression? For example first path must be $.ORDERITEM[*] and another is $.OPENORDERS[*]. Path must be literal, REPLACE/REGEX_REPLACE truncates CLOB to to 32K - doesn't suit me. Both arrays have the same data, there is no problem. I have few ideas: first, is two selects with different paths, using union; second, is sub-functions with different paths.
FOR j IN (
SELECT *
FROM JSON_TABLE(v_response, '$.ORDERITEM[*]'
COLUMNS(WERKS VARCHAR2 PATH '$.WERKS',
AUFNR VARCHAR2 PATH '$.AUFNR',
MATNR VARCHAR2 PATH '$.MATNR'
))
) LOOP
...
END LOOP;

Related

Converting Big Clob into Table using XMLTable

I´m trying to convert a input clob variable from a store procedure into a xmltype and then, using XMLTable, trying to join it with other tables on my db, here is my code:
with clientes_data as (SELECT clientes.identificacion,clientes.tipoDoc,clientes.cuentas
from xmltable('/Clientes/Cliente'
passing xmltype(to_clob('<Clientes>
<Cliente><NumeroIdentificacion>94406495</NumeroIdentificacion><TipoIdentificacion>CC</TipoIdentificacion></Cliente>
<Cliente><NumeroIdentificacion>1136881480</NumeroIdentificacion><TipoIdentificacion>CC</TipoIdentificacion></Cliente>
</Clientes>'))
columns
identificacion varchar2(10) path 'NumeroIdentificacion',
tipoDoc varchar2(2) path 'TipoIdentificacion',
cuentas xmltype path 'Cuentas') clientes)
,cuentas_Data as (
SELECT cl.identificacion,cl.tipoDoc,cuentasT.*
from clientes_data cl
LEFT JOIN
xmltable('/Cuentas/Cuenta'
passing cl.cuentas
columns
numCta varchar2(10) path 'Numero',
tipoCta varchar2(3) path 'Tipo') cuentasT ON 1=1)
select * from cuentas_Data;
--select count(*) from cuentas_Data;
But I´m gettin this error: String literal too long...The string literal is longer tahn 4000 characters when the input (the passing section) is longer than 4000 characters,so, I´m a little bit confused, cause it´s suposed that XMLTable have a parameter xmltype (kind of a clob size), but, I´m assuming that actually is a varchar2(4000)?
Thanks for any "light" on this.

Writing a Version Number Function in PL/SQL

I want to write a function that will give me the next version number for a table. The table stores the existing version on each record. For example,
I have the cat table
cats
seqid 1
name Mr Smith
version number 1.2b.3.4
How can I write a program that will be able to increment these values based on various conditions?
This is my first attempt
if v_username is not null
then v_new_nbr = substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
should be 1.2b.3.5
substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
This hurls ORA-01722: invalid number. The reason is a subtle one. It seems Oracle applies the concatenation operator before the additions, so effectively you're adding one to the string '1.2b.3.4'.
One solution is using a TO_CHAR function to bracket the addition with the second substring before concatenating the result with the first substring:
substr(v_cur_nbr, 1,7) || to_char(to_number(substr(v_cur_nbr, 8,1))+1)
Working demo on db<>fiddle.
Incidentally, a key like this is a bad piece of data modelling. Smart keys are dumb. They always lead to horrible SQL (as you're finding) and risk data corruption. A proper model would have separate columns for each element of the version number. We can use virtual columns to concatenate the version number for display circumstances.
create table cats(
seqid number
,name varchar2(32)
,major_ver_no1 number
,major_ver_no2 number
,variant varchar2(1)
,minor_ver_no1 number
,minor_ver_no2 number
,v_cur_nbr varchar2(16) generated always as (to_char(major_ver_no1,'FM9') ||'.'||
to_char(major_ver_no2,'FM9') ||'.'||
variant ||'.'||
to_char(minor_ver_no1,'FM9') ||'.'||
to_char(minor_ver_no2,'FM9') ) );
So the set-up is a bit of a nause but incrementing the version numbers is a piece of cake.
update cats
set major_ver_no1 = major_ver_no1 +1
, major_ver_no2 = 0
, variant = 'a';
There's a db<>fiddle for that too.
Try searching mask for TO_NUMBER to be able to get the decimal number, this small example might help:
CREATE TABLE tmp_table (version varchar2(100));
INSERT INTO tmp_table(version) VALUES ('1.2b.3.4');
DECLARE
mainVersion NUMBER;
subVersion NUMBER;
currentVersion VARCHAR2(100);
BEGIN
SELECT version INTO currentVersion FROM tmp_table;
mainVersion := TO_NUMBER(SUBSTR(currentVersion,1,3),'9.9') + 0.1;
subVersion := TO_NUMBER(SUBSTR(currentVersion,6,3),'9.9') + 1.1;
UPDATE tmp_table SET version = (mainVersion||'b.'||subVersion);
END;

Function results column names to be used in select statement

I have function which returns column names and i am trying to use the column name as part of my select statement, but my results are coming as column name instead of values
FUNCTION returning column name:
get_col_name(input1, input2)
Can И use this query to the results of the column from table -
SELECT GET_COL_NAME(input1,input2) FROM TABLE;
There are a few ways to run dynamic SQL directly inside a SQL statement. These techniques should be avoided since they are usually complicated, slow, and buggy. Before you do this try to find another way to solve the problem.
The below solution uses DBMS_XMLGEN.GETXML to produce XML from a dynamically created SQL statement, and then uses XML table processing to extract the value.
This is the simplest way to run dynamic SQL in SQL, and it only requires built-in packages. The main limitation is that the number and type of columns is still fixed. If you need a function that returns an unknown number of columns you'll need something more powerful, like the open source program Method4. But that level of dynamic code gets even more difficult and should only be used after careful consideration.
Sample schema
--drop table table1;
create table table1(a number, b number);
insert into table1 values(1, 2);
commit;
Function that returns column name
create or replace function get_col_name(input1 number, input2 number) return varchar2 is
begin
if input1 = 0 then
return 'a';
else
return 'b';
end if;
end;
/
Sample query and result
select dynamic_column
from
(
select xmltype(dbms_xmlgen.getxml('
select '||get_col_name(0,0)||' dynamic_column from table1'
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns dynamic_column varchar2(4000) path 'DYNAMIC_COLUMN'
);
DYNAMIC_COLUMN
--------------
1
If you change the inputs to the function the new value is 2 from column B. Use this SQL Fiddle to test the code.

trying to copy data from a prepopulated collection to an associated array in a function

I am getting below error while trying to copy data from one collection to other.
Error(17,8): PL/SQL: ORA-00904: "COLUMN_VALUE": invalid identifier
Please help me providing a better way.
create or replace type type_record as object(employee_id NUMBER(6),
first_name VARCHAR2(20));
create or replace type type_tbl as table of type_record;
create or replace function scrub_final_2 return sys_refcursor IS
x type_tbl;
test1 type_tbl;
y sys_refcursor;
z sys_refcursor;
begin
x:=type_tbl();
z:=scrub_final_1; /*This is a function which returns a refcursor*/
loop
fetch z bulk collect into test1;
exit when z%NOTFOUND;
select column_value bulk collect into x from table(test1);
end loop;
open y for select employee_id,first_name from employees a where not exists
(select employee_id from table(x) where a.employee_id=employee_id);
return y;
end;
First, using x, y, z, and test1 as variable names makes it relatively hard to understand your code since it is not obvious at any point which variables represent a cursor and which represent a collection. Calling an object type_record is also confusing since it is not, in fact, a record which is a PL/SQL structure very similar to a SQL object. Additionally, your title is rather confusing since neither of your collections are actually associative arrays.
Second, your loop as currently constructed doesn't make any sense. If you're going to have a loop, you'd want to do a bulk collect with a limit. If you're not going to use a limit in your bulk collect, there is no point in looping since you'll only ever have one iteration of the loop.
Third, there appears to be no reason to copy the data from one collection to another. You can use the data in test1 to open y rather than using x in the query. Using a second collection just means that you're wasting valuable space in the PGA.
Fourth, if you really do want to copy the data from one collection to another, you can do a simple assignment
x := test1;
Fifth, if you're going to write a select against a collection defined on an object type, the columns in the result will be the names of the object type's attributes. column_value is only a column name for collections of built-in types. If you really, really wanted to so the assignment the hard way with a select statement, you'd do something like
SELECT type_record( employee_id, first_name )
BULK COLLECT INTO x
FROM TABLE( test1 );

100 strings in IN operator, oracle pl/sql

I am passing 100 table_names in the IN operator as strings, but I am getting numeric overflow error due to too many operands.
Is there a way where I can use something else besides IN ?
set serveroutput on
DECLARE
...
BEGIN
FOR r IN
(
SELECT table_name, column_name
FROM all_tab_columns
WHERE table_name IN (...100 strings)
)
AND data_type = 'NUMBER'
ORDER BY table_name, column_id
)
LOOP
execute immediate 'SELECT COUNT("' || r.column_name || '")
,COUNT(nvl2("' || r.column_name || '", NULL, 1))
FROM "' || r.table_name || '"'
INTO not_null_count, null_count;
DBMS_OUTPUT.PUT_LINE(..)
Note: For variables I am using PLS_Integer.
The suggested action for ORA-01426 is "reduce the operands". This doesn't mean reduce the number of operands. It means you're trying to put too large a number into a variable. So shrink the number, or enlarge the variable.
You say:
"for variables I am using PLS_Integer"
So, if you have a large table, and by large I mean more than 2,147,483,647 rows, you will get a numeric overflow. Because PLS_INTEGER is a 32-bit data type.
If this is your scenario then you need to declare your variables of data type INTEGER instead (or NUMBER(38,0)).
As #BobJarvis points out, PLS_INTEGER is optimized for hardware arithmetic. So the general advice would be to use it for counting type operations. However, your case simply requires a variable to hold the output of a SQL count() operation, so I don't think there will be any difference in efficiency.
I believe the limit on 'IN' clause is 1000 strings and not 100 strings. To debug:
a.) Try running your implicit cursor query in SQL.
b.) If it works fine then run the query in execute immediate after substituting the column name.
Also , try increasing the size of your not_null_count and null_count variables.
Hope it Helps
Vishad
some other possible solutions
use a temp table - populate it with the table names to filter join to it.
create a global array type
create type table_of_varchar2 is table of varchar2(30)
populate the array and filter using table_name member of arr_tables_list
Is there a way where I can use something else besides IN ?
Consider using a cursor instead.

Resources