How to print a field type on oracle without a `SELECT` - oracle

Simple question:
How to print a field's type?
DESC TABLE_FOO.FIELD_FOO;
Results printing the whole table description.
How to print a specific field details with a command, i.e, without a SELECT?

There is no built-in way to do this, at least in any of the clients I've used. As mentioned in comments, describe is a client wrapper around a data dictionary query, and each client can implement it differently - SQL*Plus and SQL Developer seem to be slightly different, and no client is actually required to support this command at all.
Just for fun, if you really wanted to you could create a procedure to figure out and format the data type the same as desc, something like:
create or replace procedure col_data_type (p_table_name varchar2,
p_column_name varchar2)
as
l_data_type varchar2(30);
begin
select data_type
|| case when data_type = 'VARCHAR2' then '(' || data_length || ')' end
|| case when data_type = 'NUMBER' and data_precision is not null then
'(' || data_precision
|| case when data_scale is not null and data_scale > 0 then
',' || data_scale end
|| ')' end
into l_data_type
from user_tab_columns
where table_name = p_table_name
and column_name = p_column_name;
dbms_output.put_line(l_data_type);
end col_data_type;
/
Perhaps with more special formatting for other data types, but those are the obvious ones. You can then call that with execute. With a dummy table:
create table t42(i integer, n1 number, n2 number(10), n3 number(10,5),
v varchar2(10), c clob)
Then:
set serveroutput on
exec col_data_type('T42','I');
NUMBER
exec col_data_type('T42','N1');
NUMBER
exec col_data_type('T42','N2');
NUMBER(10)
exec col_data_type('T42','N3');
NUMBER(10,5)
exec col_data_type('T42','V');
VARCHAR2(10)
exec col_data_type('T42','C');
CLOB
Not entirely sure how useful that might be, or why you want to be able to do this at all. Also notice that it requires the client to be retrieving and displaying dbms_output buffer. You could make it a function instead, which puts you back to using a select, albeit a shorter one...

I don't believe it is possible to accomplish this without using a SELECT statement.
DESC OWNER.TABLE_NAME; is basically just running a query like this anyways (although not exactly the same, that depends on your client):
SELECT *
FROM ALL_TAB_COLS
WHERE OWNER = &theOwner
AND TABLE_NAME = &theTable;
If you want to only return a single column, you can do this:
SELECT *
FROM ALL_TAB_COLS
WHERE OWNER = &theOwner
AND TABLE_NAME = &theTable
AND COLUMN_NAME = &theColumn;
As #AlexPoole suggests, you could work around this by writing your own custom PROCEDURE or FUNCTION to return exactly what you need, but I believe the answer to the question "is there a built in command other than SELECT that does exactly what you need" is no, there is not.

I don't think you can do this without using a SELECT. You may be able to come up with some way to route the output of DESC to a file and then parse the file out to get what you want, but honestly - SELECT is going to be much easier.
Relational databases store the description of what they store in the database, where said description can be obtained in the same manner as any other information in the database, i.e. by using a SELECT to read it. The actual tables which store this are somewhat difficult to interpret, but happily Oracle has taken pity on us poor users and provided views which present this info in an easy-to-read manner. To get the type of a field you want to do a select one of the *_TAB_COLS views, where * is either USER, ALL, or DBA. The particular columns of interest are likely going to be COLUMN_NAME, DATA_TYPE, and DATA_LENGTH.
Share and enjoy.

Related

PL/SQL reusable dynamic sql program for same type of task but different table and column

Thank you for reply guys. I kind of solved my problem.
I used to try to update data with ref cursor in dynamic SQL using "where current of" but I now know that won't work.
Then I tried to use %rowtype to store both 'id' and 'clob' in one variable for future updating but turns out weak ref cursor can't use that type binding either.
After that I tried to use record as return of an ref cursor and that doesn't work on weak cursor either.
On the end, I created another cursor to retrieve 'id' separately along with cursor to retrieve 'clob' on the same time then update table with that id.
I'm now working on a Oracle data cleaning task and have a requirement like below:
There are 38 tables(maybe more in the future) and every table has one or multiple column which type is Clob. I need to find different keyword in those columns and according to a logic return binary label of the column and store it in a new column.
For example, there is a table 'myTable1' which has 2 Clob columns 'clob1' and 'clob2'. I'd like to find keyword 'sky' from those columns and store '0'(if not found) or '1'(if found) in two new columns 'clob1Sky','clob2Sky'.
I know if I could write it on a static way which will provide higher efficiency but I have to modify it for those very similar tasks every time. I want save some time on this so I'm trying to write it in a reusable way and not binding to certain table.
But I met some problem when writing the program. My program is like below:
create or replace PACKAGE body LABELTARGETKEYWORD
as
/**
#param varcher tableName: the name of table I want to work on
#param varchar colName: the name of clob column
#param varchar targetWord: the word I want to find in the column
#param varchar newColName: the name of new column which store label of clob
*/
PROCEDURE mainProc(tableName varchar, colName varchar,targetWord varchar,newColName varchar2)
as
type c_RecordCur is ref cursor;
c_sRecordCur c_recordCur;
/*other variables*/
begin
/*(1) check whether column of newColName exist
(2) if not, alter add table of newColName
(3) open cursor for retrieving clob
(4) loop cursor
(5) update set the value in newColName accroding to func labelword return
(6) close cursor and commit*/
end mainProc;
function labelWord(sRecord VARCHAR2,targetWord varchar2) return boolean...
function ifColExist(tableName varchar2,newColName varchar2) return boolean...
END LABELTARGETKEYWORD;
Most DML and DDL are written in dynamic sql way.
The problem is when I write the (5) part, I notice 'Where current of' clause can not be used in a ref cursor or dynamic sql statement. So I have to change the plan.
I tried to use a record(rowid,label) to store result and alter the table later.(the table only be used by two people in my group, so there won't be problem of lock and data changes). But I find because I'm trying to use dynamic sql so actually I have to define ref cursor with return of certain %rowtype and basically all other variables, %type in dynamic sql statement. Which makes me feel my method has something wrong.
My question are:
If there a way to define %type in dynamic sql? Binding type to variable in dynamic SQL?
Could anybody give me a hint how to write that (5) part in dynamic SQL?
Should not I design my program like that?
Is it not the way how to use dynamic SQL or PLSQL?
I'm very new to PL/SQL. Thank you very much.
According to Tom Kyte's advice, to do it in one statement if it can be done in one statement, I'd try to use a single UPDATE statement first:
CREATE TABLE mytable1 (id NUMBER, clob1 CLOB,
clob2 CLOB, clob1sky NUMBER, clob2sky NUMBER )
LOB(clob1, clob2) STORE AS SECUREFILE (ENABLE STORAGE IN ROW);
INSERT INTO mytable1(id, clob1, clob2)
SELECT object_id, object_name, object_type FROM all_objects
WHERE rownum <= 10000;
CREATE OR REPLACE
PROCEDURE mainProc(tableName VARCHAR2, colName VARCHAR2, targetWord VARCHAR2, newColName VARCHAR2)
IS
stmt VARCHAR2(30000);
BEGIN
stmt := 'UPDATE '||tableName||' SET '||newColName||'=1 '||
'WHERE DBMS_LOB.INSTR('||colName||','''||targetWord||''')>1';
dbms_output.put_line(stmt);
EXECUTE IMMEDIATE stmt;
END mainProc;
/
So, calling it with mainProc('MYTABLE1', 'CLOB1', 'TAB', 'CLOB1SKY'); fires the statement
UPDATE MYTABLE1 SET CLOB1SKY=1 WHERE DBMS_LOB.INSTR(CLOB1,'TAB')>1
which seems to do the trick:
SELECT * FROM mytable1 WHERE clob1sky=1;
id clob1 clob2 clob1sky clob2skiy
33 I_TAB1 INDEX 1
88 NTAB$ TABLE 1
89 I_NTAB1 INDEX 1
90 I_NTAB2 INDEX 1
...
I am not sure with your question-
If this job is suppose to run on daily or hourly basis ,running query through it will be very costly. One thing you can do - put all your clob data in a file and save it in your server(i guess it must be linux). then you can create a shell script and schedule a job to run gerp command and fetch your required value and "if found then update your table".
I think you should approaches problem another way:
1. Find all columns that you need:
CURSOR k_clobs
select table_name, column_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
Or 2 cursor(you can build you query if you have more than 1 CLOB per table:
CURSOR k_clobs_table
select DISTINCT table_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
CURSOR k_clobs_columns(table_namee varchar(255)) is
select column_name from dba_tab_cols where data_type in ('CLOB','NCLOB') and table_name = table_namee;
Now you are 100% that column you are checking is clob, so you don't have to worry about data type ;)
I'm not sure what you want achieve, but i hope it may help you.

100 strings in IN operator, oracle pl/sql

I am passing 100 table_names in the IN operator as strings, but I am getting numeric overflow error due to too many operands.
Is there a way where I can use something else besides IN ?
set serveroutput on
DECLARE
...
BEGIN
FOR r IN
(
SELECT table_name, column_name
FROM all_tab_columns
WHERE table_name IN (...100 strings)
)
AND data_type = 'NUMBER'
ORDER BY table_name, column_id
)
LOOP
execute immediate 'SELECT COUNT("' || r.column_name || '")
,COUNT(nvl2("' || r.column_name || '", NULL, 1))
FROM "' || r.table_name || '"'
INTO not_null_count, null_count;
DBMS_OUTPUT.PUT_LINE(..)
Note: For variables I am using PLS_Integer.
The suggested action for ORA-01426 is "reduce the operands". This doesn't mean reduce the number of operands. It means you're trying to put too large a number into a variable. So shrink the number, or enlarge the variable.
You say:
"for variables I am using PLS_Integer"
So, if you have a large table, and by large I mean more than 2,147,483,647 rows, you will get a numeric overflow. Because PLS_INTEGER is a 32-bit data type.
If this is your scenario then you need to declare your variables of data type INTEGER instead (or NUMBER(38,0)).
As #BobJarvis points out, PLS_INTEGER is optimized for hardware arithmetic. So the general advice would be to use it for counting type operations. However, your case simply requires a variable to hold the output of a SQL count() operation, so I don't think there will be any difference in efficiency.
I believe the limit on 'IN' clause is 1000 strings and not 100 strings. To debug:
a.) Try running your implicit cursor query in SQL.
b.) If it works fine then run the query in execute immediate after substituting the column name.
Also , try increasing the size of your not_null_count and null_count variables.
Hope it Helps
Vishad
some other possible solutions
use a temp table - populate it with the table names to filter join to it.
create a global array type
create type table_of_varchar2 is table of varchar2(30)
populate the array and filter using table_name member of arr_tables_list
Is there a way where I can use something else besides IN ?
Consider using a cursor instead.

PL/SQL rewrite concatenated query with 'IN' clause

Currently I have in my pl/sql code following statements:
-- vList looks like '1,2,3,4'
vStatement := 'SELECT NAME FROM T_USER WHERE ID IN ( ' || vList || ' ) ';
Execute Immediate vStatement BULK COLLECT INTO tNames;
I think that concatenating of query if bad practice, so I want to make this query without using stings. What is the way to rewrite this ?
P.S. maybe people here can point out why concatenation of queries is bad, because i don't have enough reasons to prove that this style is bad.
my guess is that you took some steps previously to get vList id's into a delimited string (you don't say how vList was populated ). Why not keep as one query?
begin
...
select name
bulk collect into tNames
from t_user
where id in (select id from some_table where ...);
...
Context switching when run many times can be painful, but to me the worst part is that you are blindly accepting parameter input to be a list of numbers, when it could be anything really. It could (innocently) be '1,2,X', and you'll get a runtime error "invalid number". Or worse, it could be a SQL injection attack. Its bad practice in general (dynamic sql does have its place), but definitely NOT how you're using it.
Try something like this:
create or replace type t_num_tab as table of number;
create or replace procedure test_proc(i_list in t_num_tab) as
type t_name_tab is table of varchar2(100);
l_names t_name_tab;
begin
-- get names
select name
bulk collect into l_names
from user_table
where id in (select * from table(i_list));
-- do something with l_names
dbms_output.put_line('Name count: ' || l_names.count);
end;
You can create an object type if you need something more complicated than a list of numbers.
It's not just that concatenation is slow. It's that dynamic queries in plsql are REALLY slow. Here's a good writeup of both the how and why to do this:
Ask Tom: How can I do a variable "in list"

I'm trying to search a long column with like, but Oracle complains

Just a note, we're using 11g, and I have no choice. I'm looking through all_constraints and trying to check the search_condition column, something like this:
select * from all_constraints
where table_name = UPPER('STVTERM') AND
constraint_type = 'C' AND
CAST(search_condition AS VARCHAR2(100)) NOT LIKE '%IS NOT NULL';
I was hoping to chuck this into a quick and dirty proc that spits out a Grails domain. And the constraints are the only missing piece. Is there an easy way to exclude those constraints which are just "not null" other than a where/like that I'm missing? I've tried the obvious, Oracle also balks at casting the long to varchar and then checking. Since I'm likely to want to do other manipulations of this column, some of the solutions where I create a function that does a kludgy PL-SQL conversion, checks that, and returns a "match/not-match" result aren't much help either.
Anyone have any ideas?
This is what I used when trying to solve the same problem, for posterity's sake:
-- Create the decoder function
create function funk_decode( p_cons_name in varchar2 ) return varchar2
authid current_user
is
l_search_condition user_constraints.search_condition%type;
begin
select search_condition into l_search_condition
from user_constraints
where constraint_name = p_cons_name;
return l_search_condition;
end;
/
-- Then use it in your select
SELECT constraint_name, constraint_type, status, search_condition FROM USER_CONSTRAINTS where funk_decode(constraint_name) like '%SEARCH_TERM%';
--- Then clean up
drop function funk_decode;
Of course, replace SEARCH_TERM with whatever you're looking for. It's based on some code I found here: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:839298816582
There is a function to convert LONG to varchar2:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#i1025399

SQL to reset a number of colums to default

I want to write a SQL that resets all colums in a table back to default, except a couple of colums like the primary key.
I just want to name the colums NOT to update, and reset everything else. There are quite many colums in the table, and I dont whant to write:
update my_table set column1 = DEFAULT, column2 = DEFAULT, ... where ...
for all colums, since there are quite many.
Any ideas? I am using Oracle
I don't think there is a procedure to do what you want, but if your only problem is the heavy burden of writing the SQL, you can automate that with ALL_TAB_COLUMNS view. You can improve the idea for your needs:
select
'update ' || TABLE_NAME ||
'set ' ||
COLUMN_NAME || ' = DEFAULT'
from ALL_TAB_COLUMNS
where
table_name = <YOUR_TABLE>

Resources