Oracle: update table using dynamic column names - oracle

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?

Related

How to solve SQL Error [946] [42000]: ORA-00946: missing TO keyword?

I am trying to change all PK constraint names. Below is the code I am trying to run:
BEGIN
FOR i IN (
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME
FROM ALL_CONSTRAINTS
WHERE OWNER = 'SOME_SCHEME'
AND TABLE_NAME NOT LIKE 'flyway%'
AND TABLE_NAME NOT LIKE 'BIN%'
AND CONSTRAINT_TYPE = 'P')
LOOP
dbms_output.put_line(i.CONSTRAINT_NAME || i.table_name);
EXECUTE IMMEDIATE 'ALTER TABLE i.table_name rename constraint i.constraint_name to i.table_name || ''_PK''';
END LOOP;
END;
I am getting the following error:
SQL Error [946] [42000]: ORA-00946: missing TO keyword
ORA-06512: at line 17
ORA-06512: at line 17
Why does it say TO is missing when it is not?
How can this problem be solved?
If you use dbms_output to display the statement before you execute it, you'll see it's malformed; whatever the table and constraint, the statement you are currently trying to execute is, literally:
ALTER TABLE i.table_name rename constraint i.constraint_name to i.table_name || '_PK'
The i.* parts are not variables or values, they are literally those strings. Running that statement manually gets the same error, of course.
You need to concatenate the loop variable values into the statement; you also need to specify the owner, since you're looking at all_tables, and you might as well quote the names:
DECLARE
l_stmt varchar2(4000);
BEGIN
FOR ...
LOOP
dbms_output.put_line(i.CONSTRAINT_NAME || i.table_name);
l_stmt := 'ALTER TABLE SOME_SCHEME."' || i.table_name || '"'
|| ' rename constraint "' || i.constraint_name || '"'
|| ' to "' || i.table_name || '_PK"';
dbms_output.put_line(l_stmt);
EXECUTE IMMEDIATE l_stmt;
END LOOP;
END;
/
which generates something more like:
ALTER TABLE SOME_SCHEME."SDO_DATUMS" rename constraint "DATUM_PRIM" to "SDO_DATUMS_PK"
db<>fiddle
I've hard-coded the SOME_SCHEME name you use in the loop query; you could also get that from the query itself by adding OWNER to the select list, and then concatenate that in as well (again, quoted to be overly cautious). Or set that in a local variable and use that in both places, as #MTO suggested in a comment.
If you will only run this as the SOME_SCHEME user then you can query user_constraints instead:
DECLARE
l_stmt varchar2(4000);
BEGIN
FOR i IN (
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME
FROM USER_CONSTRAINTS
WHERE TABLE_NAME NOT LIKE 'flyway%' -- are these really quoted lower-case?
AND TABLE_NAME NOT LIKE 'BIN%'
AND CONSTRAINT_TYPE = 'P'
AND CONSTRAINT_NAME != TABLE_NAME || '_PK'
)
LOOP
l_stmt := 'ALTER TABLE "' || i.table_name || '"'
|| ' RENAME constraint "' || i.constraint_name || '"'
|| ' TO "' || i.table_name || '_PK"';
dbms_output.put_line(l_stmt);
EXECUTE IMMEDIATE l_stmt;
END LOOP;
END;
/
db<>fiddle with example tables, with quoted and unquoted identifiers.
You might also want your loop query to include:
AND CONSTRAINT_NAME != TABLE_NAME || '_PK'
... so you don't touch constraints that already have the name pattern you want.

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 do I get the data from the column_name? Oracle PL/SQL

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;

Oracle how to uppercase columns name

I would like to create a function who uppercase all columns name, in a oracle db.
I don't use oracle everyday, so I need help.
I would like this, but for Oracle :
CREATE OR REPLACE FUNCTION uppercase_fields(schemaname text) RETURNS void AS $$
DECLARE
r RECORD;
full_table text;
geom_type_count integer;
BEGIN
FOR r IN
EXECUTE 'SELECT table_name, table_schema, column_name FROM information_schema.columns WHERE table_schema = $1 AND column_name <> upper(column_name)'
USING schemaname
LOOP
EXECUTE 'ALTER TABLE "' || r.table_schema || '"."' || r.table_name || '" RENAME "' || r.column_name || '" to "' || upper(r.column_name) || '"';
END LOOP;
END;
$$ LANGUAGE plpgsql;
Thanks
Oracle, as the default functionality, will covert all unquoted table/column identifiers to upper case - so you do not need to use the UPPER function; just leave the identifiers unquoted.
To find the data you require you want the ALL_TAB_COLUMNS or USER_TAB_COLUMNS tables from the data dictionary:
BEGIN
FOR r IN ( SELECT owner, table_name, column_name
FROM ALL_TAB_COLUMNS
WHERE owner IN ( 'your', 'list' , 'of', 'tablespaces' )
AND column_name <> UPPER( column_name )
)
LOOP
EXECUTE 'ALTER TABLE "' || r.owner || '"."' || r.table_name
|| '" RENAME COLUMN "' || r.column_name || '" TO ' || r.column_name;
END LOOP;
END;
/
If your column names are reserved words or otherwise cannot be in an unquoted identifier then you can use:
BEGIN
FOR r IN ( SELECT owner, table_name, column_name
FROM ALL_TAB_COLUMNS
WHERE owner IN ( 'your', 'list' , 'of', 'tablespaces' )
AND column_name <> UPPER( column_name )
)
LOOP
EXECUTE 'ALTER TABLE "' || r.owner || '"."' || r.table_name
|| '" RENAME COLUMN "' || r.column_name || '" TO "' || UPPER(r.column_name) || '"';
END LOOP;
END;
/
For change to uppercase all columns name in Oracle, use this solution:
DECLARE
TARGET_TABLE_NAME VARCHAR2(31) := 'ENTER_YOUR_TARGET_TABLE_NAME_HERE';
BEGIN
FOR I IN (
SELECT
COLUMN_NAME
FROM
ALL_TAB_COLUMNS
WHERE
TABLE_NAME = TARGET_TABLE_NAME
AND
COLUMN_NAME <> UPPER(COLUMN_NAME)
)
LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' ||
TARGET_TABLE_NAME ||
' RENAME COLUMN "' ||
I.COLUMN_NAME ||
'" TO ' ||
UPPER(I.COLUMN_NAME);
END LOOP;
END;
/
Just replace the name of your target table by ENTER_YOUR_TARGET_TABLE_NAME_HERE

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