How to Modify the Size of a Datatype in a Target table A matching it with the source table B for a same Column Name - sql

Table A(Target) Name Varchar2(40)
Table B(source) Name Varchar2(60)
I need a script where A and B table should be compared and if the Datatype size dosen't match, then the datatype size in the A table should be altered to that of Datatype size of Table B.
Result
Table A(Target) Name Varchar2(60)
Table B(source) Name Varchar2(60)

It is the ALTER TABLE you'd use.
Sample table:
SQL> create table table_a (name varchar2(40));
Table created.
SQL> insert into table_a values ('Littlefoot');
1 row created.
How to modify column's size:
SQL> alter table table_a modify name varchar2(60);
Table altered.
No problem with making it larger, but - you might have problems if you'd want to make it smaller and length of some data in that column is longer than desired target size. In this example, 'Littlefoot' has 10 letters so making the column smaller than that raises an error:
SQL> alter table table_a modify name varchar2(5);
alter table table_a modify name varchar2(5)
*
ERROR at line 1:
ORA-01441: cannot decrease column length because some value is too big
SQL>
If you want to write some code which will do that job for you, you'll have to query user_tab_columns and use dynamic SQL (i.e. execute immediate). For example:
SQL> create table table_a (id number, name varchar2(40));
Table created.
SQL> create table table_b (id number, name varchar2(60));
Table created.
SQL> begin
2 for cur_r in (select a.column_name, a.data_length a_len,
3 b.data_length b_len
4 from user_tab_columns a join user_tab_columns b on a.column_name = b.column_name
5 where a.table_name = 'TABLE_A'
6 and b.table_name = 'TABLE_B'
7 and a.data_type = 'VARCHAR2'
8 )
9 loop
10 if cur_r.a_len < cur_r.b_len then
11 execute immediate 'alter table table_a modify ' || cur_r.column_name || ' varchar2(' ||
12 cur_r.b_len ||')';
13 end if;
14 end loop;
15 end;
16 /
PL/SQL procedure successfully completed.
What's the outcome?
SQL> desc table_a;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(60) --> this
SQL> desc table_b;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(60) --> this
SQL>
Columns' sizes match.
Of course, that simple code might require additional settings (if you'd want to affect other datatypes), but that should give you initial idea.

Related

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

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.

How to drop many columns in table in oracle when columns are very large?

I have a table and the table consists of 70 columns.I want to drop about 68 columns and i just want 2 columns in my table.So,whatever I tried is given below:
Alter table_name drop column where column
_name not in (col1, col2);
I found this step is time consuming. Is there any other better option where I can delete 68 columns without getting much time engaged?
Create a new table with a SELECT: CREATE new_table AS SELECT col1, col2 FROM old_table
Drop the old table (use the purge option if you don't want it in the recycle bin)
Rename the new table to the old table name: RENAME new_table TO old_table
You'll also need to add back any indexes, triggers, constraints or grants that you had on the old table if you want them on the new table.
Write a script to generate the DDL to save typing.
CREATE TABLE test_drop ( a number, b number, c number, d number, e number);
select 'alter table ' || owner || '.' || table_Name || ' drop column ' || listagg(column_name,',') within group ( order by column_name) || ';' drop_ddl
from dba_tab_columns
where owner = user
and table_Name = 'TEST_DROP'
and column_Name not in ('A','B')
group by owner, table_name;
+-----------------------------------------------+
| DROP_DDL |
+-----------------------------------------------+
| alter table APPS.TEST_DROP drop column C,D,E; |
+-----------------------------------------------+
Then copy the output to and you have your DDL. Recreating the table is an unnecessary pain.

Select records from table where table name come from another table

We generate tables dynamically Eg. Table T_1, T_2, T_3, etc & we can get that table names from another table by following query.
SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC';
Now I want to get records from this retrieved table name. What can I do ?
I'm doing like following but that's not working :
SELECT * FROM (SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC')
FYI : I'm hitting two individual queries as of now though I want to eliminate one and I can not follow cursor/procedure approach due to some limitations.
A procedure which utilizes refcursor seems to be the most appropriate to me. Here's an example:
SQL> -- creating test case (your T_NAMES table and T_1 which looks like Scott's DEPT)
SQL> create table t_names (t_id number, t_key varchar2(3));
Table created.
SQL> insert into t_names values (1, 'ABC');
1 row created.
SQL> create table t_1 as select * from dept;
Table created.
SQL> -- a procedure; accepts KEY and returns refcursor
SQL> create or replace procedure p_test
2 (par_key in varchar2, par_out out sys_refcursor)
3 as
4 l_t_name varchar2(30);
5 begin
6 select 'T_' || t_id
7 into l_t_name
8 from t_names
9 where t_key = par_key;
10
11 open par_out for 'select * from ' || l_t_name;
12 end;
13 /
Procedure created.
OK, let's test it:
SQL> var l_out refcursor
SQL> exec p_test('ABC', :l_out)
PL/SQL procedure successfully completed.
SQL> print l_out
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
I could propose to you Dynamic SQL.
First of all, you need to create a cursor. The cursor will iterate by the dynamic tables. Then you could use dynamic SQL to create a query and then execute it.
So example:
https://livesql.oracle.com/apex/livesql/file/content_C81136WLRFYZF8ION6Q57GWE1.html - detailed cursor example.
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm#i13057 - dynamic SQL in Oracle

Autopopulating a column with a Trigger SQL

I'm trying to start a sequence that automatically populates a column when an insertion has been made. It should start from 500 and increment by 1. Any idea how would I go about doing this? So far I have something like this, but it seems to crash
CREATE TRIGGER ADD_TRIGGER ON tablename
AFTER INSERT
AS
BEGIN
ADD colname NUMBER IDENTITY(500,1);
END
GO
You can create a sequence
CREATE SEQUENCE mySeq
START WITH 500
INCREMENT BY 1
CACHE 100;
and then use it in your trigger
CREATE OR REPLACE TRIGGER myTrigger
AFTER INSERT ON table_name
FOR EACH ROW
BEGIN
SELECT mySeq.nextval
INTO :new.colname
FROM dual;
END;
Oracle 12c introduces IDENTITY COLUMNS.
SQL> CREATE TABLE new_identity_table
2 (
3 ID NUMBER GENERATED ALWAYS AS IDENTITY,
4 text VARCHAR2(50)
5 );
Table created.
SQL>
SQL> INSERT
2 INTO new_identity_table
3 (
4 text
5 )
6 VALUES
7 (
8 'This table has an identity column'
9 );
1 row created.
SQL> column text format A40;
SQL>
SQL> select * from new_identity_table;
ID TEXT
---------- ----------------------------------------
1 This table has an identity column
Oracle creates a sequence to populate the identity column. You can find it named as ISEQ$$
SQL> select sequence_name, min_value, max_value, increment_by from user_sequences;
SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY
-------------------- ---------- ---------------------------- ------------
ISEQ$$_93199 1 9999999999999999999999999999 1
More more information about the identity columns, use the ALL_TAB_IDENTITY_COLS view.
SQL> SELECT table_name,
2 column_name,
3 generation_type,
4 identity_options
5 FROM all_tab_identity_cols
6 WHERE owner = 'LALIT'
7 ORDER BY 1, 2;
TABLE_NAME COLUMN_NAME GENERATION IDENTITY_OPTIONS
-------------------- --------------- ---------- --------------------------------------------------
NEW_IDENTITY_TABLE ID ALWAYS START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 9999999
999999999999999999999, MIN_VALUE: 1, CYCLE_FLAG: N
, CACHE_SIZE: 20, ORDER_FLAG: N
For pre 12c releases, see Auto-increment primary key in Pre 12c releases (Identity functionality)

Convert LONG into VARCHAR2 or some text datatype

As we all know LONG is deprecated in Oracle a long back but Oracle itself is still using this datatype in their views.
So if I have to change LONG into some kind of text datatype how can I achieve that.
I am trying to query this and getting error.
ORA-00932: inconsistent datatypes: expected - got LONG
Query -
SELECT NVL(ie.column_expression, ic.column_name)
from user_ind_columns ic left join user_ind_expressions ie on ic.index_name = ie.index_name and ic.table_name = ie.table_name
where ic.table_name = 'Some Table'
There are several methods, one such is create table using TO_LOB. It is designed to convert a LONG or LONG RAW column to a CLOB or BLOB, respectively. Other methods are using PL/SQL, DBMS_XMLGEN. You can also use TO_LOB in insert statements.
Let's see how to convert LONG into CLOB-
SQL> CREATE TABLE t (x INT, y LONG);
Table created.
SQL>
SQL> INSERT INTO t VALUES (1, RPAD('*',9,'!'));
1 row created.
SQL> INSERT INTO t VALUES (2, RPAD('*',9,'#'));
1 row created.
SQL> INSERT INTO t VALUES (3, RPAD('*',9,'#'));
1 row created.
SQL> COMMIT;
Commit complete.
SQL>
So, we have table t with column y s LONG data type.
SQL> CREATE TABLE t1
2 AS
3 SELECT * FROM t
4 /
SELECT * FROM t
*
ERROR at line 3:
ORA-00997: illegal use of LONG datatype
SQL>
We can see the LONG restriction.
Let's use TO_LOB to convert it into CLOB.
SQL> CREATE TABLE t1
2 AS
3 SELECT x,
4 to_lob(y) as y
5 FROM t
6 /
Table created.
SQL> desc t1;
Name Null? Type
----------------------------------------------------- -------- ------------------------------------
X NUMBER(38)
Y CLOB
SQL>
Now you have the same table with the LONG column converted to CLOB.
this is stupid (as in probably not efficient) but it works for samll lengths of y (ie < 2000 characters)..
CREATE TABLE t (x INT, y LONG);
INSERT INTO t VALUES (1, RPAD('*',9,'!'));
CREATE TABLE t1
AS
SELECT x,
regexp_substr(SYS.DBMS_XMLGEN.GETXML('select y from t where rowid = '''||rowid||''''),'<Y>(.*)</Y>',1,1,'in',1) y
FROM t
/
it works by using dbms_xmlgen to generate a clob based on the LONG column.. then substr-ing the value back out.
this only works for small contents of the LONG column. but that is all i had and this worked for me.
I had a similar need, to list the objects and their sizes (including info on columns used in indexes), and came with this solution:
select idx1.table_owner owner, idx1.table_name, idx1.index_name, listagg(nvl(idx1.column_expression,idx1.column_name),',') within group (order by idx1.column_position) column_name
from xmltable(
'/ROWSET/ROW'
passing (select dbms_xmlgen.getxmltype('select ic.table_owner, ic.table_name, ic.index_name, ic.column_position, ic.column_name, ie.column_expression
from all_ind_columns ic
left outer join dba_ind_expressions ie on ie.table_owner=ic.table_owner and ie.table_name=ic.table_name and ie.index_name=ic.index_name and ie.column_position=ic.column_position') from dual)
columns index_name varchar2(30) path 'INDEX_NAME'
, table_owner varchar2(30) path 'TABLE_OWNER'
, table_name varchar2(30) path 'TABLE_NAME'
, column_position number path 'COLUMN_POSITION'
, column_name varchar2(30) path 'COLUMN_NAME'
, column_expression varchar2(4000) path 'COLUMN_EXPRESSION') idx1
group by idx1.table_owner, idx1.table_name, idx1.index_name