pl sql with result merge together issue - oracle

This is my pl sql query and result.
with
tblObject (profile_value,profile_entry) as
(select profile_value,profile_entry
FROM
table1'
),
tblObjecttemp as
(select regexp_substr(profile_value, '\|([^|]+)\|' , 1, column_value) val,
column_value cv,
profile_value,
profile_entry
from tblObject cross join
table(cast(multiset(select level from dual
connect by level <= regexp_count(profile_value, '\|') + 1
) as sys.odcinumberlist))
)
select 'dro.LU_NAME=' || '''' || profile_entry || '''' || ' and dro.KEY_REF=' || '''' || listagg(REGEXP_REPLACE(val,'[|]',''), '''' ||' or dro.KEY_REF=''') within group (order by cv) result
from tblObjecttemp
group by profile_entry;
dro.LU_NAME='ActiveSeparate' and dro.KEY_REF='WO_NO=1^' or dro.KEY_REF='WO_NO=600003^'
dro.LU_NAME='Analysis' and dro.KEY_REF='ANALYSIS_NO=4^
Now I need to add all rows together with 'or'. I tried using listagg inside listagg again. But its getting errors. Is there any easy way to this. please help.

I have found it my self:
select listagg('dro.LU_NAME=' || '''' || profile_entry || '''' || ' and dro.KEY_REF=' || '''' || listagg(REGEXP_REPLACE(val,'[|]',''), '''' ||' or dro.KEY_REF=''') within group (order by cv),' or ')within group (order by profile_entry) result
from tblObjecttemp
group by profile_entry;

Related

Solving PL/SQL for loop variable string not resolving completely

I am trying to loop through multiple tables in a data mart and see how my TABLE_A joins to a TABLE_B based on a specific field (that changes with every loop). The variables for fields and tables resolve correctly in the DBMS_Output, but the query itself does not resovle to the full length. Instead, parts of the query are being cut off with each loop. How can I fix this?
My loop looks like this:
DECLARE
CURSOR c_tables is
SELECT
COLUMN_NAME
,JOIN_TABLE_NAME
FROM meself.test_loop ;
v_string1 varchar2(32767) := '';
BEGIN
FOR i IN c_tables LOOP
v_string1 := '
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.'||i.COLUMN_NAME||'
,''' || i.JOIN_TABLE_NAME || ''' AS JOIN_TABLE_NAME
,''' || i.COLUMN_NAME || ''' AS HASH_KEY_NAME
, ''TABLE_A'' AS Table_Name
,(CASE
WHEN
a.'|| i.COLUMN_NAME || ' = z.' || i.COLUMN_NAME || ' THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT '|| i.COLUMN_NAME ||'
FROM DATAMART.'|| i.JOIN_TABLE_NAME ||') z
ON a.'|| i.COLUMN_NAME ||' = z.'|| i.COLUMN_NAME ||'
) x
GROUP BY x.TABLE_NAME, x.JOIN_TABLE_NAME, x.HASH_KEY_NAME, x.MATCH_TYPE'
;
dbms_output.put_line( v_string1 );
--execute immediate v_string1;
END LOOP;
END;
**Which resolves to this (just showing 2 loops for simplicity). You will see the JOIN ON section is missing or incomplete before moving on to the next loop. **
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.LINE_SCTGRY_D_SK
,'TABLE_B' AS JOIN_TABLE_NAME
,'LINE_SCTGRY_D_SK' AS HASH_KEY_NAME
,'TABLE_A' AS Table_Name
,(CASE
WHEN
a.LINE_SCTGRY_D_SK = z.LINE_SCTGRY_D_SK THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT LINE_SCTGRY_D_SK
FROM DATAMART.TABLE_B
INSERT INTO myself.FIN_HASHKEY_MATCH
SELECT
x.TABLE_NAME
,x.JOIN_TABLE_NAME
,x.HASH_KEY_NAME
,x.MATCH_TYPE
,count(*) AS TOTALS
FROM
(
SELECT
DISTINCT a.MEMBER_D_SK
,'TABLE_B' AS JOIN_TABLE_NAME
,'MEMBER_D_SK' AS HASH_KEY_NAME
, 'TABLE_A' AS Table_Name
,(CASE
WHEN
a.MEMBER_D_SK = z.MEMBER_D_SK THEN "MATCH"
ELSE "FAIL"
END) AS MATCH_TYPE
FROM DATAMART.TABLE_A a
LEFT JOIN (SELECT
DISTINCT MEMBER_D_SK
FROM DATAMART.TABLE_B) z
ON a.MEMBER_D_SK = z.ME

Trying to write output to a CSV on a directory, failing with PLS-00302

I'm attempting to output some monitoring information into a .csv file. I have to stick with the "Basic" framework of the code. The issue is getting it to output to .csv/getting the code to compile.
I've tried various ways of doing this, now I'm stuck I mostly find myself moving quotes and double quotes around.
create or replace procedure WRITE_EST_SIZE_01 is
file_handle UTL_FILE.file_type;
begin
file_handle := utl_file.fopen('ESTIMATES_CSV',
'csv_filename' ||
to_char(sysdate,'MONYYYY')||'.csv',
'w', 32767);
for rws in (SELECT 'OWNER' || ',' ||
'SEGMENT_NAME' || ',' ||
'U' || ',' ||
'SUM_BYTES'
FROM
union ALL
select /*+ parallel*/
s.owner || ',' ||
s.segment_name || ',' ||
'U' || ',' ||
sum(s.bytes)/1024/1024
from DBA_SEGMENTS s
where s.owner = (select distinct targetschema
from pdu.pdu_table) and
s.segment_name in (select table_name
from another_table) and
s.segment_type LIKE '%TABLE%'
group by s.owner, s.segment_name
union all
select /*+ parallel*/
i.table_owner || ',' ||
i.table_name || ',' ||
'I' || ',' ||
sum(s.bytes)/1024/1024
from DBA_SEGMENTS s,
DBA_INDEXES i
where i.table_owner = (select distinct targetschema
from pdu.pdu_table) and
i.table_name in (select table_name
from another_table) and
i.owner = s.owner and
i.index_name = s.segment_name and
s.segment_type like '%INDEX%'
group by i.table_owner, i.table_name
union all
select /*+ parallel*/
l.owner || ',' ||
l.table_name || ',' ||
'L' || ',' ||
sum(s.bytes)/1024/1024
from DBA_SEGMENTS s,
ALL_LOBS l
where l.owner = (select distinct targetschema
from another_table) and
l.table_name in (select table_name
from another_table) and
l.owner = s.owner and
l.segment_name = s.segment_name
group by l.owner, l.table_name
--order by 1, 2)
loop
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.SEGMENT_NAME || ',' ||
rws.U || ',' ||
rws.SUM_BYTES -- your columns here
);
end loop;
utl_file.fclose(file_handle);
end WRITE_EST_SIZE_01;
This actually won't compile, but complains that rws.OWNER should be declared. It compiles if I put all the rws. in quotes, but then the csv output is overwritten with whatever is in quotes. Can anyone see a way of doing this whereby it actually "Will" dump the output of the sql to a .csv?
In your SQL you're creating a concatenated string when it appears you just wanted to fetch the individual fields. I suggest:
create or replace procedure WRITE_EST_SIZE_01 is
file_handle UTL_FILE.file_type;
begin
file_handle := utl_file.fopen('ESTIMATES_CSV',
'csv_filename' ||
to_char(sysdate,'MONYYYY')||'.csv',
'w', 32767);
for rws in (select s.owner,
s.segment_name,
'U' AS FLAG,
sum(s.bytes)/1024/1024 AS SUM_BYTES
from DBA_SEGMENTS s
where s.owner = (select distinct targetschema
from pdu.pdu_table) and
s.segment_name in (select table_name
from another_table) and
s.segment_type LIKE '%TABLE%'
group by s.owner, s.segment_name
union all
select i.table_owner AS OWNER,
i.table_name AS SEGMENT_NAME,
'I' AS FLAG,
sum(s.bytes)/1024/1024 AS SUM_BYTES
from DBA_SEGMENTS s,
DBA_INDEXES i
where i.table_owner = (select distinct targetschema
from pdu.pdu_table) and
i.table_name in (select table_name
from another_table) and
i.owner = s.owner and
i.index_name = s.segment_name and
s.segment_type like '%INDEX%'
group by i.table_owner, i.table_name
union all
select l.owner,
l.table_name AS SEGMENT_NAME,
'L' AS FLAG,
sum(s.bytes)/1024/1024 AS SUM_BYTES
from DBA_SEGMENTS s,
ALL_LOBS l
where l.owner = (select distinct targetschema
from another_table) and
l.table_name in (select table_name
from another_table) and
l.owner = s.owner and
l.segment_name = s.segment_name
group by l.owner, l.table_name
--order by 1, 2)
loop
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.SEGMENT_NAME || ',' ||
rws.FLAG || ',' ||
rws.SUM_BYTES -- your columns here
);
end loop;
utl_file.fclose(file_handle);
end WRITE_EST_SIZE_01;
Your cursor query is doing the concatenation in each branch of the union, so if you ran that standalone you'd see a result set with a single column. When you try to process the loop you're trying to look for the individual owner/segment/etc. - but they are not part of the projection from that cursor query.
If you give the single generated column value an alias, in at least the first branch:
SELECT 'OWNER'||','||'SEGMENT_NAME'||','||'U'||','||'SUM_BYTES' AS CSV_TEXT
or more simply:
SELECT 'OWNER,SEGMENT_NAME,U,SUM_BYTES' AS CSV_TEXT
then in your loop you can refer to that alias:
utl_file.put_line(file_handle, rws.CSV_TEXT);
Although it would probably be simpler just to write the header row out to the file directly before your cursor loop, instead of making it part of that query:
utl_file.put_line(file_handle, 'OWNER,SEGMENT_NAME,U,SUM_BYTES');
You could then keep the concatenation in the remaining union branches with the same single column-value alias; or have the union branches get the raw columns (owner etc.) without concatenating, and then keep the concatenation inside the loop. [As #BobJarvis' answer is doing!] But don't do both...
When you write:
) loop
utl_file.put_line(file_handle, rws.OWNER||','||rws.SEGMENT_NAME||','||rws.U||','||rws.SUM_BYTES);
you use column names which should be described in the query inside for rws in (select ...) loop. Currently, that SELECT statement has only one column whis automatically generated name. You need to change it to:
SELECT 'OWNER' owner, 'SEGMENT_NAME' segment_name, 'U' u, 'SUM_BYTES' sum_bytes
FROM dual
union all
select /*+ parallel*/
s.owner, s.segment_name, 'U', sum(s.bytes)/1024/1024
from ...
Also, you need to change all other subqueries in this way.
Or, you can keep this query as is, except a small change:
for rws in (SELECT 'OWNER,SEGMENT_NAME,U,SUM_BYTES' row_data
FROM
union ALL
And change the last line:
...
) loop
utl_file.put_line(file_handle, rws._row_data);

Concatenating regexp_replace into listagg: Result too long (SQL Error: ORA-01489)

I have created a pl/sql procedure for a package that is performing a reconciliation between sets of tables which should match.
I am using listagg to concatenate the column names of the current table name in the loop into a string used in a dynamic SQL statement that compares two tables (34 sets, looped for each table name).
The procedure worked as expected, but the results were returned unexpectedly from the minus. After researching, I determined that some fields contained a HEX (00) character received in the flat file that populates the data on only the data from one side of the recon. In order to account for the special characters, I added a regexp_replace concatenated in line with the listagg in the column name select so it outputs the complete listagg results with each column name wrapped in a regexp_replace.
It works. However, some tables have over a hundred columns, and the listagg failes for the results being over 4000 characters.
Is there a better way to go about this entire thing?
Here is the code:
Collects column names into the comma-separated list (comma character is concatenated into the string itself for use as a separator in dynamic SQL select below)
execute immediate
'SELECT ' || q'{listagg('regexp_replace(' || column_name || ', ''[^A-Z0-9 ]'', '''')', '||'', '' || ')}' || ' within group (order by rownum) "COLUMN_NAME"
FROM user_tab_cols
where table_name =''' || csrpubtable.table_name || ''''
into v_column_names;
These two dynamic SQL statements perform the reconciliation in both directions. These aren't directly related to the error, but definitely to my question of a better overall way to accomplish the task.
--Insert data to RECON_PUB_TABLES where record exists in FILE but not PROD
execute immediate
'INSERT INTO RECON_PUB_TABLES
SELECT ''' || csrpubtable.table_name || ''', ''FILE'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name || '
minus
SELECT ''' || csrpubtable.table_name || ''', ''FILE'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name || '#pub_recon2prod where trunc(' || v_lastupdate_column || ') <= trunc(to_date(''' || v_compare_date || ''', ''dd-MON-yy''))';
--Insert data to RECON_PUB_TABLES where record exists in PROD but not FILE
execute immediate
'INSERT INTO RECON_PUB_TABLES
SELECT ''' || csrpubtable.table_name || ''', ''PROD'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name || '#pub_recon2prod where trunc(' || v_lastupdate_column || ') <= trunc(to_date(''' || v_compare_date || ''', ''dd-MON-yy''))
minus
SELECT ''' || csrpubtable.table_name || ''', ''PROD'' , ' || v_column_names || ', trunc(sysdate) from ' || csrpubtable.table_name ;
varchar2 is limited to 32k within plsql
if 32 is enough you can try something like this
create or replace procedure conc_col_names(tableName IN varchar2) as
collist varchar2(32767);
begin
for xx in (select * from user_tab_columns where table_name = tableName order by column_name asc) loop
if ( length(collist) > 0) then
collist := collist||',';
end if;
collist := collist||'regexp_replace('||xx.column_name||',''[^A-Z0-9 ]'')';
end loop;
/* add the rest code for comparing rows in the two table here */
end;
/

How can I use external variables into EXECUTE IMMEDIATE statements in PL/SQL?

I need to write a query using the EXECUTE IMMEDIATE command to sum two values I calculate inside a nested query. I would like to know if I have to use USING clause or other clauses and how to put the variables I created into the statement. The query is the following:
Edit: this is the new query, there's still a problem in binding variables (Ora-01008: not all variables are bound). Can you help me with this problem?
EXECUTE IMMEDIATE 'SELECT sum (n_record_trovati_p) FROM (
SELECT count(*) as n_record_trovati_p FROM od_pv_trading_day_orders partition (' || partition_current_month || ') WHERE DATETIME = :1 AND orderid = :2 AND broker = :3
UNION
SELECT count(*) as n_record_trovati_p FROM od_pv_trading_day_orders partition (' || partition_previous_month || ') WHERE DATETIME = :1 AND orderid = :2 AND broker = :3
)' INTO n_record_trovati
USING d_datetime, n_orderid, cur.broker;
//old query
sys_current_date VARCHAR2 (7);
sys_previous_date VARCHAR2 (7);
partition_current_month VARCHAR2 (8);
partition_previous_month VARCHAR2 (8);
BEGIN
...
SELECT TO_CHAR(ADD_MONTHS(d_datetime,0),'yyyymm') INTO sys_current_date FROM dual;
SELECT TO_CHAR(ADD_MONTHS(d_datetime,-1),'yyyymm') INTO sys_previous_date FROM dual;
partition_current_month := 'P'|| sys_current_date;
partition_previous_month := 'P'|| sys_previous_date;
EXECUTE IMMEDIATE 'SELECT SUM (found_records) INTO ' || n_record_trovati || ' FROM (
SELECT COUNT(*) as found_records FROM example_table PARTITION (' || partition_current_month || ') WHERE DATETIME = ' || d_datetime || ' AND orderid = ' || n_orderid || ' AND broker = ' || cur.broker || '
UNION
SELECT COUNT(*) as found_records FROM example_table PARTITION (' || partition_previous_month || ') WHERE DATETIME = ' || d_datetime || ' AND orderid = ' || n_orderid || ' AND broker = ' ||cur.broker || '
)';
...
end
I tried dividing the variable names from the rest of the execute immediate string using || operator but an error pops up. Can you tell me what's wrong with my query and how can I fix it? Thanks
You don't use INTO inside the string. It should be
EXECUTE IMMEDIATE 'SELECT SUM (found_records) FROM (
SELECT COUNT(*) as found_records FROM example_table PARTITION (' || partition_current_month || ') WHERE DATETIME = ' || d_datetime || ' AND orderid = ' || n_orderid || ' AND broker = ' || cur.broker || '
UNION
SELECT COUNT(*) as found_records FROM example_table PARTITION (' || partition_previous_month || ') WHERE DATETIME = ' || d_datetime || ' AND orderid = ' || n_orderid || ' AND broker = ' ||cur.broker || '
)' INTO n_record_trovati;

How to format code to '' || chr(10) || using plsql developer

I need to convert a large amount of code into the below format:
'MERGE INTO employees emp' || chr(10) ||
'USING (SELECT * FROM (SELECT (SELECT VALUE
FROM departments
WHERE CNTRY_CDE = ''100''
AND NAME = ''Scott'') BATCH_ID, SUBJECT_ID,' || chr(10) ||
as this code is being inserted in to a table as clob data.
Is there any way to do it?
The above given code is just the sample I have.
If I understand correctly you have an SQL statement which looks something like
'MERGE INTO employees emp USING (SELECT * FROM (SELECT (SELECT VALUE FROM departments WHERE CNTRY_CDE = ''100'' AND NAME = ''Scott'') BATCH_ID, SUBJECT_ID,'
and you want it to look like
'MERGE INTO employees emp' || chr(10) || 'USING (SELECT * FROM (SELECT (SELECT VALUE FROM departments WHERE CNTRY_CDE = ''100'' AND NAME = ''Scott'') BATCH_ID, SUBJECT_ID,' || chr(10) ||
Despite my misgivings about the practicality of this, I suppose one way to accomplish it is something like the following:
declare
strOldstmt VARCHAR2(2000) := 'MERGE INTO employees emp USING (SELECT * FROM (SELECT (SELECT VALUE FROM departments WHERE CNTRY_CDE = ''100'' AND NAME = ''Scott'') BATCH_ID, SUBJECT_ID,';
strNewstmt VARCHAR2(2000);
begin
-- Test statements here
DBMS_OUTPUT.PUT_LINE('strOldstmt=''' || strOldstmt || '''');
strNewstmt := REPLACE(strOldstmt, 'USING', ''' || CHR(10) || '' USING') || ''' || CHR(10) || ';
DBMS_OUTPUT.PUT_LINE('strNewstmt=''' || strNewstmt || '''');
end;
The problem here is that this is very specific to this particular statement, which may not work with other statements; however, it does work for the example you've given and I'm not able to guess at what your more general requirements might be.
Best of luck.

Resources