PL/SQL Case WHEN with two constants followed by THEN expression - oracle

is it possible for somehow having a CASE with an OR option like below in PL/SQL ?
CASE selector
WHEN 'value1' OR 'value2' THEN S1;
WHEN 'value3' OR 'value4' THEN S2;
WHEN 'value3' THEN S3;
...
ELSE Sn; -- default case
END CASE;

I'd rather use IN:
SQL> declare
2 selector varchar2(20);
3 result varchar2(20);
4 begin
5 selector := '10';
6
7 result := case when selector in ('10', '20') then 's1'
8 when selector in ('30', '40') then 's2'
9 else 'sn'
10 end;
11 dbms_output.put_line('Result = ' || result);
12 end;
13 /
Result = s1
PL/SQL procedure successfully completed.
SQL>
You can use OR, though:
SQL> declare
2 selector varchar2(20);
3 result varchar2(20);
4 begin
5 selector := '10';
6
7 result := case when selector = '10' or selector = '20' then 's1'
8 when selector = '30' or selector = '40' then 's2'
9 else 'sn'
10 end;
11 dbms_output.put_line('Result = ' || result);
12 end;
13 /
Result = s1
PL/SQL procedure successfully completed.
SQL>

Related

How can i display a string adding a hyphen each word on PL/SQL

How can I display a string, separating each letter by a dash with a for loop?
For example i want to display:
h-e-l-l-o-w-o-r-l-d
I tried with the substr function but I can't get it out
If it must be PL/SQL and FOR loop, then you could
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(20) := 'helloworld';
3 retval varchar2(50);
4 begin
5 for i in 1 .. length(l_str) loop
6 retval := retval || substr(l_str, i, 1) ||'-';
7 end loop;
8 retval := rtrim(retval, '-');
9 dbms_output.put_line(retval);
10 end;
11 /
h-e-l-l-o-w-o-r-l-d
PL/SQL procedure successfully completed.
SQL>
Otherwise, consider e.g.
SQL> select rtrim(regexp_replace('helloworld', '(.)', '\1-'), '-') result from dual;
RESULT
-------------------
h-e-l-l-o-w-o-r-l-d
SQL>
or
SQL> select listagg(substr('helloworld', level, 1), '-') within group (order by level) result
2 from dual
3 connect by level <= length('helloworld');
RESULT
--------------------------------------------------------------------------------
h-e-l-l-o-w-o-r-l-d
SQL>
You can just output each successive character and, after the first, output a hyphen between them:
DECLARE
string VARCHAR2(20) := 'helloworld';
BEGIN
DBMS_OUTPUT.PUT(SUBSTR(string, 1, 1));
FOR i IN 2 .. LENGTH(string)
LOOP
DBMS_OUTPUT.PUT('-');
DBMS_OUTPUT.PUT(SUBSTR(string, i, 1));
END LOOP;
DBMS_OUTPUT.NEW_LINE();
END;
/
Which outputs:
h-e-l-l-o-w-o-r-l-d
If you want to do it without loops then you can use:
DECLARE
string VARCHAR2(20) := 'helloworld';
BEGIN
DBMS_OUTPUT.PUT_LINE( SUBSTR(REGEXP_REPLACE(string, '(.)', '-\1'), 2) );
END;
/
You should not use LTRIM (or RTRIM) to remove the hyphens as, if the input string has leading (or trailing) hyphens then these would be removed from the output and that would be erroneous.
For example:
DECLARE
string VARCHAR2(20) := '--helloworld--';
BEGIN
DBMS_OUTPUT.PUT_LINE('Correct:');
DBMS_OUTPUT.PUT_LINE(SUBSTR(REGEXP_REPLACE(string, '(.)', '-\1'), 2));
DBMS_OUTPUT.PUT_LINE('Incorrect:');
DBMS_OUTPUT.PUT_LINE(LTRIM(REGEXP_REPLACE(string, '(.)', '-\1'), '-'));
END;
/
Outputs:
Correct:
----h-e-l-l-o-w-o-r-l-d----
Incorrect:
h-e-l-l-o-w-o-r-l-d----
db<>fiddle here

Can not assign value to an IN OUT parameter

procedure check_startDate_is_grower_than_last_foo(startDate IN OUT DATE) as
last_foo_tsp DATE;
begin
select max(version_tsp) into last_foo_tsp from foo;
if startDate <= last_foo_tsp then
if trunc(startDate) = trunc(last_foo_tsp) then
startDate := (last_foo_tsp + 1/86400); -- +1 seg
else
raise_application_error(-20001, 'Blabla');
end if;
end if;
end;
I get an error:
PLS-00363: expression 'STARTDATE' cannot be used as an assignment target
What I'm doing wrong?
I don't know what you did wrong, because - it works for me.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select * from foo;
VERSION_TSP
-------------------
15.02.2022 00:00:00
SQL> create or replace
2 procedure p_check (startDate IN OUT DATE) as
3 last_foo_tsp DATE;
4 begin
5 select max(version_tsp) into last_foo_tsp from foo;
6 if startDate <= last_foo_tsp then
7 if trunc(startDate) = trunc(last_foo_tsp) then
8 startDate := (last_foo_tsp + 1/86400); -- +1 seg
9 else
10 raise_application_error(-20001, 'Blabla');
11 end if;
12 end if;
13 end;
14 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> declare
2 l_datum date := date '2022-02-15';
3 begin
4 p_check (l_datum);
5 dbms_output.put_line('result = ' || l_datum);
6 end;
7 /
result = 15.02.2022 00:00:01
PL/SQL procedure successfully completed.
SQL>

How to check if an oracle database has some PDB's and then print out the data to screen

My end goal is to query a database, check if v$pdb's exists and then if it does, query it. If it does not, move on and do something else. Basically I want a script that works with 11g and later versions too. I'm falling at the first fence really. I simply want this to output to screen. All it outputs though is "v_str".
SET FEEDBACK OFF;
SET SERVEROUTPUT ON;
declare
v_str varchar2(200);
v_str1 varchar2(200);
begin
v_str := 'select dbid, con_id, name into v_str1 from v$pdbs';
v_str1 := q'!begin dbms_output.put_line('v_str'); end;!';
Execute immediate v_str;
begin dbms_output.put_line(v_str1);
end;
/
Can anyone help me to get the output to prompt to screen...? Thanks!
A little conditional compilation should help out here
SQL> declare
2 has_container varchar2(1);
3 in_container varchar2(1);
4 begin
5 $IF DBMS_DB_VERSION.VER_LE_11_2
6 $THEN
7 has_container := 'N';
8 in_container := 'N';
9 $ELSE
10 has_container := case when to_number(sys_context('USERENV','CON_ID')) = 0 then 'N' else 'Y' end;
11 in_container := case when to_number(sys_context('USERENV','CON_ID')) > 1 then 'Y' else 'N' end;
12 $END
13 dbms_output.put_line('has_container='||has_container);
14 dbms_output.put_line('in_container='||in_container);
15
16 end;
17 /
has_container=Y
in_container=N
In this code
has_container = is the database multitenant (Y/N)
is_container = if the database IS multitenant, am I currently in the root or a pluggable
Then if you want a list, you can use a cursor loop
SQL> declare
2 has_container varchar2(1);
3 in_container varchar2(1);
4 begin
5 $IF DBMS_DB_VERSION.VER_LE_11_2
6 $THEN
7 has_container := 'N';
8 in_container := 'N';
9 $ELSE
10 has_container := case when to_number(sys_context('USERENV','CON_ID')) = 0 then 'N' else 'Y' end;
11 in_container := case when to_number(sys_context('USERENV','CON_ID')) > 1 then 'Y' else 'N' end;
12 if has_container = 'Y' and in_container = 'N' then
13 for i in ( select name from v$pdbs )
14 loop
15 dbms_output.put_line(i.name);
16 end loop;
17 end if;
18 $END
19 dbms_output.put_line('has_container='||has_container);
20 dbms_output.put_line('in_container='||in_container);
21
22 end;
23 /
PDB$SEED
PDB1
PDB2
has_container=Y
in_container=N
SQL>

Assign value to array from plsql block

I have the following code in Oracle Forms detail block
BEGIN
v_product_no := :detail_block.product_no;
Go_block('detail_block');
first_record;
--some if condition
WHILE :SYSTEM.last_record != 'TRUE' LOOP
next_record;
if(:detail_block.product_no = v_product_no) then
-- other condtions
end if;
END LOOP;
I would like to store v_product_no into a some kind of collection object so that I could compare with value of :detail_block.product_no.
How can I do this?
Edit 1
product_no will have values such as K1BATTERY, K2BATTERY, ZCATBATEERY etc.
So if K1BATTERY is same as :detail_block.product_no then proceed with next condition
Edit 2
Go_block('detail_block');
v_product_no := :detail_block.product_no;
v_products(v_product_no) := 1;
first_record;
WHILE :SYSTEM.last_record != 'TRUE' LOOP
if(v_products.exists(v_product_no)) then
alert('duplicate');
end if;
END LOOP;
END if;
Edit 3
Go_block('detail_block');
v_product_no := :detail_block.product_no;
v_products(v_product_no) := 1;
first_record;
-- condition
WHILE :SYSTEM.last_record = 'FALSE' LOOP
next_record;
v_product_no := :detail_block.product_no;
if(v_products.exists(v_product_no)) then
alert('duplicate');
else
v_products(v_product_no) := 1;
end if;
END LOOP;
END if;
Use pl/sql associative array to store already processed values.
declare
type t_processed is table of number(1) index by varchar2(100);
v_product_no varchar2(100); --hold the current value
v_products t_processed; --hold all processed values as keys
begin
v_product_no := :detail_block.product_no;
v_products(v_product_no) := 1; --create entry (v_product_no, 1)
...
--later in while
v_product_no := :detail_block.product_no;
if(v_products.exists(v_product_no)) then --entry exists
-- other conditions
else
v_products(v_product_no) := 1; --create entry (v_product_no, 1)
end if;
...
I think you are having Varying IN list of values in v_product_no variable.
You could do it in following way,
Test# 1
SQL> var product_no VARCHAR2(1000)
SQL> exec :product_no := 'K1BATTERY'
PL/SQL procedure successfully completed.
SQL>
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 v_product_no VARCHAR2(1000);
3 BEGIN
4 v_product_no := 'K1BATTERY, K2BATTERY, ZCATBATEERY';
5 IF :product_no IN (trim(regexp_substr(v_product_no, '[^,]+'))) THEN
6 DBMS_OUTPUT.PUT_LINE('FOUND A MATCH');
7 ELSE
8 DBMS_OUTPUT.PUT_LINE('NO MATCH FOUND');
9 END IF;
10 END;
11 /
FOUND A MATCH
PL/SQL procedure successfully completed.
Test# 2
SQL> var product_no VARCHAR2(1000)
SQL> exec :product_no := 'ABCD'
PL/SQL procedure successfully completed.
SQL>
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 v_product_no VARCHAR2(1000);
3 BEGIN
4 v_product_no := 'K1BATTERY, K2BATTERY, ZCATBATEERY';
5 IF :product_no IN (trim(regexp_substr(v_product_no, '[^,]+'))) THEN
6 DBMS_OUTPUT.PUT_LINE('FOUND A MATCH');
7 ELSE
8 DBMS_OUTPUT.PUT_LINE('NO MATCH FOUND');
9 END IF;
10 END;
11 /
NO MATCH FOUND
PL/SQL procedure successfully completed.
SQL>

Getting index of element in PL/SQL collection

Is there a built-in function to determine the (first) index of an element in a PL/SQL collection?
Something like
DECLARE
TYPE t_test IS TABLE OF VARCHAR2(1);
v_test t_test;
BEGIN
v_test := NEW t_test('A', 'B', 'A');
dbms_output.put_line( 'A: ' || get_index( v_test, 'A' ) );
dbms_output.put_line( 'B: ' || get_index( v_test, 'B' ) );
dbms_output.put_line( 'C: ' || get_index( v_test, 'C' ) );
END;
A: 1
B: 2
C:
I can use Associative Arrays, Nested Tables or Varrays, whatever necessary. If the same element exists more than once, then the index of the first occurrence is sufficient.
Otherwise I'd have to do something like
CREATE FUNCTION get_index ( in_test IN t_test, in_value IN VARCHAR2 )
RETURN PLS_INTEGER
AS
i PLS_INTEGER;
BEGIN
i := in_test.FIRST;
WHILE( i IS NOT NULL ) LOOP
IF( in_test(i) = in_value ) THEN
RETURN i;
END IF;
i := in_test.NEXT(i);
END LOOP;
RETURN NULL;
END get_index;
Not sure, if this really helps, or if you think it is more elegant:
create type t_test as table of varchar2(1);
/
DECLARE
--TYPE t_test IS TABLE OF VARCHAR2(1);
v_test t_test;
function get_index(q in t_test, c in varchar2) return number is
ind number;
begin
select min(rn) into ind from (
select column_value cv, rownum rn
from table(q)
)
where cv = c;
return ind;
end get_index;
BEGIN
v_test := NEW t_test('A', 'B', 'A');
dbms_output.put_line( 'A: ' || get_index( v_test, 'A' ) );
dbms_output.put_line( 'B: ' || get_index( v_test, 'B' ) );
dbms_output.put_line( 'C: ' || get_index( v_test, 'C' ) );
END;
/
show errors
drop type t_test;
I don't think there is a built-in function that searches a collection. However, if you know you will need to search a collection a lot, you could build an index. Adding element to the collection will be a bit more expensive, but looking for an element will be an O(1) operation (instead of O(n) for a brute force search). For example, you could use something like this:
SQL> DECLARE
2 TYPE t_test IS TABLE OF VARCHAR2(1);
3 TYPE t_test_r IS TABLE OF NUMBER INDEX BY VARCHAR2(1);
4
5 v_test t_test;
6 v_test_r t_test_r;
7
8 FUNCTION get_index(p_test_r t_test_r,
9 p_element VARCHAR2) RETURN NUMBER IS
10 BEGIN
11 RETURN p_test_r(p_element);
12 EXCEPTION
13 WHEN no_data_found THEN
14 RETURN NULL;
15 END get_index;
16
17 PROCEDURE add_element(p_test IN OUT t_test,
18 p_test_r IN OUT t_test_r,
19 p_element VARCHAR2) IS
20 BEGIN
21 p_test.extend;
22 p_test(p_test.count) := p_element;
23 p_test_r(p_element) := least(p_test.count,
24 nvl(get_index(p_test_r, p_element),
25 p_test.count));
26 END add_element;
27 BEGIN
28 v_test := NEW t_test();
29 add_element(v_test, v_test_r, 'A');
30 add_element(v_test, v_test_r, 'B');
31 add_element(v_test, v_test_r, 'A');
32 dbms_output.put_line('A: ' || get_index(v_test_r, 'A'));
33 dbms_output.put_line('B: ' || get_index(v_test_r, 'B'));
34 dbms_output.put_line('C: ' || get_index(v_test_r, 'C'));
35 END;
36 /
A: 1
B: 2
C:
PL/SQL procedure successfully completed
You could also define a record that contains both arrays and all functions/procedures to interact with arrays would use this record type.
When in doubt, consult the documentation ;) (here)
DECLARE
TYPE aa_type_int IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
aa_int aa_type_int;
PROCEDURE print_first_and_last IS
BEGIN
DBMS_OUTPUT.PUT_LINE('FIRST = ' || aa_int.FIRST);
DBMS_OUTPUT.PUT_LINE('LAST = ' || aa_int.LAST);
END print_first_and_last;
BEGIN
aa_int(1) := 3;
aa_int(2) := 6;
aa_int(3) := 9;
aa_int(4) := 12;
DBMS_OUTPUT.PUT_LINE('Before deletions:');
print_first_and_last;
aa_int.DELETE(1);
aa_int.DELETE(4);
DBMS_OUTPUT.PUT_LINE('After deletions:');
print_first_and_last;
END;
/
Result:
Before deletions:
FIRST = 1
LAST = 4
After deletions:
FIRST = 2
LAST = 3

Resources