Convert LONG into VARCHAR2 or some text datatype - sql

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

Related

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

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.

Why can't you use " ' " on a numeric value in a table?

For an example, I wanted to search all employees in department 20. Why do I have to list is as the first one versus the second one. Does ' only apply to data that is in words?
Select * from emp
Where dept = 20
versus
Select * from emp
Where dept = '20'
It is about datatype.
if dept column's datatype is NUMBER, you'd use where dept = 20
if it is a string (one of CHAR datatypes; VARCHAR2 being the most common), then you'd enclose value into single quotes: where dept = '20'
Oracle will try (and sometimes succeed) to implicitly convert one datatype to another. So, if dept column was a varchar2 and you used where dept = 20, it might work. But, if there were dept values that aren't numerics but contain letters (e.g. departments whose values were 2A or CT), then you might expect problems.
For example:
SQL> create table temp (dept varchar2(2));
Table created.
Insert works because of implicit conversion of 20 into a string:
SQL> insert into temp values (20);
1 row created.
Select also works:
SQL> select * from temp where dept = 20;
DE
--
20
This is, though, how you should be using it:
SQL> select * from temp where dept = '20';
DE
--
20
But, if not all values in a table are numerics (although stored into varchar2 datatype column) ...
SQL> insert into temp values ('2A');
1 row created.
... then implicit conversion won't work any more:
SQL> select * from temp where dept = 20;
ERROR:
ORA-01722: invalid number
no rows selected
Enclosing the value into single quotes is the way to go:
SQL> select * from temp where dept = '20';
DE
--
20
SQL>

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 text search in BLOB field

We have created a BLOB type column to store 6000 to 7000 ASCII characters of payload.
I am inserting the payload using following code.
PreparedStatement stmt=conn.prepareStatement(insertQuery);
String record="My ASCII Payload";
stmt.setBytes(1, record.getBytes());
stmt.execute();
When i run below query i can see the length of chars in the output.
select dbms_lob.getLength(MY_PAYLOAD)
from RAW_DATA;
6085
I wanted to perform text search in this BLOB column , i tried following options nothing works. What is the correct way to perform text search in a BLOB column ?
SELECT * FROM RAW_DATA t
WHERE dbms_lob.instr(t.MY_PAYLOAD,utl_raw.cast_to_raw(‘1234’))>0
select *
from RAW_DATA
where dbms_lob.instr (MY_PAYLOAD, -- the blob
utl_raw.cast_to_raw ('1234'),
1, -- where to start. i.e. offset
1 -- Which occurrance i.e. 1=first
) > 0
You can create text indexes on blobs as long as they contain text (naturally)
SQL> create table t ( x int, b blob);
Table created.
SQL>
SQL> insert into t values ( 1, utl_raw.cast_to_Raw('Hello there'));
1 row created.
SQL> insert into t values ( 2, utl_raw.cast_to_Raw('Goodbye tomorrow'));
1 row created.
SQL>
SQL> create index t_ix on t ( b )
2 indextype is ctxsys.context;
Index created.
SQL>
SQL> select x
2 from t where contains(b,'Hello') > 0;
X
----------
1
SQL>
SQL> select utl_raw.cast_to_varchar2(b)
2 from t
3 where contains(b,'Hello') > 0;
UTL_RAW.CAST_TO_VARCHAR2(B)
---------------------------------------------------------------------------------
Hello there

How to pass string data type to number datatype in plsql

I have procedure like this...
declare
v_psg varchar2(10);
id_no number;
begin
select value into v_psg from settings_am where key = 'PSG';
select id into id_no from product where to_char(psg) in (v_psg);
end;`
The value returned from select value into v_psg from settings_am where key = 'PSG'; would be
'1','2','3'
when i run this procedure i am returned with ora error - ORA-01403.
please advise how i should pass the v_psg value to psg column of product table?
EDIT - Tried with test case suggested
If you got ORA-01403, you were kind of lucky. It is the NO_DATA_FOUND error, which means that one (probably the first) query didn't return anything.
Those two statements could be combined into
select id
from product
where to_char(psg) in (select value
from settings_am
where key = 'PSG'
);
Why would you select value first, and then use it in another query? Besides, it just wouldn't work. v_psg is declared as VARCHAR2 variable. The way you described it, it contains the following string: '1','2','3', as if this is what you have:
SQL> create table settings_am (key varchar2(10),
2 value varchar2(20)); --> note size here
Table created.
SQL> insert into settings_am (key, value)
2 values ('PSG', q'['1','2','3']');
1 row created.
SQL> select * From settings_am;
KEY VALUE
---------- --------------------
PSG '1','2','3'
SQL>
As you can see, I enlarged the value column size, although variable you declared says 10. Why? Because of
SQL> select length(value) from settings_am where key = 'PSG';
LENGTH(VALUE)
-------------
11
i.e. you can't put something that is long 11 into something that accepts length 10.
Or, if your data actually contains 3 rows for the PSG key, are those values already enclosed into single quotes? If so, that's strange; people usually don't do that. Anyway, suppose that you managed to get string '1,2,3' (which is what I presume you actually have) into a VARCHAR2 variable, then you have to split it into rows in order to be able to use it in the IN clause:
SQL> create table product (id number, psg varchar2(10));
Table created.
SQL> insert into product (id, psg) values (100, '1');
1 row created.
SQL> insert into product (id, psg) values (200, '2');
1 row created.
SQL>
Query is then (where lines #3 - 5 represent splitting a string into rows):
SQL> select p.id
2 from product p
3 where p.psg in (select regexp_substr('&&v_psg', '[^,]+', 1, level)
4 from dual
5 connect by level <= regexp_count('&&v_psg', ',') + 1
6 );
Enter value for v_psg: 1,2,3
ID
----------
100
200
So, wouldn't it be simpler to use
SQL> select id
2 from product
3 where to_char(psg) in (select value
4 from settings_am
5 where key = 'PSG'
6 );
ID
----------
100
200
SQL>
Note that both options also show why your query is wrong: you can't put two values (rows) into a variable declared as id_no number; as you'd get TOO_MANY_ROWS error.
Finally, what is it that you'd want to do? What problem are you trying to solve? Apparently, except for special cases (only one row for each value) your query can't work. If you could provide test case (create table & insert into sample data), as well as expected output, it would be easier to help you.