My Question is "How can we return multiple records from pl/sql stored procedure without taking OUT parameter".I got this doubt because if we are using cursors or refcursor in out parameter it may degrade performance.So what is the solution??
As OldProgrammer wrote, i think the performance of a cursor wouldn't be you problem. But here a Solution anyway:
You can return custom types like Table of number. If it's only a list of numbers you could return a table of numbers. If you Want to return rows from a table you could return table of 'tablename'%ROWTYPE. But i guess you want to create some custom types.
CREATE OR REPLACE TYPE PUWB_INT.MyOrderType AS OBJECT
(
OrderId NUMBER,
OrderName VARCHAR2 (255)
)
/
CREATE OR REPLACE TYPE PUWB_INT.MyOrderListType AS TABLE OF MYORDERtype
/
Now we can use them similar to a return myNumberVariable;
Let's build a function (procedures don't have return values):
CREATE OR REPLACE FUNCTION PUWB_INT.MyFunction (SomeInput VARCHAR2)
RETURN MyOrderListType
IS
myOrderList MyOrderListType := MyOrderListType ();
BEGIN
FOR o IN (SELECT 1 AS Id, 'One' AS Name FROM DUAL
UNION ALL
SELECT 2 AS Id, 'Two' AS Name FROM DUAL)
LOOP
myOrderList.EXTEND ();
myOrderList (myOrderList.COUNT) := MyOrderType (o.Id, o.Name || '(' || SomeInput || ')');
END LOOP;
RETURN myOrderList;
END MyFunction;
/
Now we can call the function and get a table of our custom-type:
DECLARE
myOrderList MyOrderListType;
myOrder MyOrderType;
BEGIN
myOrderList := MyFunction ('test');
FOR o IN myOrderList.FIRST .. myOrderList.LAST
LOOP
myOrder := myOrderList (o);
DBMS_OUTPUT.put_line ('Id: ' || myOrder.OrderId || ', Name: ' || myOrder.OrderName);
END LOOP;
END;
Be aware, that the calling schema, has to know the type.
I'm new to PL/SQL and trying to create an object with a reference to a table row:
CREATE OR REPLACE TYPE my_object AS OBJECT(
table_row my_table%ROWTYPE
);
The compiler gives me the error message PLS-00329.
Ok, now I know I'm not allowed to reference a table like this. But is there a workaround?
You have to list all of the fields in the object type declaration. You mentioned you're concerned about that being error-prone. You can semi-automate the object creation via a PL/SQL block (or procedure that you pass the table and object name into).
Say you have a table defined as;
CREATE TABLE my_table (
ID NUMBER(38),
col_1 DATE,
col_2 NUMBER(3, 2),
col_3 VARCHAR2(10),
col_4 CLOB
);
You can extract the column names and data types from the data dictionary and build up your object creation statement:
DECLARE
v_stmt VARCHAR2(4000);
BEGIN
v_stmt := 'CREATE OR REPLACE TYPE my_object AS OBJECT(';
FOR r IN (
SELECT column_name,
CAST (data_type || CASE
WHEN data_type IN ('VARCHAR', 'VARCHAR2', 'NVARCHAR2', 'RAW', 'CHAR')
THEN '(' || data_length || ')'
WHEN data_type IN ('NUMBER')
AND (data_precision IS NOT NULL OR data_scale IS NOT NULL)
THEN '(' || data_precision || CASE
WHEN data_scale > 0 THEN ',' || data_scale
END || ')'
END AS VARCHAR2(30)) AS data_type,
CASE WHEN column_id < MAX(column_id) OVER () THEN ',' END AS comma
FROM user_tab_columns
WHERE table_name = 'MY_TABLE'
ORDER BY column_id
)
LOOP
v_stmt := v_stmt || r.column_name || ' ' || r.data_type || r.comma;
END LOOP;
v_stmt := v_stmt || ')';
dbms_output.put_line(v_stmt); -- just for debugging
EXECUTE IMMEDIATE v_stmt;
END;
/
You can extract and use the nullable flag too if you want, but it may not be useful here. (This is adapted from a describe replacement. There may be some data types that aren't handled properly but it covers the common ones; if you have columns with UDTs or other oddities it will need to be extended to include those properly.)
The dbms_output just shows the generated statement to make it easier to debug:
CREATE OR REPLACE TYPE my_object AS OBJECT(ID NUMBER(38),COL_1 DATE,COL_2 NUMBER(3,2),COL_3 VARCHAR2(10),COL_4 CLOB)
PL/SQL procedure successfully completed.
As that statement was also executed the object was created:
desc my_object;
Name Null? Type
----- ----- ------------
ID NUMBER(38)
COL_1 DATE
COL_2 NUMBER(3,2)
COL_3 VARCHAR2(10)
COL_4 CLOB
If you want to add functions you can use the generated create statement as a starting point, and edit it to add whatever you need before running it manually instead of using execute immediate.
Convenient as it sounds, I think there is unlikely ever to be any such syntax for types, as types must present a SQL interface - you have to be able to describe the type and see its attribute list, the attributes need to be published in user_type_attrs, you have to be able to create subtypes and object-relational tables and columns from it, use it in a query and see the column projection etc.
When you create a view using select *, the column list is expanded on creation but does not get rebuilt if you later change the table. So even if Oracle did provide a way to do something similar with types, I suspect they would have to follow the same pattern of simply generating the list on creation and then leaving it to you to maintain, to avoid the complication of managing the dependencies and cascade invalidations, especially given the restrictions around type evolution when types themselves have complex dependencies.
I want use separate columns in WHERE clause based on the INPUT received in the stored procedure.
If TYPE_DEFINITION = 'SUP' then use SUPPLIER column
If TYPE_DEFINITION = 'CAT' then use CATEGORY column
I know I can write two separate SELECT's using a CASE statement, but that will be very dumb and redundant. Any cleaner way of doing it?
CREATE OR REPLACE PROCEDURE SG.STORED_PROCEDURE (
TYPE_DEFINITION IN VARCHAR2,
VALUE IN VARCHAR2,
STORELIST IN VARCHAR2)
AS
BEGIN
SELECT O.ORGNUMBER,
S.SKU,
FROM SKU S JOIN ORG O ON S.ORGID = O.ORGID
WHERE
AND O.ORGNUMBER IN (STORELIST)
AND (CASE TYPE_DEFINITION
WHEN 'SUP' THEN S.SUPPLIER = VALUE
ELSE S.CATEGORY = VALUE
END);
END;
/
Your code is very close. The CASE THEN must return an expression, not a condition. But the CASE can be used as part of a condition, just move the = VALUE
to the outside.
Change this:
AND (CASE TYPE_DEFINITION
WHEN 'SUP' THEN S.SUPPLIER = VALUE
ELSE S.CATEGORY = VALUE
END);
To This:
AND VALUE = (CASE TYPE_DEFINITION
WHEN 'SUP' THEN S.SUPPLIER
ELSE S.CATEGORY
END);
Your code makes sense. This limitation is probably a result of Oracle not fully supporting Booleans.
UPDATE
If you run into performance problems you may want to use dynamic SQL or ensure that the static SQL is correctly using FILTER operations. When Oracle builds an execution plan it is able to use bind variables like constants, and choose a different plan based on the input. As Ben pointed out, these FILTER operations don't always work perfectly, sometimes it may help if you use simplified conditions like this:
(TYPE_DEFINITION = 'SUP' AND S.SUPPLIER = VALUE)
OR
((TYPE_DEFINITION <> 'SUP' OR TYPE_DEFINITION IS NULL) AND S.CATEGORY = VALUE)
You need to use dynamic sql in your procedure.
Something like this:
CREATE OR REPLACE PROCEDURE SG.STORE_PROC (
TYPE_DEFINITION IN VARCHAR2,
VALUE IN VARCHAR2,
STORELIST IN VARCHAR2)
AS
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
v_stmt_str VARCHAR2(200);
v_orgnumber VARCHAR2(200);
v_sku VARCHAR2(200);
BEGIN
v_stmt_str := 'SELECT O.ORGNUMBER, S.SKU,FROM SKU S JOIN ORG O ON S.ORGID = O.ORGID ';
if type_definition = 'SUP' then
v_stmt_str := v_stmt_str || 'WHERE s.supplier = :v';
else
v_stmt_str := v_stmt_str || 'WHERE s.category = :v';
end if;
-- Open cursor & specify bind variable in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING value;
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO v_orgnumber, v_sku;
-- you can do something here with your values
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
/
I have 2 tables with same set Column Name (52+ coulmns) . I need to write an Oracle function to compare whether any records get changed between these columns. EMP_ID is the primary Key
I'm trying to use the below function, but it is giving me incorrect result,
I'm calling the funcaiton like this:
get_data_change (emp_id, 'DEPT_NAME');
get_data_change (emp_id, 'PHONE_NUMBER');
Function I have created:
CREATE OR REPLACE function get_data_change (
in_emp_id varchar2, in_Column_Name varchar2)
return char is
v_data_changed char;
begin
select eid, in_Column_Name
into v_table1_eid, v_table1_Column_Value
from table 1
where eid=in_emp_id;
Select eid, in_Column_Name
into v_table2_eid, v_table2_Column_Value
from table 2
where eid = in_emp_id;
if ( v_table2_Column_Value != v_table1_Column_Value)
then
v_data_changed := 'Y'
else
v_data_changed :='N'
endif
return v_data_changed
end
end get_data_change;
in_Column_Name is a string variable to which you are assigning a literal string value such as 'DEPT_NAME'.
Therefore, your queries are interpreting this as a literal string value and returning the same thing into v_table1_Column_Value.
To do what you expect you need to use Dynamic SQL, something like:
EXECUTE IMMEDIATE 'select eid, ' || in_Column_Name
|| ' from table1 where eid=:P_emp_id'
into v_table1_eid, v_table1_Column_Value
using in_emp_id;
You need to be aware of the possibility of SQL Injection here - i.e. the value of in_Column_Name cannot be supplied by end-users.
I'm trying to check if a value from a column in an oracle (10g) query is a number in order to compare it. Something like:
select case when ( is_number(myTable.id) and (myTable.id >0) )
then 'Is a number greater than 0'
else 'it is not a number'
end as valuetype
from table myTable
Any ideas on how to check that?
One additional idea, mentioned here is to use a regular expression to check:
SELECT foo
FROM bar
WHERE REGEXP_LIKE (foo,'^[[:digit:]]+$');
The nice part is you do not need a separate PL/SQL function. The potentially problematic part is that a regular expression may not be the most efficient method for a large number of rows.
Assuming that the ID column in myTable is not declared as a NUMBER (which seems like an odd choice and likely to be problematic), you can write a function that tries to convert the (presumably VARCHAR2) ID to a number, catches the exception, and returns a 'Y' or an 'N'. Something like
CREATE OR REPLACE FUNCTION is_number( p_str IN VARCHAR2 )
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
l_num NUMBER;
BEGIN
l_num := to_number( p_str );
RETURN 'Y';
EXCEPTION
WHEN value_error THEN
RETURN 'N';
END is_number;
You can then embed that call in a query, i.e.
SELECT (CASE WHEN is_number( myTable.id ) = 'Y' AND myTable.id > 0
THEN 'Number > 0'
ELSE 'Something else'
END) some_alias
FROM myTable
Note that although PL/SQL has a boolean data type, SQL does not. So while you can declare a function that returns a boolean, you cannot use such a function in a SQL query.
Saish's answer using REGEXP_LIKE is the right idea but does not support floating numbers. This one will ...
Return values that are numeric
SELECT foo
FROM bar
WHERE REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$');
Return values not numeric
SELECT foo
FROM bar
WHERE NOT REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$');
You can test your regular expressions themselves till your heart is content at http://regexpal.com/ (but make sure you select the checkbox match at line breaks for this one).
This is a potential duplicate of Finding rows that don't contain numeric data in Oracle. Also see: How can I determine if a string is numeric in SQL?.
Here's a solution based on Michael Durrant's that works for integers.
SELECT foo
FROM bar
WHERE DECODE(TRIM(TRANSLATE(your_number,'0123456789',' ')), NULL, 'number','contains char') = 'number'
Adrian Carneiro posted a solution that works for decimals and others. However, as Justin Cave pointed out, this will incorrectly classify strings like '123.45.23.234' or '131+234'.
SELECT foo
FROM bar
WHERE DECODE(TRIM(TRANSLATE(your_number,'+-.0123456789',' ')), NULL, 'number','contains char') = 'number'
If you need a solution without PL/SQL or REGEXP_LIKE, this may help.
You can use the regular expression function 'regexp_like' in ORACLE (10g)as below:
select case
when regexp_like(myTable.id, '[[:digit:]]') then
case
when myTable.id > 0 then
'Is a number greater than 0'
else
'Is a number less than or equal to 0'
end else 'it is not a number' end as valuetype
from table myTable
I'm against using when others so I would use (returning an "boolean integer" due to SQL not suppporting booleans)
create or replace function is_number(param in varchar2) return integer
is
ret number;
begin
ret := to_number(param);
return 1; --true
exception
when invalid_number then return 0;
end;
In the SQL call you would use something like
select case when ( is_number(myTable.id)=1 and (myTable.id >'0') )
then 'Is a number greater than 0'
else 'it is not a number or is not greater than 0'
end as valuetype
from table myTable
This is my query to find all those that are NOT number :
Select myVarcharField
From myTable
where not REGEXP_LIKE(myVarcharField, '^(-)?\d+(\.\d+)?$', '')
and not REGEXP_LIKE(myVarcharField, '^(-)?\d+(\,\d+)?$', '');
In my field I've . and , decimal numbers sadly so had to take that into account, else you only need one of the restriction.
How is the column defined? If its a varchar field, then its not a number (or stored as one). Oracle may be able to do the conversion for you (eg, select * from someTable where charField = 0), but it will only return rows where the conversion holds true and is possible. This is also far from ideal situation performance wise.
So, if you want to do number comparisons and treat this column as a number, perhaps it should be defined as a number?
That said, here's what you might do:
create or replace function myToNumber(i_val in varchar2) return number is
v_num number;
begin
begin
select to_number(i_val) into v_num from dual;
exception
when invalid_number then
return null;
end;
return v_num;
end;
You might also include the other parameters that the regular to_number has. Use as so:
select * from someTable where myToNumber(someCharField) > 0;
It won't return any rows that Oracle sees as an invalid number.
Cheers.
CREATE OR REPLACE FUNCTION is_number(N IN VARCHAR2) RETURN NUMBER IS
BEGIN
RETURN CASE regexp_like(N,'^[\+\-]?[0-9]*\.?[0-9]+$') WHEN TRUE THEN 1 ELSE 0 END;
END is_number;
Please note that it won't consider 45e4 as a number, But you can always change regex to accomplish the opposite.
#JustinCave - The "when value_error" replacement for "when others" is a nice refinement to your approach above. This slight additional tweak, while conceptually the same, removes the requirement for the definition of and consequent memory allocation to your l_num variable:
function validNumber(vSomeValue IN varchar2)
return varchar2 DETERMINISTIC PARALLEL_ENABLE
is
begin
return case when abs(vSomeValue) >= 0 then 'T' end;
exception
when value_error then
return 'F';
end;
Just a note also to anyone preferring to emulate Oracle number format logic using the "riskier" REGEXP approach, please don't forget to consider NLS_NUMERIC_CHARACTERS and NLS_TERRITORY.
well, you could create the is_number function to call so your code works.
create or replace function is_number(param varchar2) return boolean
as
ret number;
begin
ret := to_number(param);
return true;
exception
when others then return false;
end;
EDIT: Please defer to Justin's answer. Forgot that little detail for a pure SQL call....
You can use this example
SELECT NVL((SELECT 1 FROM DUAL WHERE REGEXP_LIKE (:VALOR,'^[[:digit:]]+$')),0) FROM DUAL;
Function for mobile number of length 10 digits and starting from 9,8,7 using regexp
create or replace FUNCTION VALIDATE_MOBILE_NUMBER
(
"MOBILE_NUMBER" IN varchar2
)
RETURN varchar2
IS
v_result varchar2(10);
BEGIN
CASE
WHEN length(MOBILE_NUMBER) = 10
AND MOBILE_NUMBER IS NOT NULL
AND REGEXP_LIKE(MOBILE_NUMBER, '^[0-9]+$')
AND MOBILE_NUMBER Like '9%' OR MOBILE_NUMBER Like '8%' OR MOBILE_NUMBER Like '7%'
then
v_result := 'valid';
RETURN v_result;
else
v_result := 'invalid';
RETURN v_result;
end case;
END;
Note that regexp or function approaches are several times slower than plain sql condition.
So some heuristic workarounds with limited applicability make sence for huge scans.
There is a solution for cases when you know for sure that non-numeric values would contain some alphabetic letters:
select case when upper(dummy)=lower(dummy) then '~numeric' else '~alpabetic' end from dual
And if you know some letter would be always present in non-numeric cases:
select case when instr(dummy, 'X')>0 then '~alpabetic' else '~numeric' end from dual
When numeric cases would always contain zero:
select case when instr(dummy, '0')=0 then '~alpabetic' else '~numeric' end from dual
if condition is null then it is number
IF(rtrim(P_COD_LEGACY, '0123456789') IS NULL) THEN
return 1;
ELSE
return 0;
END IF;
Here's a simple method which :
does not rely on TRIM
does not rely on REGEXP
allows to specify decimal and/or thousands separators ("." and "," in my example)
works very nicely on Oracle versions as ancient as 8i (personally tested on 8.1.7.4.0; yes, you read that right)
SELECT
TEST_TABLE.*,
CASE WHEN
TRANSLATE(TEST_TABLE.TEST_COLUMN, 'a.,0123456789', 'a') IS NULL
THEN 'Y'
ELSE 'N'
END
AS IS_NUMERIC
FROM
(
-- DUMMY TEST TABLE
(SELECT '1' AS TEST_COLUMN FROM DUAL) UNION
(SELECT '1,000.00' AS TEST_COLUMN FROM DUAL) UNION
(SELECT 'xyz1' AS TEST_COLUMN FROM DUAL) UNION
(SELECT 'xyz 123' AS TEST_COLUMN FROM DUAL) UNION
(SELECT '.,' AS TEST_COLUMN FROM DUAL)
) TEST_TABLE
Result:
TEST_COLUMN IS_NUMERIC
----------- ----------
., Y
1 Y
1,000.00 Y
xyz 123 N
xyz1 N
5 rows selected.
Granted this might not be the most powerful method of all; for example ".," is falsely identified as a numeric. However it is quite simple and fast and it might very well do the job, depending on the actual data values that need to be processed.
For integers, we can simplify the Translate operation as follows :
TRANSLATE(TEST_TABLE.TEST_COLUMN, 'a0123456789', 'a') IS NULL
How it works
From the above, note the Translate function's syntax is TRANSLATE(string, from_string, to_string). Now the Translate function cannot accept NULL as the to_string argument.
So by specifying 'a0123456789' as the from_string and 'a' as the to_string, two things happen:
character a is left alone;
numbers 0 to 9 are replaced with nothing since no replacement is specified for them in the to_string.
In effect the numbers are discarded. If the result of that operation is NULL it means it was purely numbers to begin with.