I have a table with several columns some of them are:
FILTER_COLUMN VARCHAR2
L_FILTER_VALUEN NUMBER
L_FILTER_VALUEA VARCHAR
L_FILTER_LIST_ID NUMBER
I'm making a procedure to append a string (later to be used on a query)
but there is a catch:
If all of those 4 columns are empty, that's OK. However, if FILTER_COLUMN is not empty, I must guarantee that at least one of the other three is NOT empty.
Since I'm a bit of a nooblet in PL/SQL I've decided to do it "the wrong way":
IF FILTER_COLUMN IS NOT NULL
IF L_FILTER_VALUEN IS NULL
IF L_FILTER_VALUEA IS NULL
IF L_FILTER_LIST_ID IS NULL
RETURN FALSE;
ELSE
SQL_STMT := L_FILTER_LIST_ID
END IF;
ELSE
....
(and so on)
Is there a cleaner way to it and append all the not null columns?
From the top of my head, you have 2 basic options:
Use AND:
IF filter_column IS NOT NULL THEN
IF l_filter_valuen IS NULL AND l_filter_valuea IS NULL AND l_filter_list_id IS NULL THEN
RETURN FALSE;
ELSE
sql_stmt := l_filter_list_id;
END IF;
ELSE
....
(and so on)
Use the COALESCE function which will return the first not null variable. If it returns NULL it means all of the variables are NULL:
IF filter_column IS NOT NULL THEN
IF COALESCE(l_filter_valuen, l_filter_valuea, l_filter_list_id) IS NULL THEN
RETURN FALSE;
ELSE
sql_stmt := l_filter_list_id;
END IF;
ELSE
....
(and so on)
Related
CREATE PROCEDURE book_check(book_Id varchar(64))
begin
declare book_available varchar(64);
select book_id into book_available
from book_copies
where No_of_Copies >0 and book_id=book_Id;
if(book_Id in book_available ) then
select concat ("Book available");
else
select concat ("Book not available");
end if;
end
//
what can i write in place of 'in' . I know the syntax i wrong .
It's easy - try something like this:
create or replace function book_check(book_id varchar) return varchar as
begin
for r in (select 1 from book_copies where no_of_copies > 0 and book_id = book_check.book_id) loop
return 'Book available';
end loop;
return 'Book not available';
end book_check;
/
It's unclear to me what you are trying to do. I assume you want to find out if a book is available or not and return that information to the caller of the function.
Your declaration of the procedure header and the variables is wrong.
Procedure or function parameters are not defined with a length for the datatype.
Inside a procedure or function you don't need declare
you can't have a select statement without putting the result somewhere. * Assigning a constant value to a variable is done using :=
If you want to return information to the caller, use a function, not a procedure
You should not give variables or parameters the same name as a column. A common naming convention in the Oracle world is to give parameters the prefix p_ and local variables the prefix l_ but anything that avoids a name clash between column names and variables is OK - just be consistent.
CREATE function book_check(p_book_id varchar)
return varchar
as
l_count integer;
l_result varchar(20);
begin
select count(*)
into l_count
from book_copies
where No_of_Copies > 0
and book_id = p_book_id;
if l_count > 0 then
l_result := 'Book available';
else
l_result := "Book not available";
end if;
return result;
end;
/
You should really take the time and read the PL/SQL Language reference. All the above is explained there.
can someone help with that.
I need to check against a column say, "COL_A" that if its not null and is numeric (10,0).
Need to check and find the rows that are not null and are numeric (10,0).
Please help
You should create a function to validate the number and use a NVL function to Null validation:
Code To function:
CREATE OR REPLACE FUNCTION isNumeric(strVal IN VARCHAR2)
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
numericVal NUMBER;
BEGIN
numericVal := to_number(strVal);
RETURN 'True';
EXCEPTION
WHEN value_error THEN
RETURN 'False';
END isNumeric;
Code to call the function:
SELECT DECODE(isNumeric(NVL("COL_A", 'null')) = 'True', "COL_A", 0) AS "valor"
FROM myTable
The answer above is probably the best response. If you wanted to use SQL instead of PL/SQL, the following should work:
SELECT col_a
FROM mytable
WHERE regexp_like( col_a, '^[[:digit:]]*$' );
If you are using PL/SQL and need to process records in a loop:
DECLARE
CURSOR mycur IS
SELECT col_a
FROM mytable
WHERE regexp_like(col_a, '^[[:digit:]]*$');
BEGIN
FOR myrec IN mycur LOOP
DBMS_OUTPUT.PUT_LINE(col_a);
END LOOP;
END;
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;
I have a function which would return a record with type my_table%ROWTYPE, and in the caller, I could check if the returned record is null, but PL/SQL complains the if-statement that
PLS-00306: wrong number or types of arguments in call to 'IS NOT NULL'
Here is my code:
v_record my_table%ROWTYPE;
v_row_id my_table.row_id%TYPE := 123456;
begin
v_record := myfunction(v_row_id)
if (v_record is not null) then
-- do something
end if;
end;
function myfunction(p_row_id in my_table.row_id%TYPE) return my_table%ROWTYPE is
v_record_out my_table%ROWTYPE := null;
begin
select * into v_record_out from my_table
where row_id = p_row_id;
return v_record_out;
end myfunction;
Thanks.
As far as I know, it's not possible. Checking the PRIMARY KEY or a NOT NULL column should be sufficient though.
You can check for v_record.row_id IS NULL.
Your function would throw a NO_DATA_FOUND exception though, when no record is found.
You can't test for the non-existence of this variable so there are two ways to go about it. Check for the existence of a single element. I don't like this as it means if anything changes your code no longer works. Instead why not just raise an exception when there's no data there:
I realise that the others in the exception is highly naughty but it'll only really catch my table disappearing when it shouldn't and nothing else.
v_record my_table%ROWTYPE;
v_row_id my_table.row_id%TYPE := 123456;
begin
v_record := myfunction(v_row_id)
exception when others then
-- do something
end;
function myfunction(p_row_id in my_table.row_id%TYPE) return my_table%ROWTYPE is
v_record_out my_table%ROWTYPE := null;
cursor c_record_out(c_row_id char) is
select *
from my_table
where row_id = p_row_id;
begin
open c_record_out(p_row_id);
fetch c_record_out into v_record_out;
if c_record_out%NOTFOUND then
raise_application_error(-20001,'no data);
end if;
close c_record_out;
return v_record_out;
end myfunction;