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

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

Related

Oracle: What is the data type of ORA_ROWSCN?

What is the data type of ORA_ROWSCN? It seems to be NUMBER but I cannot find it specified in the documentation.
declare
myscn ???;
begin
select ora_rowscn into myscn from t where ...;
end;
It is a NUMBER. You can see yourself by creating a simple view and describing it (or selecting from *_tab_columns). Here's a simple sqlfiddle demonstration
create table foo (
col1 number
);
create view vw_foo
as
select col1, ora_rowscn scn
from foo;
select *
from user_tab_cols
where table_name = 'VW_FOO';
If you want more detail than you'd probably ever care about on the format of the SCN (system change number), here is one decent article

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

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.

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"

PL/SQL procedure - too many values

I'm sure this is something simple, but I'm really new to PL/SQL and this has me stuck.
I've written a simple stored procedure to return a few values about a customer. Right off the bat, the %rowtype's are not coming up as reserved keywords but the compiler isn't flagging those as errors.
It is, however, ignoring the entire SQL statement flagging the line FROM demo_customers as too many values. Even if I try reducing it to only select one column it still gives me the same error.
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT customer_id, cust_first_name, cust_last_name, cust_email
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you want to select into a %ROWTYPE record, you'll want to do a SELECT * rather than selecting individual columns
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT *
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you select 4 columns explicitly, Oracle expects you to have 4 variables to select those values into.

plsql cursor via straight sql

Im looking at the following two ways of retrieving a value and storing it later via an insert statement. i.e. via Pl/SQL cursors or via direct SQL.
Is there any advantage to either approach? Or is there a more efficient approach?
Approach 1
Cursor system_date
Is
select sysdate from dual;
system_date_rec system_date%type;
Open system_Date;
Fetch system_date into system_date_rec;
Insert into table(dateValue)
values(system_date_rec.date);
Approach 2
dateString varchar(20);
Select sysdate into dateString from dual;
Insert into table(dateValue)
values(dateString);
How about approach 3:
Insert into table(dateValue)
values(sysdate);
or assuming you did actually need to do a select to get the data:
Insert into table(dateValue)
select dateValue from other_table where ...;
Regarding whether an explicit cursor or a SELECT INTO is preferable when one or the other is needed, I would go for the SELECT INTO because it is neater and safer if you expect the query to return exactly one row:
select some_value
into l_var
from other_table
where ...;
if l_var = 'A' then
do_something;
end if;
Now you will get an exception (NO_DATA_FOUND or TOO_MANY_ROWS) if the number of rows returned is not as expected. With the cursor you will just end up with l_var unchanged, or set to the value from the first matching row - which probably means yu've got a bug but don't know it.
Each approach has it's merit but if it's one and only one value you are getting then I'd go with select ... into ... as this is much simpler and will check that you have one and only one value.
Although Tony's approach is possibly preferable to both in the right circumstances.
If you also want to get the value back there is always the RETURNING clause of the insert statement.
my_date_value date;
...
INSERT into table(datevalue)
values (sysdate)
returning sysdate into my_date_value;
I'd agree with #Tony and #MikeyByCrikey that select ... into is generally preferable, not least - in my personal, subjective opinion - because it keeps the select and into together instead of having the select out of sight up in the declare section. Not really an issue if it's simple but you've suggested you're doing several big queries and manipulations, which implies a longish procedure.
Slightly off-topic, but if all the manipulations are to gather data for a single insert at the end, then rather than having lots of separate variables I'd consider declaring a single variable as a row type and updating the columns as appropriate:
declare
l_row my_table%ROWTYPE;
begin
select ... into l_row.column1;
select ... into l_row.column2;
if l_row.column2 = 'A' then
/* do something */
end if;
l_row.column3 := 'somevalue';
fetch ... into l_row.column4;
/* etc */
insert into my_table values l_row;
end;

Resources