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.
Related
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;
/
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 do I get the data(i.e rows) from the column_name I retrieved from SQL statement? (Completely new to PL/SQL).
Here is my code:
create or replace procedure com_coll_cur
as
type comColcur is ref cursor;
com_col_cur comColcur;
sql_stmt varchar2(4000);
type newtab_field is table of comColcur%TYPE;
begin
sql_stmt :=
'select column_name from all_tab_cols where table_name in (''TAB1'', ''TAB2'') ' ||
'group by column_name having count(*)>1';
open com_col_cur for sql_stmt;
loop
fetch com_col_cur into newtab_field;
exit when com_col_cur%NOTFOUND;
end loop;
close com_col_cur;
end;
What I'm trying to do here is first find the common columns between the two tables. This part only grabs column_name but I also want the data in that common columns. So I used cursor to "point" that common column_name and used loop(fetch) to get the data inside that common column_name. Finally, I want to create new table with this common columns only with their data.
I am new to everything here and any help will be appreciate it.
You don't understand how works cursors and fetch.
Fetch get the data from the cursor, so in your procedure example you get only column names, not the data in the columns. To get data you need another cursor - select from the target table or use the dynamic sql.
This is a code that do what you describe. It is not clear to me how you want to store data from two tables - subsequently or in another manner. Let's assume that we store them subsequently. Also this code suggests than columns with the same names have the same datatypes. Part of this code (to make datatype) I get from another stackoverflow post to save time to write it:
How do I get column datatype in Oracle with PL-SQL with low privileges?
dbms_output.put_line - used to print sql statements that we create
declare
cSql varchar2(4000);
cCols varchar2(4000);
cNewTableName varchar2(30) := 'AABBCC';
cTb1 varchar2(30) := 'TAB1';
cTb2 varchar2(30) := 'TAB2';
begin
for hc in (
select T.column_name, T.typ
from
(
select column_name,
data_type||
case when data_precision is not null and nvl(data_scale,0)>0 then '('||data_precision||','||data_scale||')'
when data_precision is not null and nvl(data_scale,0)=0 then '('||data_precision||')'
when data_precision is null and data_scale is not null then '(*,'||data_scale||')'
when char_length>0 then '('||char_length|| case char_used
when 'B' then ' Byte'
when 'C' then ' Char'
else null
end||')'
end||decode(nullable, 'N', ' NOT NULL') typ
from all_tab_cols
where table_name in (cTb1, cTb2) ) T
group by T.column_name, T.typ having count(*) > 1)
loop
cSql := cSql || case when cSql is null then null else ',' end || hc.column_name || ' ' || hc.typ;
cCols := cCols || case when cCols is null then null else ',' end || hc.column_name;
end loop;
if (cSql is not null) then
-- First drop table if it exists
for hc in (select * from all_objects where object_type = 'TABLE' and object_name = cNewTableName)
loop
execute immediate 'drop table ' || hc.object_name;
end loop;
-- create table
cSql := 'create table ' || cNewTableName || '(' || cSql || ')';
dbms_output.put_line(cSql);
execute immediate cSql;
-- insert data
cSql := 'insert into ' || cNewTableName || '(' || cCols || ') select ' || cCols || ' from ' || cTb1;
dbms_output.put_line(cSql);
execute immediate cSql;
cSql := 'insert into ' || cNewTableName || '(' || cCols || ') select ' || cCols || ' from ' || cTb2;
dbms_output.put_line (cSql);
execute immediate cSql;
end if;
end;
I am using Oracle 11g. My tables include columns like name and l_name (lowercase of name column). I am trying to iterate through all the columns in my table space to set the l_ columns to lowercase of their respective uppercase columns. Here is what I tried:
for i in (select table_name from user_tables) loop
SELECT SUBSTR(column_name,3) bulk collect into my_temp_storage FROM user_tab_columns WHERE table_name = i.table_name and column_name like 'L\_%' escape '\';
for j in (select column_name from user_tab_columns where table_name = i.table_name) loop
for k in 1..my_temp_storage.count
loop
if(j.column_name like 'L\_%' escape '\' and SUBSTR(j.column_name,3) = my_temp_storage(k)) then
DBMS_OUTPUT.PUT_LINE( 'update ' || i.table_name || ' set ' || j.column_name || ' = LOWER(' ||my_temp_storage(k)|| ') where ' || j.column_name || ' is not null');
execute immediate 'update ' || i.table_name || ' set ' || j.column_name || ' = LOWER(' ||my_temp_storage(k)|| ') where ' || j.column_name || ' is not null';
end if;
end loop;
end loop;
end loop;
I am storing all the names of columns in uppercase in my_temp_storage and updating the table with the LOWER value of the columns in my_temp_storage. This gave me an error saying:
Error report -
ORA-00900: invalid SQL statement
ORA-06512: at line 8
00900. 00000 - "invalid SQL statement"
*Cause:
*Action:
But the DBMS output seemed to be fine:
`update EMPLOYEE set L_NAME = LOWER(NAME) where L_NAME is not null`
Could you help me with the way I did or any other way it can be done?
The program could certainly be simplified:
begin
for i in (select table_name, column_name from user_tab_columns
where column_name like 'L\_%' escape '\')
loop
l_sql := 'update ' || i.table_name || ' set ' || i.column_name
|| ' = LOWER(' ||substr(i.columm_name,3)
|| ') where ' || i.column_name || ' is not null';
execute immediate l_sql;
end loop;
end;
It seems an odd database design though. Have you considered virtual columns, and/or function-based indexes, instead of manually maintained columns?
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.