Below is my code to create types and tables. It's all correct:
CREATE TYPE shape_typ AS OBJECT (
l INTEGER, -- length
w INTEGER, -- width
h INTEGER, -- height
MEMBER FUNCTION area RETURN INTEGER,
MEMBER FUNCTION volume RETURN INTEGER,
MEMBER PROCEDURE display (SELF IN OUT NOCOPY shape_typ) );
CREATE TYPE BODY shape_typ AS
MEMBER FUNCTION volume RETURN INTEGER IS
BEGIN
RETURN l * w * h;
-- same as previous line RETURN SELF.l * SELF.w * SELF.h;
END;
MEMBER FUNCTION area RETURN INTEGER IS
BEGIN -- not necessary to include SELF in following
RETURN 2 * (l * w + l * h + w * h);
END;
MEMBER PROCEDURE display (SELF IN OUT NOCOPY shape_typ) IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Length: ' || l || ' - ' ||
'Width: ' || w || ' - ' ||
'Height: ' || h );
DBMS_OUTPUT.PUT_LINE('Volume: ' || volume || ' - ' ||
'Area: ' || area );
END;
END;
CREATE TABLE shapes (
shape shape_typ,
create_date DATE );
INSERT INTO shapes VALUES (
shape_typ (3,3,3), '17-MAR-2008' );
INSERT INTO shapes VALUES (
shape_typ (1,8,2), '17-FEB-2008' );
INSERT INTO shapes VALUES (
shape_typ (1,1,1), '27-MAR-2008' );
SELECT s.shape.l,s.shape.w,s.shape.h,s.shape.area() FROM shapes s;
DECLARE
shap shapes%rowtype;
BEGIN -- PL/SQL block for selecting-displaying a student
SELECT * INTO shap
FROM shapes s
WHERE s.shape.l=1 and s.shape.w=1 and s.shape.h=1;
shap.shape.display;
END;
However, when I try to execute the procedure in a the following statement, it gives me an error message.
SELECT s.shape.l,s.shape.w,s.shape.h,s.shape.display() FROM shapes s;
The error message is that:
ORA-06553: PLS-222: no function with name 'DISPLAY' exists in this scope
06553. 00000 - "PLS-%s: %s"
*Cause:
*Action:
Error at Line: 49 Column: 38
I am wondering whether it is because I cannot use the procedure in a select statement?
No, you can't use procedures in SQL-Statements.
The procedure DISPLAY can only be used in a PL/SQL context.
See Oracle Documentation : Coding PL/SQL Subprograms and Packages
Related
I am not that fluent in using regular expressions. I want that user should not provide value for a column with '<' or '>' in it.
Regards,
Sachin
Use group () and or | operators for example to validate user input you sould do:
set serveroutput on;
declare
l_value varchar2(32767) := '<p>test and data</p>';
begin
if regexp_like(l_value,'(<|>)','i') then
dbms_output.put_line('invalid');
else
dbms_output.put_line('valid');
end if;
end;
/
Good luck.
Use [ and ] to define a set of characters to match, e.g. [abc], [a-z], [a-z0-9_]
select string
, case
when regexp_like(string,'[<>]') then 'Invalid'
else 'Valid'
end as test
from
( select '<p>text</p>' as string from dual union all
select 'text' from dual );
STRING TEST
---------------- -------
<p>text</p> Invalid
text Valid
Or in PL/SQL:
declare
teststring varchar2(100) := '<p>test and data</p>';
regex varchar2(100) := '[<>]';
begin
dbms_output.put('"'||teststring||'"');
dbms_output.put(case when regexp_like(teststring,regex) then ' matches ' else ' does not match ' end );
dbms_output.put(regex);
dbms_output.new_line();
end;
/
"<p>test and data</p>" matches [<>]
PL/SQL procedure successfully completed
As a check constraint:
create table mytable
( col varchar2(20)
constraint mytable_ltgt_chk check ( not regexp_like(col,'[<>]') )
);
Test:
insert into mytable (col) values ('kitten > puppy');
rejected with:
ORA-02290: check constraint (MYRIAD_OWNER_82.MYTABLE_LTGT_CHK) violated
If you wanted to exclude square brackets as well, that would be:
constraint mytable_symbol_chk check ( not regexp_like(col,'[][<>]') );
Or without any regex:
constraint mytable_symbol_chk check ( col = translate(col,'[]<>','.') )
https://regex101.com/r/oQJztM/2/tests
I open a cursor, and do a fetch bulk collect. The problem comes when I do a FORALL to INSERT all records inside a table, because this table is a variable. This is the code:
TYPE DST_TYPE IS TABLE OF DST_TABLE%ROWTYPE;
TYPE CURSOR IS REF CURSOR;
v_srcTable := 'my_src_table'; /* Dynamic Variable */
v_dstTable := 'my_dst_table'; /* Dynamic Variable */
v_partition := 'my_partition'; /* Dynamic Variable */
v_dstRecords DST_TYPE;
v_Cursor CURSOR;
OPEN v_Cursor FOR
'SELECT /*+ PARALLEL (TT 2) */ field1,field2,field3,...,field50 FROM' || v_srcTable || ' TT ';
LOOP
FETCH v_Cursor BULK COLLECT INTO v_dstRecords LIMIT 1000;
FORALL i in 1 .. v_dstRecords.COUNT
INSERT /*+ APPEND */ INTO v_dstTable PARTITION(v_partition) MT VALUES v_dstRecords(i);
COMMIT;
EXIT WHEN v_Cursor%NOTFOUND;
END LOOP;
CLOSE v_Cursor;
This fails because v_dstTable is not a valid table (valid table is "my_dst_table"). So, if I want to parse the table name, I need to change that for "EXECUTE IMMEDIATE INSERT..:" syntax. The problem is, if I use this syntax, I cannot pass a rowtype in VALUES
v_SQL := 'INSERT /*+ APPEND */ INTO ' || v_dstTable || ' PARTITION(' || v_partition || ') MT VALUES :1';
FORALL i in 1 .. v_dstRecords.COUNT
EXECUTE IMMEDIATE v_SQL USING v_dstRecords(i);
The only alternative I see is to pass all the parameters of the rowtype as bind variables, but there are 50 parameters...
v_SQL := 'INSERT /*+ APPEND */ INTO ' || v_dstTable || ' PARTITION(' || v_partition || ') MT VALUES (:1,:2,:3,...,:50)';
FORALL i in 1 .. v_dstRecords.COUNT
EXECUTE IMMEDIATE v_SQL USING v_dstRecords(i).field1, v_dstRecords(i).field2, ..., v_dstRecords(i).field50;
Is there any way to do this without using syntax with bind variables? If you have only 1 parameter is ok, but if you have 50...
Thanks
I have the following code:
create or replace type t_gib force as object (
fld number,
member function f return number
) not final;
/
declare
type tab_bin is table of pls_integer index by pls_integer;
type t_rec is record (
f1 t_gib
,f2 tab_bin
,f3 tab_bin
,f4 t_gib
);
--
l_rec t_rec;
begin
l_rec.f2(1) := 5;
l_rec.f2(2) := 7;
l_rec.f3(1) := 9;
--
dbms_output.put_line(l_rec.f2.count || '-' || l_rec.f3.count);
dbms_output.put_line(l_rec.f2(l_rec.f2.last) || '-' || l_rec.f3(l_rec.f3.last));
end;
/
This does not compile. But it compiles if I remove the not final.
Additionally it also compiles if I just re-arrange the record thus
type t_rec is record (
f2 tab_bin
,f3 tab_bin
,f4 t_gib
,f1 t_gib
);
Does anybody have an explanation for this behaviour?
Most likely your have some other business going on there as your example works fine (11gR2). Please see the full working example below. I guess you should also drop force and not final keywords unless you know what you're doing, but they should have no effect in this example.
Test type
create or replace type foo_t force is object (
m_f number
,member function f return number
) not final;
/
show errors
create or replace type body foo_t is
member function f return number is
begin
return m_f;
end;
end;
/
show errors
Example use
declare
type int_int_hash_t is table of pls_integer index by pls_integer;
type bar_t is record (
b1 foo_t
,b2 int_int_hash_t
,b3 int_int_hash_t
,b4 foo_t
);
v_bar bar_t;
begin
v_bar.b1 := foo_t(1);
v_bar.b2(2) := 2;
v_bar.b3(3) := 3;
v_bar.b4 := foo_t(4);
dbms_output.put_line(v_bar.b1.f);
dbms_output.put_line(v_bar.b2(2));
dbms_output.put_line(v_bar.b3(3));
dbms_output.put_line(v_bar.b4.f);
end;
/
Results
SQL> #so62.sql
No errors.
1
2
3
4
PL/SQL procedure successfully completed.
SQL>
I have a list of comma-separated strings (from a user input) and I'd like to use this list as a parameter in a pl/sql stored function in a nested sql block using a "not in where clause".
I can't find an elegant way to make it work...
That's what I'm thinking of:
CREATE TABLE example ( somevalue VARCHAR(36) NOT NULL);
--
INSERT INTO example VALUES ('value1');
INSERT INTO example VALUES ('value2');
INSERT INTO example VALUES ('value3');
--
SELECT * FROM example;
--
CREATE OR REPLACE
FUNCTION resultmaker(
ignoreList IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2(4000);
BEGIN
result := 'Here is my calculated result, using ignorelist=' || ignoreList || ':' || CHR(10);
FOR rec IN
(SELECT DISTINCT somevalue
FROM example
WHERE somevalue NOT IN resultmaker.ignoreList -- here's my issue, the NOT IN clause using the parameter value
)
LOOP
result := result || 'not in ignorelist: ' || rec.somevalue || CHR(10);
END LOOP;
result := result || '.' || CHR(10);
--
RETURN result;
END resultmaker;
/
--
-- simulate function call with user input 'value2, value3'
SELECT resultmaker('value2, value3') FROM dual; -- doesn't work
--
DROP TABLE example;
DROP FUNCTION resultmaker;
Just pass the parameter like '"value2","value3"' and have your statement replace the double quote with single quotes like REPLACE(#Param1,'"','''').
Call to function: SELECT * FROM Function1('"value2","value3"')
Inside function: NOT IN REPLACE(#Param1,'"','''')
In every case you should parse that input. As there is no built-in string tokenizer in PL/SQL (at least I couldn't find it) You may want to look into these options,
http://blog.tanelpoder.com/2007/06/20/my-version-of-sql-string-to-table-tokenizer/
Does PL/SQL have an equivalent StringTokenizer to Java's?
After you parsed the string, you may create a new string like:
not_in_statement varchar2(1000);
CURSOR c1 IS select token from tokenized_strings_table;
BEGIN
not_in_statement := '('
FOR rec IN c1 LOOP
not_in_statement := not_in_statement || '''||rec.token||'''||','
END LOOP
not_in_statement := not_in_statement||')'
END
SELECT DISTINCT somevalue
FROM example
WHERE somevalue NOT IN not_in_statement
You may need to make it dynamic SQL, I did not have time to try.
Here's my solution using dynamic sql for my original question above:
CREATE TABLE example ( somevalue VARCHAR(36) NOT NULL);
--
INSERT INTO example VALUES ('value1');
INSERT INTO example VALUES ('value2');
INSERT INTO example VALUES ('value3');
--
SELECT * FROM example;
--
CREATE OR REPLACE
FUNCTION resultmaker(
ignoreList IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2(4000);
example_cursor sys_refcursor;
rec example.somevalue%type;
BEGIN
result := 'Here is my calculated result, using ignorelist=' || ignoreList || ':' || CHR(10);
OPEN example_cursor FOR ( 'SELECT DISTINCT somevalue FROM example WHERE somevalue NOT IN (' || ignoreList || ')' );
FETCH example_cursor INTO rec;
WHILE example_cursor%found
LOOP
result := result || 'not in ignorelist: ' || rec || CHR(10);
FETCH example_cursor INTO rec;
END LOOP;
CLOSE example_cursor;
result := result || '.' || CHR(10);
--
RETURN result;
END resultmaker;
/
--
-- simulate function call with user input 'value2', 'value3'
SELECT resultmaker('''value2'', ''value3''') FROM dual;
--
DROP TABLE example;
DROP FUNCTION resultmaker;
The classic and probably correct solution would be to use PL/SQL table passing it as prameter...
There are some good solutions at asktom.oracle.com regarding taking a string of values and dynamically creating an IN clause for them:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:210612357425
For example I want to make my own Boolean type and call it Bool. How do I do that?
Or a type for traffic lights, i.e. that has only Red, Yellow, Green in it (and null of course).
I don't think that solution, provided by A.B.Cade is totally correct. Let's assume procedure like this:
procedure TestEnum(enum_in lights);
What is the value of enum_in? red? yellow? green?
I propose another solution. Here is package example
CREATE OR REPLACE PACKAGE pkg_test_enum IS
SUBTYPE TLight IS BINARY_INTEGER RANGE 0..2;
Red CONSTANT TLight := 0;
Yellow CONSTANT TLight := 1;
Green CONSTANT TLight := 2;
--get sting name for my "enum" type
FUNCTION GetLightValueName(enum_in TLight) RETURN VARCHAR2;
PROCEDURE EnumTest(enum_in TLight);
END pkg_test_enum;
CREATE OR REPLACE PACKAGE BODY pkg_test_enum IS
FUNCTION GetLightValueName(enum_in TLight)
RETURN VARCHAR2
IS
ResultValue VARCHAR2(6);
BEGIN
CASE enum_in
WHEN Red THEN ResultValue := 'Red';
WHEN Green THEN ResultValue := 'Green';
WHEN Yellow THEN ResultValue := 'Yellow';
ELSE ResultValue := '';
END CASE;
RETURN ResultValue;
END GetLightValueName;
PROCEDURE EnumTest(enum_in TLight)
IS
BEGIN
--do stuff
NULL;
END EnumTest;
END pkg_test_enum;
I can now use TLight in different packages. I can now test enum_in against predefined values or null.
Here is usage example
begin
pkg_test_enum.EnumTest(pkg_test_enum.Red);
end;
Besides, you can make this type not nullable.
SUBTYPE TLight IS BINARY_INTEGER RANGE 0..2 NOT NULL;
This blog describes a way to do it using constant values
In addition to the constants, the blog defines a subtype for valid colors.
SQL> declare
2 RED constant number(1):=1;
3 GREEN constant number(1):=2;
4 BLUE constant number(1):=3;
5 YELLOW constant number(1):=4;
6 --
7 VIOLET constant number(1):=7;
8 --
9 subtype colors is binary_integer range 1..4;
10 --
11 pv_var colors;
12 --
13 function test_a (pv_var1 in colors) return colors
14 is
15 begin
16 if(pv_var1 = YELLOW) then
17 return(BLUE);
18 else
19 return(RED);
20 end if;
21 end;
22 --
The closest think I could think of is:
create or replace type lights as object
(
red varchar2(8),
yellow varchar2(8),
green varchar2(8),
constructor function lights return self as result
)
and the body:
create or replace type body lights is
constructor function lights return self as result is
begin
self.red = 'red';
self.yellow = 'yellow';
self.green = 'green';
return;
end;
end;
Then in the code you can use it:
declare
l lights := new lights;
begin
dbms_output.put_line(l.red);
end;
I have previously used the same approach as #mydogtom and #klas-lindbäck. I found this when I was trying to refresh my memory. However, the object approach suggested by #a-b-cade got me thinking. I agree with the problems described by #mydogtom (what is the value?) but it got me thinking is using an object was possible.
What I came up with was an approach that used an object with a single member property for the value of the enum and static functions for each possible value. I couldn't see how to combine with a subtype to get a real restriction on the value field, not to formally make it not-null. However, we can validate it in the constructor. The functional downside, compared to a "proper" enum (e.g. in Java) is that we can't stop someone directly updating the val property to an invalid value. However, as long as people use the constructor and the set_value function, it's safe. I'm not sure the overhead (both run-time in terms of creating an object and in terms of maintaining the objects, etc.) is worth it, so I'll probably keep using the approach described by #mydogtom but I'm not sure.
You could also have name as a property and set in in set_value (kind of like #a-b-cade's version) but that adds another property that could be updated directly and so another set of states where the val and name don't match, so I preferred the approach with name being a function.
An example usage of this could be (using my demo_enum type below):
procedure do_stuff(enum in demo_enum) is
begin
if enum.eqals(demo_enum.foo()) then
-- do something
end if;
end do_stuff;
or
procedure do_stuff(enum1 in demo_enum, enum2 in demo_enum) is
begin
if enum1.eqals(enum2) then
-- do something
end if;
end do_stuff;
What I did was define a base class, with as much as possible there: the actual val field, equals function for static values, set_value and to_string fucntions. Also name function, but this just be overridden (couldn't see how to formally make a member function abstract, so the base version just throws an exception). I'm using name also as the way to check the value is valid, in order to reduce the number of places I need to enumerate the possible values
create or replace type enum_base as object(
-- member field to store actual value
val integer,
-- Essentially abstract name function
-- Should be overridden to return name based on value
-- Should throw exception for null or invalid values
member function name return varchar2,
--
-- Used to update the value. Called by constructor
--
member procedure set_value(pvalue in integer),
--
-- Checks the current value is valid
-- Since we can't stop someone updating the val property directly, you can supply invalid values
--
member function isValid return boolean,
--
-- Checks for equality with integer value
-- E.g. with a static function for a possible value: enum_var.equals( my_enum_type.someval() )
--
member function equals(other in integer) return boolean,
--
-- For debugging, prints out name and value (to get just name, use name function)
--
member function to_string return varchar2
) not instantiable not final;
/
create or replace type body enum_base is
member function name return varchar2 is
begin
-- This function must be overriden in child enum classes.
-- Can't figure out how to do an abstract function, so just throw an error
raise invalid_number;
end;
member procedure set_value(pvalue in integer) is
vName varchar2(3);
begin
self.val := pvalue;
-- call name() in order to also validate that value is valid
vName := self.name;
end set_value;
member function isValid return boolean is
vName varchar2(3);
begin
begin
-- call name() in order to also validate that value is valid
vName := self.name;
return true;
exception
when others then
return false;
end;
end isValid;
member function equals(other in integer) return boolean is
begin
return self.val = other;
end equals;
member function to_string return varchar2 is
begin
if self.val is null then
return 'NULL';
end if;
return self.name || ' (' || self.val || ')';
end to_string;
end;
/
In the actual enum class I have to define a constructor (which just calls set_value) and override the name function to return a name for each possible value. I then define a static function for each possible value that returns the integer index of that value. Finally I define an overload of equals that compares to another enum of the same type. If you wanted to attach other properties to each value then you an do so by defining additional functions.
create or replace type demo_enum under enum_base (
-- Note: the name of the parameter in the constructor MUST be the same as the name of the variable.
-- Otherwise a "PLS-00307: too many declarations" error will be thrown when trying to instanciate
-- the object using this constructor
constructor function demo_enum(val in integer) return self as result,
--
-- Override name function from base to give name for each possible value and throw
-- exception for null/invalid values
--
overriding member function name return varchar2,
--
-- Check for equality with another enum object
--
member function equals(other in demo_enum) return boolean,
--
-- Define a function for each possible value
--
static function foo return integer,
static function bar return integer
) instantiable final;
/
create or replace type body demo_enum is
constructor function demo_enum(val in integer) return self as result is
begin
self.set_value(val);
return;
end demo_enum;
overriding member function name return varchar2 is
begin
if self.val is null then
raise invalid_number;
end if;
case self.val
when demo_enum.foo() then
return 'FOO';
when demo_enum.bar() then
return 'BAR';
else
raise case_not_found;
end case;
end;
member function equals(other in demo_enum) return boolean is
begin
return self.val = other.val;
end equals;
static function foo return integer is
begin
return 0;
end foo;
static function bar return integer is
begin
return 1;
end bar;
end;
/
This can be tested. I defined two sets of tests. one was a manual set of tests for this particular enum, also to illustrate usage:
--
-- Manual tests of the various functions in the enum
--
declare
foo demo_enum := demo_enum(demo_enum.foo());
alsoFoo demo_enum := demo_enum(demo_enum.foo());
bar demo_enum := demo_enum(demo_enum.bar());
vName varchar2(100);
procedure assertEquals(a in varchar2, b in varchar2) is
begin
if a <> b then
raise invalid_number;
end if;
end assertEquals;
procedure assertEquals(a in boolean, b in boolean) is
begin
if a <> b then
raise invalid_number;
end if;
end assertEquals;
procedure test(vName in varchar2, enum in demo_enum, expectFoo in boolean) is
begin
dbms_output.put_line('Begin Test of ' || vName);
if enum.equals(demo_enum.foo()) then
dbms_output.put_line(vName || ' is foo');
assertEquals(expectFoo, true);
else
dbms_output.put_line(vName || ' is not foo');
assertEquals(expectFoo, false);
end if;
if enum.equals(demo_enum.bar()) then
dbms_output.put_line(vName || ' is bar');
assertEquals(expectFoo, false);
else
dbms_output.put_line(vName || ' is not bar');
assertEquals(expectFoo, true);
end if;
if enum.equals(foo) then
dbms_output.put_line(vName || '.equals(vFoo)');
assertEquals(expectFoo, true);
else
assertEquals(expectFoo, false);
end if;
if expectFoo then
assertEquals(enum.name, 'FOO');
else
assertEquals(enum.name, 'BAR');
end if;
assertEquals(enum.isValid, true);
case enum.val
when demo_enum.foo() then
dbms_output.put_line(vName || ' matches case foo');
when demo_enum.bar() then
dbms_output.put_line(vName || ' matches case bar');
else
dbms_output.put_line(vName || ' matches no case!!!');
end case;
dbms_output.put_line(vName || ': ' || enum.to_string());
dbms_output.put_line('--------------------------------------------------');
dbms_output.put_line('');
end test;
begin
test('foo', foo, true);
test('bar', bar, false);
test('alsoFoo', alsoFoo, true);
foo.val := -1;
assertEquals(foo.isValid, false);
begin
vName := foo.name;
exception
when case_not_found then
dbms_output.put_line('Correct exception for fetching name when invalid value: ' || sqlerrm);
end;
foo.val := null;
assertEquals(foo.isValid, false);
begin
vName := foo.name;
exception
when invalid_number then
dbms_output.put_line('Correct exception for fetching name when null value: ' || sqlerrm);
end;
end;
The other was slightly more automated, and could be used for any enum that inherits enum_base (as long as it doesn't add other functions - couldn't see a way to find only static functions, if anyone knows let me know). This checks that you haven't defined the same integer value to multiple possible value static functions by mistake:
--
-- generated test that no two values are equal
--
declare
vSql varchar2(4000) := '';
typename constant varchar2(20) := 'demo_enum';
cursor posvals is
select procedure_name
from user_procedures
where object_name = upper(typename)
and procedure_name not in (upper(typename), 'EQUALS', 'NAME');
cursor posvals2 is
select procedure_name
from user_procedures
where object_name = upper(typename)
and procedure_name not in (upper(typename), 'EQUALS', 'NAME');
procedure addline(line in varchar2) is
begin
vSql := vSql || line || chr(10);
end;
begin
addline('declare');
addline(' enum ' || typename || ';');
addline('begin');
for posval in posvals loop
addline(' enum := ' || typename || '(' || typename || '.' || posval.procedure_name || '());');
for otherval in posvals2 loop
addline(' if enum.equals(' || typename || '.' || otherval.procedure_name || '()) then');
if otherval.procedure_name = posval.procedure_name then
addline(' dbms_output.put_line(''' || otherval.procedure_name || ' = ' || posval.procedure_name || ''');');
else
addline(' raise_application_error(-20000, ''' || otherval.procedure_name || ' = ' || posval.procedure_name || ''');');
end if;
addline(' else');
if otherval.procedure_name = posval.procedure_name then
addline(' raise_application_error(-20000, ''' || otherval.procedure_name || ' != ' || posval.procedure_name || ''');');
else
addline(' dbms_output.put_line(''' || otherval.procedure_name || ' != ' || posval.procedure_name || ''');');
end if;
addline(' end if;');
end loop;
addline('');
end loop;
addline('end;');
execute immediate vSql;
end;