Oracle SQL/PLSQL: change type of specific columns in one time - sql

Assume following table named t1:
create table t1(
clid number,
A number,
B number,
C number
)
insert into t1 values(1, 1, 1, 1);
insert into t1 values(2, 0, 1, 0);
insert into t1 values(3, 1, 0, 1);
clid A B C
1 1 1 1
2 0 1 0
3 1 0 1
Type of columns A, B, and C is number. What I need to do is to change types of those columns to VARCHAR but in a quite tricky way.
In my real table I need to change datatype for hundred of columns so it is not so convenient to write a statement like following hundred of time:
ALTER TABLE table_name
MODIFY column_name datatype;
What i need to do is rather to convert all columns to VARCHAR except CLID column like we can do that in Python or R
Is there any way to do so in Oracle SQL or PLSQL?
Appreciate your help.

Here is a example of procedure that can help...
It accepts two parameters that should be a name of your table and list of columns you do not want to change...
At the begining there is a cursor that gets all the column names for your table except the one that you do not want to change...
Then it loop's though the columns and changes them...
CREATE OR REPLACE procedure test_proc(p_tab_name in varchar2
, p_col_names in varchar2)
IS
v_string varchar2(4000);
cursor c_tab_cols
is
SELECT column_name
FROM ALL_TAB_COLS
WHERE table_name = upper(p_tab_name)
and column_name not in (select regexp_substr(p_col_names,'[^,]+', 1, level) from dual
connect by regexp_substr(p_col_names, '[^,]+', 1, level) is not null);
begin
FOR i_record IN c_tab_cols
loop
v_string := 'alter table ' || p_tab_name || ' modify '
|| i_record.column_name || ' varchar(30)';
EXECUTE IMMEDIATE v_string;
end loop;
end;
/
Here is a demo:
DEMO
You can also extend this procedure with a type of data you want to change into... and with some more options I am sure....

Unfortunately, that isn't as simple as you'd want it to be. It is not a problem to write query which will write query for you (by querying USER_TAB_COLUMNS), but - column must be empty in order to change its datatype:
SQL> create table t1 (a number);
Table created.
SQL> insert into t1 values (1);
1 row created.
SQL> alter table t1 modify a varchar2(1);
alter table t1 modify a varchar2(1)
*
ERROR at line 1:
ORA-01439: column to be modified must be empty to change datatype
SQL>
If there are hundreds of columns involved, maybe you can't even
create additional columns in the same table (of VARCHAR2 datatype)
move values in there
drop "original" columns
rename "new" columns to "old names"
because there'a limit of 1000 columns per table.
Therefore,
creating a new table (with appropriate columns' datatypes),
moving data over there,
dropping the "original" table
renaming the "new" table to "old name"
is probably what you'll finally do. Note that it won't be necessarily easy either, especially if there are foreign keys involved.
A "query that writes query for you" might look like this (Scott's sample tables):
SQL> SELECT 'insert into dept (deptno, dname, loc) '
2 || 'select '
3 || LISTAGG ('to_char(' || column_name || ')', ', ')
4 WITHIN GROUP (ORDER BY column_name)
5 || ' from emp'
6 FROM user_tab_columns
7 WHERE table_name = 'EMP'
8 AND COLUMN_ID <= 3
9 /
insert into dept (deptno, dname, loc) select to_char(EMPNO), to_char(ENAME), to_char(JOB) from emp
SQL>
It'll save you from typing names of hundreds of columns.

I think its not possible to change data type of a column if values are there...
Empty the column by copying values to a dummy column and change data types.

Related

Convert a single row with dynamic number of columns into a single column in Oracle

I have to convert a single row that is obtained via select statement into a single column with concatenated values of the individual columns of the result. The problem is that the columns are unknown and can vary in number.
Let's say the table looks similar to this:
Table USER
Name Surname Age Logindate City
Max Smith 25 20.05.20 NY
I need to SELECT * FROM USER and convert the result into a single string like Max, Smith, 25, 20.05.20, NY or with column names Name: Max, Surname: Smith, Age: 25, Logindate: 20.05.20, City: NY that I can afterwards insert into a column of other table. The name of the table that I'm selecting from is known and hardcoded into the SELECT statement that is executed inside a stored procedure.
Since the number of columns and column names are unknown, I cannot use a CONCAT function. I was also going to be satisfied with the output format of SELECT JSON_OBJECT(*) FROM USER, but the function with such usage of star operator is not supported in Oracle18c (it is in Oracle19c).
The transformation of column values of a single row into a single string seems like a basic operation, but I wasn't able to find any simple solution.
Use the data dictionary to generate the right SQL statement and then use dynamic SQL to execute it.
--Sample tables for input and output:
create table user_table as
select 'Max' Name, 'Smith' Surname, 25 Age, date '2020-05-20' LoginDate, 'NY' City
from dual;
create table concatenated_values(value varchar2(4000));
--Procedure to read all columns from USER_TABLE and write them to CONCATENATED_VALUES.
create or replace procedure concatenate_values(p_table_name varchar2) is
v_sql varchar2(4000);
begin
--Generate a SQL statement to concatenate all the values.
select
'select ' ||
listagg(column_name, '||'',''||') within group (order by column_id) ||
' from ' || owner || '.' || table_name
into v_sql
from all_tab_columns
where owner = user
and table_name = p_table_name
group by owner, table_name;
--Run the SQL statement and insert the value.
execute immediate 'insert into concatenated_values ' || v_sql;
end;
/
--Call the procedure.
begin
concatenate_values('USER_TABLE');
end;
/
--Results:
select * from concatenated_values;
VALUE
-------------------------
Max,Smith,25,20-MAY-20,NY

Is it possible to duplicate all values in a table while updating one or more columns

I have a table with many columns, and what I would like to do is duplicate all of the rows in the table, but also update one of the columns to a new value.
For example lets say I have the table below. I want to add to my table a duplicate of each row, except instead of BASIC access, it will have 'ADVANCED':
Before:
NAME, GENDER, ACCESS
----------------------
STEVE, MALE, BASIC
MOLLY, FEMALE, BASIC
After
NAME, GENDER, ACCESS
----------------------
STEVE, MALE, BASIC
MOLLY, FEMALE, BASIC
STEVE, MALE, ADVANCED
MOLLY, FEMALE, ADVANCED
Is there a way to do this without specifying all columns? I have 60 columns in the table, and the structure can change (meaning columns may be added, removed, renamed, etc).
Is it possible in Oracle SQL to automate this?
Just use insert . . . select:
insert into t (name, gender, access)
select name, gender, 'ADVANCED'
from t;
You need to list all the columns. You can shorten the manual process by using a query to generate the list. If you had to do this a lot and always knew you were leaving out access and access is the last column, you could use a view:
create view v_t as
select . . . -- all but access
from t;
insert into t ( . . . )
select v.*, 'ADVANCED'
from v_t;
Or you could use dynamic SQL to generate the statement.
However, I don't recommend any of those. Instead I would be concerned about a data model where you are regularly adding and modifying the columns in a table. That sounds dangerous.
Without specifying all the columns? With some help of a "temporary" table, here's how:
Your current table:
SQL> create table test
2 (name varchar2(10),
3 gender varchar2(20),
4 caccess varchar2(20));
Table created.
SQL> insert into test
2 select 'steve', 'male', 'basic' from dual union all
3 select 'molly', 'female', 'basic' from dual;
2 rows created.
Create a "temporary" table as a copy of the "original" table
update column you want to modify
copy the whole "temporary" table to the "original"
drop the "temporary" table
SQL> create table test_temp as select * From test;
Table created.
SQL> update test_temp set caccess = 'advanced';
2 rows updated.
SQL> insert into test select * From test_temp;
2 rows created.
SQL> drop table test_Temp;
Table dropped.
SQL> select * From test;
NAME GENDER CACCESS
---------- -------------------- --------------------
steve male basic
molly female basic
steve male advanced
molly female advanced
SQL>
Apparently, that works, but - what if the original table is huge? It takes a lot of space, and its copy takes approximately twice as much. Why are you doing that, anyway?
Try below method with anonymous block to avoid listing columns in insert statements
CREATE TABLE ACCESS_CHN
(NAAME VARCHAR2(100),
GENDER VARCHAR2(20),
ACCCESS VARCHAR2(30))
INSERT into ACCESS_CHN values('STEVE','MALE','BASIC');
INSERT into ACCESS_CHN values('MOLLY','FEMALE','BASIC');
COMMIT;
DECLARE
column_list varchar2(2000):=NULL;
plsql_block VARCHAR2(1000);
BEGIN
select LISTAGG(column_name,',') within group (order by column_id)
into column_list
from user_tab_columns
where table_name='ACCESS_CHN';
plsql_block := 'CREATE TABLE ACCESS_CHN_BKP as select '|| column_list || ' from ACCESS_CHN';
EXECUTE IMMEDIATE plsql_block;
plsql_block := 'UPDATE ACCESS_CHN_BKP set ACCCESS=''ADVANCED'' ';
EXECUTE IMMEDIATE plsql_block;
COMMIT;
plsql_block := 'CREATE TABLE ACCESS_CHN_FINAL as select * from ACCESS_CHN
union all
select * from ACCESS_CHN_BKP';
EXECUTE IMMEDIATE plsql_block;
END;
--To rerun drop tables ACCESS_CHN_BKP and ACCESS_CHN_FINAL

Extract first line from all subpartitions in all tables in Oracle database

I want to get the first value for all subpartitions of all tables in my Oracle database.
From my resultset, I want to update another table with the values returned and insert a comment('No value' for example) for subpartitions whic are empty.
I have written a piece of code to begin with one table.
Can you please tell me if the algorithm I am using is right and why there is no output?
DECLARE
BEGIN
for i in (select table_name, subpartition_name
from dba_tab_subpartitions
where table_name ='my_table'
order by 1)
loop
execute immediate ' insert into bkp_part (partition_name) values('||i.subpartition_name||')';
commit;
execute immediate 'select *
from '|| i.table_name||' subpartition('||i.subpartition_name||')
where rownum < 2';
end loop;
end;
END;
/
There is no output because you aren't creating any. Your dynamic query isn't going to be executed because you don't select the columns into anything; if you really want the entire row you'd need a %rowtype variable to select into, but I'm not sure if you really want the whole row or just one value from it. And having selected into something, you then need to use that variable - putting it into a table from what you've said, or displaying for debug purpoises via dbms_output.
Your insert statement doesn't need to be dynamic; you can just do:
insert into bkp_part (partition_name) values (i.subpartition_name);
To get the first row from each subpartition, you need to establish what you mean by 'first'. In this example I'm using a hash subpartition, so for the sake of argument I'll decide that the 'first' row is the lowest value in that hash bucket, and to keep it simple I'm only interested in that single column.
I've set up a dummy table with:
create table my_table (id number, part_date date)
partition by range (part_date)
subpartition by hash (id)
subpartition template (
subpartition subpart_1,
subpartition subpart_2,
subpartition subpart_3)
(
partition part_1 values less than (date '2015-01-01')
);
insert into my_table values (1, date '2014-12-29');
insert into my_table values (2, date '2014-12-30');
insert into my_table values (4, date '2014-12-31');
Looking at how the data is distributed, I have nothing in the first subpartition, IDs 1 and 4 in the second, and ID 2 in the third.
That means I can do:
set serveroutput on
declare
l_id my_table.id%type;
begin
for i in (
select table_name, subpartition_name
from user_tab_subpartitions
where table_name = 'MY_TABLE'
order by table_name, subpartition_position
) loop
-- skipping because I don't have that table
-- insert into bkp_part (partition_name) values (i.subpartition_name);
execute immediate 'select min(id) from ' || i.table_name
|| ' subpartition(' || i.subpartition_name || ')'
into l_id;
-- just for debugging/demo
dbms_output.put_line('Subpartition ' || i.subpartition_name
|| ' minimum ID is ' || l_id);
-- do something else with the value; update or insert...
-- update bkp_part set min_id = l_id
-- where partition_name = i.subpartition_name;
end loop;
end;
/
anonymous block completed
Subpartition PART_1_SUBPART_1 minimum ID is
Subpartition PART_1_SUBPART_2 minimum ID is 1
Subpartition PART_1_SUBPART_3 minimum ID is 2
You can adapt that to get the columns you're interested in and do something more useful with them (like inserting/updating your bkp_part table)

How to merge (combine) 2 columns into 1 in oracle?

I have 3 textfields where user types table name and 2 column names which need to be merged.
How should I merge (combine) 2 column values into 1?
I use oracle 11g enterprise
concatenate?
select col1 || ' ' || col2 from tablex
This is a very vague requirement. Concatenate the values maybe?
insert into sometable( Column1 )
values ( Column1 || Column2 );
If you need to specify the table name to INSERT into, then you will need to use dynamic SQL to achieve this. Would you need to specify the target column name as well? This example assumes you would use PL/SQL, which may not be appropriate in your case.
sql_stmt := 'INSERT INTO '|| specified_table || '(' || merge_column || ') VALUES ( :1 )';
EXECUTE IMMEDIATE sql_stmt USING column1 || column2;
http://docs.oracle.com/cd/B13789_01/appdev.101/b10807/13_elems017.htm
You could make another column (an auxiliary column ) and replace the other 2 columns with this one.
Merging columns in Oracle Live
SELECT CONCAT (ENAME, JOB) from scott.EMP
the above command will merge ENAME and JOB columns, and these 2 different columns will be shown in a single column

Normalize columns names in ORACLE 11g

I need remove the quotes from the names of the columns in many tables in my schema. there is any way to automate this process?, any function in oracle or some tool that allows me to change the names of the columns removing the quotes. I am using oracle 11g.
UPDATE
I'm sorry, I had to rephrase my question.
thanks in advance.
I assume here by "fields" you mean "column names".
Keep in mind that column names in Oracle are not case sensitive unless you put them in quotes when creating the table. It's generally not a good idea to use quotes around the column names when creating the table. In other words, if you create the table like this:
CREATE TABLE FOO (
colUMN1 varchar2(10),
CoLumn2 number(38)
)
Then you can still run select statements like this:
SELECT column1, column2 FROM FOO
You can also do this:
SELECT COLUMN1, COLUMN2 FROM FOO
Also note that if you run this query, you'll see that Oracle stored the column names as uppercase in the data dictionary:
SELECT * FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'FOO'
So there's no need to rename these columns to all uppercase. The queries you write can use all uppercase column names (assuming the tables weren't created using quotes around the column names) and they'll work fine. It's generally a bad idea to try to force them to be case sensitive.
If you just want to get rid of all the case sensitive column names
SQL> create table foo ( "x" number );
Table created.
SQL> ed
Wrote file afiedt.buf
1 begin
2 for x in (select *
3 from user_tab_cols
4 where column_name != UPPER(column_name))
5 loop
6 execute immediate 'ALTER TABLE ' || x.table_name ||
7 ' RENAME column "' || x.column_name || '"' ||
8 ' TO ' || upper(x.column_name);
9 end loop;
10* end;
SQL> /
PL/SQL procedure successfully completed.
SQL> desc foo
Name Null? Type
----------------------------------------- -------- ----------------------------
X NUMBER