Solving PL/SQL for loop variable string not resolving completely - for-loop

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

Related

More advanced PL/SQL query result comparison

Wider story
I'm building an automatic grader of my students' queries. I'll start and stop Docker containers for each student and already know how to build the system around it. I'll send the query directly to the database container, together with my own correct query.
Question
I'd like to have 2 things when comparing two queries:
checkbox which switches "Order of columns important, yes/no"
checkbox which switches "Order of rows important, yes/no"
This is what I have for that "core" part.
sqlplus -s system/oracle#localhost:1521/xe <<EOF
set feedback off trimspool on;
spool result.txt;
(select * from cities) MINUS (select id, name from cities);
spool off;
exit;
EOF
I need some suggestions. How would you compare two queries when the order of columns is not important? What about order of rows, do I need to force ORDER BY in every task? What is the best approach to give more automatic feedback, just printing compiler errors?
I'll appreciate every tip.
P.S. I already did my fair share of googling but without much luck for pl/sql.
You can use DBMS_SQL package to create cursor and get column names. What about row order my first assumption was to use ROWNUM pseudocolumn and after addition of it use MINUS. But the problem is that the only way to guarantee the ordering is to use ORDER BY, and I cannot generate correct ORDER BY part in outer select after columns were swapped. Place that column after SELECT with substringing is bad because it is hard to manage UNION then. You can ask to place some dummy column at the topmost SELECT which you then can replace with ROWNUM as rn and then go forth. Or execute both cursors and try to compare them row by row.
The code I get is below. Here you can try it.
declare
v_sql_t varchar(32000) := 'select 1 as a, ''q'' as b from dual union all select 2, ''a'' from dual order by 1';
v_sql_s varchar(32000) := 'select 1 as a, ''q'' as b from dual union all select 2, ''a'' from dual order by 2';
v_cursor integer;
v_col_count integer;
v_cols_t dbms_sql.desc_tab;
v_cols_s dbms_sql.desc_tab;
v_cols_select varchar2(32000);
v_select_keyword_begin integer;
v_final_select varchar2(32000);
v_col_order integer := 1;
v_row_order integer := 1;
begin
v_cursor := dbms_sql.open_cursor;
dbms_sql.parse(v_cursor, v_sql_t, dbms_sql.native);
dbms_sql.describe_columns(v_cursor, v_col_count, v_cols_t);
dbms_sql.parse(v_cursor, v_sql_s, dbms_sql.native);
dbms_sql.describe_columns(v_cursor, v_col_count, v_cols_s);
--Check column order
for i in 1..v_cols_t.count loop
if v_col_order > 0 and v_cols_t(i).col_name != v_cols_s(i).col_name then
dbms_output.put_line('Wrong column order. Should be: ' || v_cols_t(i).col_name || ', got ' || v_cols_s(i).col_name);
end if;
if i = 1 then
v_cols_select := v_cols_t(i).col_name;
else
v_cols_select := v_cols_select || ',' || v_cols_t(i).col_name;
end if;
end loop;
if v_row_order > 0 then
v_cols_select := v_cols_select || ', rownum as rn';
end if;
v_final_select := 'with t as ( select ' || v_cols_select || ' from (' || v_sql_t || ' ) )' || chr(10)
|| ', s as ( select ' || v_cols_select || ' from (' || v_sql_s || ' ) )' || chr(10)
|| ', t_s as (select * from t minus select * from s)' || chr(10)
|| ', s_t as (select * from s minus select * from t)' || chr(10)
|| 'select ''T'' as src, t_s.* from t_s
union all
select ''S'' as src, s_t.* from s_t';
dbms_output.put_line(v_final_select);
end;
/

PL/SQL developer export query result as merge statements

I just started to use PL/SQL Developers tools. Before I used to use Toad for Oracle.
In Toad, I can generate Merge Statement from the Query result for every records and it give me following example result. (for single result)
MERGE user T
USING (
select
1 as id,
'nam' as name,
'sur' as surname
from
dual
) S
ON (S.id = T.id)
WHEN MATCHED
THEN UPDATE
SET T.name = S.name,
T.surname = S.surname
WHEN NOT MATCHED BY TARGET
THEN INSERT (id, name, surname)
VALUES (t.id, t.name, t.surname);
How can I generate "Merge Statement" in PL/SQL developer? If don't, is anyway to generate this statement? Thanks for your help!
Had the same problem here today and I just created an small script what generate it for me and put the output to DBMS Output. Just enable it an set the Buffer high enough.
Then run following script, adjust table and column names:
BEGIN
FOR r_cur IN ( SELECT column_one,
column_two,
column_three,
column_value
FROM some_table
WHERE column_one LIKE 'something%'
ORDER BY column_one, column_two, column_three)
LOOP
DBMS_OUTPUT.put_line (
'MERGE INTO some_table A USING
(SELECT\n
''' || r_cur.column_one || ''' as column_one,
''' || r_cur.column_two || ''' as column_two,
''' || r_cur.column_three || ''' as column_three,
''' || r_cur.column_value || ''' as column_value
FROM DUAL) B
ON (A.column_one = B.column_one and A.column_two = B.column_two and A.column_three = B.column_three)
WHEN NOT MATCHED THEN
INSERT (
column_one, column_two, column_three, column_value)
VALUES (
B.column_one, B.column_two, B.column_three, B.column_value)
WHEN MATCHED THEN
UPDATE SET
A.column_value = B.column_value;
' );
END LOOP;
END;
/

Find MAX(PK_COLUMN) and display it with corresponding Table Name

I am having 20 tables ( Each table has a PK and data ), i want to find out what is the current MAX(PK) Value for each table.
I Want the result as follows :
TABLE_NAME MAX_VAL
-------------------- ----------
TABELE_A 114
TABELE_B 55
TABELE_C 14
TABELE_D 866
TABELE_3 4552
is there any way to accomplish this or else i have to write 20 times SELECT MAX(PK_COL) FROM TABLE ?
Assuming your currently connected schema is composed of those twenty tables, and each have identical primary key column name(pk_col), then consider the following code block containing an implicit cursor :
declare
v_max pls_integer;
begin
dbms_output.put_line('table_name max_val');
for c in ( select * from user_tables )
loop
execute immediate 'select max(pk_col) from '||c.table_name into v_max;
dbms_output.put_line(c.table_name||' '||v_max);
end loop;
end;
/
i have found another method which will bring TABLE_NAME,PK_COLUMN and MAX( PK_COLUMN ).
SELECT CASE
WHEN RN = 1 THEN
FORMATTED_QUERY_SET
ELSE
FORMATTED_QUERY_SET || ' UNION ALL '
END AS FORMATTED_QUERY_SET
FROM (SELECT ' SELECT NVL(MAX( ' || COL.COLUMN_NAME ||
' ),0) CURR_MAX_VAL, ''' || TAB.TABLE_NAME ||
''' TABLE_NAME,''' || COL.COLUMN_NAME ||
''' COLUMN_NAME FROM ' || TAB.TABLE_NAME AS FORMATTED_QUERY_SET,
TAB.TABLE_NAME,
ROW_NUMBER() OVER(ORDER BY TAB.TABLE_NAME DESC) AS RN
FROM USER_CONSTRAINTS TAB
JOIN USER_CONS_COLUMNS COL
ON TAB.TABLE_NAME = COL.TABLE_NAME
JOIN USER_TAB_COLUMNS COL2
ON COL.COLUMN_NAME = COL2.COLUMN_NAME
AND COL.TABLE_NAME = COL2.TABLE_NAME
WHERE TAB.CONSTRAINT_TYPE = 'P'
AND COL.CONSTRAINT_NAME LIKE '%_PK'
AND REGEXP_LIKE(COL2.DATA_TYPE, ('NUMB|INTE')))
ORDER BY TABLE_NAME;
Copy the output returned by the above query and execute.
Note : Remove the last ' UNION ALL ' operator from the query string.
Note : Please correct me if i am doing anything wrong .

Using Oracle: Can I use 'dynamically' created variable in the pivot clause?

I'm trying to create pivot clause that gets it parameters from variable.
I have some test code like:
SELECT * FROM (
SELECT U.USER_ID, U.USER_NAME, E.EXAM_ID, EU.EXAM_DATE
FROM USERS U, EXAMS E, EXAM_USER EU
WHERE U.USER_ID = EU.USER_ID(+)
AND E.EXAM_ID(+) = EU.EXAM_ID
ORDER BY U.USER_ID
)
PIVOT (MAX(EXAM_DATE) FOR EXAM_ID IN ('3' AS "exam 3",'2' AS "exam 2",'1' AS "exam 1"))
order by 1
;
This works just fine. Then I declared the variable EXAM_IDS like:
DECLARE
EXAM_IDS VARCHAR2 (255);
BEGIN
SELECT LISTAGG('''' || EXAM_ID || ''' AS "' || EXAM_NAME || '"', ',')
WITHIN GROUP (ORDER BY EXAM_ID DESC)
INTO EXAM_IDS
FROM EXAMS;
END;
I'm pretty sure the variable EXAM_IDS has now a string as used in pivot clause(?) but I don know how to combine these two:
DECLARE
EXAM_IDS VARCHAR2 (255);
BEGIN
SELECT LISTAGG('''' || EXAM_ID || ''' AS "' || EXAM_NAME || '"', ',')
WITHIN GROUP (ORDER BY EXAM_ID DESC)
INTO EXAM_IDS
FROM EXAMS;
END;
SELECT * FROM (
SELECT U.USER_ID, U.USER_NAME, E.EXAM_ID, EU.EXAM_DATE
FROM USERS U, EXAMS E, EXAM_USER EU
WHERE U.USER_ID = EU.USER_ID(+)
AND E.EXAM_ID(+) = EU.EXAM_ID
ORDER BY U.USER_ID
)
PIVOT (MAX(EXAM_DATE) FOR EXAM_ID IN (' || EXAM_IDS || '))
order by 1
;
And this does not work. Is there a way to do this or should I just run two separate SQL queries?
More info about this setup (like my classes) can be found from Using Oracle combine three tables to one with PIVOT
For 12c and above, you may use DBMS_SQL.RETURN_RESULT by opening a REFCURSOR for the dynamic PIVOT query.
I have removed the notorious (+) syntax for left join, always use the ANSI join syntax.
DECLARE
exam_ids VARCHAR2(255);
x SYS_REFCURSOR;
BEGIN
SELECT
LISTAGG(''''
|| exam_id
|| ''' AS "'
|| exam_name
|| '"',',') WITHIN GROUP(
ORDER BY
exam_id DESC
)
INTO exam_ids
FROM
exam;
OPEN x FOR 'SELECT
*
FROM
(
SELECT
u.user_id,
u.user_name,
e.exam_id,
eu.exam_date
FROM
users u
LEFT JOIN exam_user eu ON u.user_id = eu.user_id
LEFT JOIN exam e ON e.exam_id = eu.exam_id
ORDER BY
u.user_id
)
PIVOT ( MAX ( exam_date )
FOR exam_id
IN ( ' || EXAM_IDS || ' )
)
ORDER BY
1';
dbms_sql.return_result(x);
END;
/
For 11g, you may use a bind variable and print command ( works in sqlplus and in sql developer/Toad when run as script (F5))
variable x REFCURSOR -- bind variable declared.
DECLARE
.. -- no need to declare sys_refcursor
BEGIN
..
OPEN :x FOR 'SELECT . --note the change with colon
*
FROM
(
SELECT
..
END;
/
print x

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