Check if string contains certain characters - sql

I'm trying to check if the login being inserted into employee table is given in a certain format, which is: the first letter is strictly given - x, then 5 letters and two numbers => xlogin00.
CREATE OR REPLACE TRIGGER check_login
BEFORE INSERT OR UPDATE OF login ON employee
FOR EACH ROW
DECLARE
xlogin00 employee.login%TYPE;
prefix VARCHAR2(1);
name VARCHAR2(5);
number VARCHAR2(2);
BEGIN
xlogin00 := :new.login;
prefix := substr(xlogin00,1,1);
name := substr(xlogin00,2,5);
number := substr(xlogin00,7,2);
if(LENGTH(xlogin00) != 8) then
Raise_Application_Error (-20203, 'Error');
end if;
if(prefix != 'x') then
Raise_Application_Error (-20204, 'Error');
end if;
if NOT REGEXP_LIKE(name, '[^a-z]') then
Raise_Application_Error (-20204, 'Error');
end if;
if NOT REGEXP_LIKE(number, '[^0-9]') then
Raise_Application_Error (-20204, 'Error');
end if;
END;
The only thing that seems to work in my code is if(prefix != 'x'). Other stuff doesn't work at all. What am I doing wrong?

You can do all of that with one check:
IF NOT REGEXP_LIKE(:new.login, '^x[a-z]{5}\d\d$') THEN
Raise_Application_Error (-20204, 'Error');
Note that you had also your boolean logic reversed: in both last tests you have a double negation (NOT in the IF and [^...] in the regex).
Note that in the proposed regex the ^ and $ are needed to make sure the match is with the complete login value, not just a substring.

Related

Oracle Collection function - Not getting expected results

Please have a look at the below code. The 4th element in the collection is null, and hence I am expecting that the variable does not exists. But still, I am getting TRUE when using exist function. Please clarify as to why this is happening?
DECLARE
type nt is varray(5) of varchar2(5);
nt1 nt := nt();
BEGIN
nt1.extend(5);
nt1 :=nt('ant','ball','cat',null,'elm');
for i in nt1.first..nt1.last
loop
dbms_output.put_line(nt1(i));
end loop;
if nt1.exists(4) then dbms_output.put_line('TRUE'); end if;
END;
Is it enough, If I have just extended that variable, for the exist function to evaluate to true?
The element is there, only its value is null.
With
nt1.extend(5);
you already create 5 entries, so nt1.exists(4) is true. As a varray can have no gaps, exists only tells you if the array is filled up to that position, yet. (And if it is, you can safely access that element.)
if nt1.exists(4) then
if nt1(4) is null then
dbms_output.put_line('Entry 4 is empty (null)');
else
dbms_output.put_line('Entry 4 has value' || nt1(4));
end if;
else
dbms_output.put_line('Array not filled up till 4th element, yet');
end if;

SQL decode base64 in sql developer

I got the below sql base64 decoder from stackoverflow (forgot the link). I have slightly modified to my requirement. When I try to execute, the decoding is only partially working. I have max of 10MB worth of base64 encoded clob data per row in my DB.
Not sure what is that I am missing.
create or replace FUNCTION Decode64_CLOB3(IN_CLOB CLOB) RETURN CLOB IS
clobOriginal clob;
clobInBase64 clob;
substring varchar2(2000);
n pls_integer := 0;
substring_length pls_integer := 2000;
function from_base64(t in varchar2) return varchar2 is
begin
return
utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t)));
end from_base64;
begin
select myCLobData into clobInBase64 from myClobTable where ID = 3;
clobInBase64 :=IN_CLOB;
n := 0;
clobOriginal := null;
while true loop
substring := dbms_lob.substr(clobInBase64,
least(substring_length, length(clobInBase64) - (substring_length * n + 1)));
if substring is null then
DBMS_OUTPUT.put_line('this is me 2');
exit;
end if;
clobOriginal := clobOriginal || from_base64(substring);
n := n + 1;
end loop;
return clobOriginal;
end;
update2
I did some debugging and found out that the issue is with the substring.
The substring works fine for the firstloop of (2000) char. but its not able to move the the next 2000 char. Not sure what is the problem. Can some one advise.
You've changed the call to dbms_lob.substr() and in doing so you've removed the third argument, which is the offset. Since you aren't providing that it defaults to 1, so every time around the loop you're getting the same value - the first 2000 characters from your encoded string.
Well, you'll get 2000 for lots of calls, then once n gets big enough you'll get null, with the code you've posted. Since you said it goes into a 'hung' state that suggests what you're running is slightly different.
With some slight modifications this seemed to work:
create or replace function decode64_clob3(p_clob_encoded clob)
return clob
is
l_clob_decoded clob;
substring varchar2(2048);
substring_length pls_integer := 2048;
n pls_integer := 0;
function from_base64(t in varchar2) return varchar2
is
begin
return utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t)));
end from_base64;
begin
dbms_lob.createtemporary(l_clob_decoded, false);
while true
loop
substring := dbms_lob.substr(p_clob_encoded, substring_length,
(substring_length * n) + 1);
if substring is null then
exit;
end if;
dbms_lob.append(l_clob_decoded, from_base64(substring));
n := n + 1;
/* protective check for infinite loop while debugging
if n > 10000 then
dbms_output.put_line('n is ' || n);
exit;
end if;
*/
end loop;
return l_clob_decoded;
end;
/
I changed the CLOB handling slightly; concatenation is fine but this is a little more explicit. I'm not sure I'd bother with the nested function though, personally. And I'm not sure the least() calculation is really adding anything.
But this can error with either ORA-06502: PL/SQL: numeric or value error: invalid LOB locator specified: ORA-22275 or ORA-14553: cannot perform a lob write operation inside a query if the base64-encoded value is line-wrapped, which is usually the case. The first chunk is decoded OK, but the next chunk starts with an unconsumed newline which throws everything off and produces garbage; eventually that garbage is null or some other value that causes one of those exceptions.
So you need to track the position as you move through the CLOB, in multiples of the line-wrap length; and adjust the tracked position to take extra newlines into account:
create or replace function decode64_clob3(p_clob_encoded clob)
return clob
is
l_clob_decoded clob;
l_substring varchar2(2048);
l_substring_length pls_integer := 2048;
l_pos pls_integer := 1;
begin
dbms_lob.createtemporary(l_clob_decoded, false);
while l_pos <= dbms_lob.getlength(p_clob_encoded)
loop
l_substring := dbms_lob.substr(p_clob_encoded, l_substring_length, l_pos);
if l_substring is null then
exit;
end if;
dbms_lob.append(l_clob_decoded,
utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(l_substring))));
/* Adjust l_pos to skip over CR/LF chars if base64 is line-wrapped */
l_pos := l_pos + length(l_substring);
while dbms_lob.substr(p_clob_encoded, 1, l_pos) in (chr(10), chr(13)) loop
l_pos := l_pos + 1;
end loop;
end loop;
return l_clob_decoded;
end;
/
Or strip out any potential newline/carriage return characters first:
create or replace function decode64_clob3(p_clob_encoded clob)
return clob
is
l_clob_encoded clob;
l_clob_decoded clob;
l_substring varchar2(2048);
l_substring_length pls_integer := 2048;
l_pos pls_integer := 1;
begin
l_clob_encoded := replace(replace(p_clob_encoded, chr(10), null), chr(13), null);
dbms_lob.createtemporary(l_clob_decoded, false);
while l_pos <= dbms_lob.getlength(l_clob_encoded)
loop
l_substring := dbms_lob.substr(l_clob_encoded, l_substring_length, l_pos);
if l_substring is null then
exit;
end if;
dbms_lob.append(l_clob_decoded,
utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(l_substring))));
l_pos := l_pos + length(l_substring);
end loop;
return l_clob_decoded;
end;
/
This works with wrapped and unwrapped base64 values.
set serveroutput on
with t (c) as (
select utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw('Hello world'))) from dual
)
select c, decode64_clob3(c)
from t;
C
--------------------------------------------------------------------------------
DECODE64_CLOB3(C)
--------------------------------------------------------------------------------
SGVsbG8gd29ybGQ=
Hello world
Also tested with encoded values that are larger than the substring_length value.

How get all matching positions in a string?

I have a column flag_acumu in a table in PostgreSQL with values like:
'SSNSSNNNNNNNNNNNNNNNNNNNNNNNNNNNNSNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN'
I need to show all positions with an 'S'. With this code, I only get the first such position, but not the later ones.
SELECT codn_conce, flag_acumu, position('S' IN flag_acumu) AS the_pos
FROM dh12
WHERE position('S' IN flag_acumu) != 0
ORDER BY the_pos ASC;
How to get all of them?
In Postgres 9.4 or later you can conveniently use unnest() in combination with WITH ORDINALITY:
SELECT *
FROM dh12 d
JOIN unnest(string_to_array(d.flag_acumu, NULL))
WITH ORDINALITY u(elem, the_pos) ON u.elem = 'S'
WHERE d.flag_acumu LIKE '%S%' -- optional, see below
ORDER BY d.codn_conce, u.the_pos;
This returns one row per match.
WHERE d.flag_acumu LIKE '%S%' is optional to quickly eliminate source rows without any matches. Pays if there are more than a few such rows.
Detailed explanation and alternatives for older versions:
PostgreSQL unnest() with element number
Since you didn't specify your needs to a point in which one could answer properly, I'm going with my assumption that you want a list of positions of occurence of a substring (can be more than 1 character long).
Here's the function to do that using:
FOR .. LOOP control structure,
function substr(text, int, int).
CREATE OR REPLACE FUNCTION get_all_positions_of_substring(text, text)
RETURNS text
STABLE
STRICT
LANGUAGE plpgsql
AS $$
DECLARE
output_text TEXT := '';
BEGIN
FOR i IN 1..length($1)
LOOP
IF substr($1, i, length($2)) = $2 THEN
output_text := CONCAT(output_text, ';', i);
END IF;
END LOOP;
-- Remove first semicolon
output_text := substr(output_text, 2, length(output_text));
RETURN output_text;
END;
$$;
Sample call and output
postgres=# select * from get_all_positions_of_substring('soklesocmxsoso','so');
get_all_positions_of_substring
--------------------------------
1;6;11;13
This works too. And a bit faster I think.
create or replace function findAllposition(_pat varchar, _tar varchar)
returns int[] as
$body$
declare _poslist int[]; _pos int;
begin
_pos := position(_pat in _tar);
while (_pos>0)
loop
if array_length(_poslist,1) is null then
_poslist := _poslist || (_pos);
else
_poslist := _poslist || (_pos + _poslist[array_length(_poslist,1)] + 1);
end if;
_tar := substr(_tar, _pos + 1, length(_tar));
_pos := position(_pat in _tar);
end loop;
return _poslist;
end;
$body$
language plpgsql;
Will return a position list which is an int array.
{position1, position2, position3, etc.}

recursive permutation algorithm in plsql

I'm trying to run a recursive procedure that permutates a given string.
It's compiling on sqldeveloper but when I try to run with input its giving me ora-06502: numeric or value errors on line 13 (the prefix assignment)
create or replace
procedure print_anagrams
(pre in varchar2, str in varchar2)
is
prefix varchar2(30);
stringg varchar2(30);
strlen number;
begin
strlen := length(str);
if strlen = 0 then
dbms_output.put_line(pre);
else
for i in 1..strlen loop
prefix := pre || SUBSTR(str,i,1);
stringg := SUBSTR(str,1,i) || SUBSTR(str,i+1,strlen);
print_anagrams(prefix,stringg);
end loop;
end if;
end;
There were two problems:
Firstly, the LENGTH function returns NULL if its parameter is NULL, not 0, so the following condition in your code was never true (because strlen is NULL):
if strlen = 0 then
You were getting the ora-06502: numeric or value errors error, because, when the str argument was empty, the upper range limit of the FOR LOOP was NULL (because strlen is NULL):
for i in 1..NULL loop
And this yields:
ora-06502: numeric or value errors
Secondly, the last parameter of the substr function in Oracle has different meaning than String's substring method in Java. In Oracle, that parameter means "how many characters should be returned", whereas in Java it stands for "end index of the substring to be returned from the original string", so the following line should be changed:
stringg := SUBSTR(str,1,i) || SUBSTR(str,i+1,strlen);
to:
stringg := SUBSTR(str,1,i - 1) || SUBSTR(str,i+1,strlen);
The change had to be made, because in the Java code that you provided the link to, the loop starts from 0, and 0 is passed as the third argument, which results in an empty string being returned for the first iteration of the loop. Without the change, first iteration in PL/SQL version would return the first character from the argument.
In the end, you get a working procedure:
create or replace
procedure print_anagrams
(pre in varchar2, str in varchar2)
is
prefix varchar2(30);
stringg varchar2(30);
strlen number;
begin
strlen := length(str);
if NVL(strlen, 0) = 0 then
dbms_output.put_line(pre);
else
for i in 1..strlen loop
prefix := pre || SUBSTR(str,i,1);
stringg := SUBSTR(str,1,i - 1) || SUBSTR(str,i+1,strlen);
print_anagrams(prefix,stringg);
end loop;
end if;
end;
/
Test:
EXEC print_anagrams('', 'cat');
Output:
cat
cta
act
atc
tca
tac
Oracle Substr Function
Java String's substring method

NO_DATA_FOUND EXCEPTION in two cursors - Oracle PL/SQL

I need to be able to raise an exception as the title says. The exception I have currently gives me this error:
DBMS_OUTPUT.PUT_LINE (‘No rows found’);
*
ERROR at line 39:
ORA-06550: line 39, column 23:
PLS-00103: Encountered the symbol "`" when expecting one of the following:
( ) - + case mod new not null others <an identifier>
<a double-quoted delimited-identifier> <a bind variable>
table avg count current exists max min prior sql stddev sum
variance execute multiset the both leading trailing forall
merge year month DAY_ hour minute second timezone_hour
timezone_minute timezone_region timezone_abbr time timestamp
interval date
<a string literal with character set specification>
This is my code:
SET SERVEROUTPUT ON FORMAT WRAP SIZE 12000
Declare
v_model VARCHAR2(40);
v_carcategory VARCHAR2(40);
v_totalcars NUMBER;
v_maxdate DATE:=TO_DATE(1, 'J');
Cursor carcur IS
SELECT * FROM i_car;
CURSOR c1(v_car_registration VARCHAR2) IS
SELECT * from i_booking a
WHERE a.registration=v_car_registration;
Begin
For car_rec in carcur
LOOP
v_maxdate:=TO_DATE(1, 'J');
for rec in c1(car_rec.registration)
LOOP
IF rec.date_reserved > v_maxdate
then
v_maxdate:=rec.date_reserved ;
If car_rec.Cost <=50000 THEN v_carcategory := 'Budget Car';
End IF;
If car_rec.Cost BETWEEN 50000 AND 100000 THEN v_carcategory := 'Standard Car';
End IF;
If car_rec.Cost >=100000 THEN v_carcategory := 'Premium Car';
End If;
end IF;
v_totalcars := findtotalcarmodels (car_rec.model_name);
end loop;
DBMS_OUTPUT.PUT_LINE('Registration:'|| ' '|| car_rec.registration);
DBMS_OUTPUT.PUT_LINE('Cost:'|| ' $' || car_rec.Cost);
DBMS_OUTPUT.PUT_LINE('Model Name:'|| ' '|| car_rec.model_name);
DBMS_OUTPUT.PUT_LINE('Car Category:'|| ' '||v_carcategory);
DBMS_OUTPUT.PUT_LINE('Total number of Cars:'|| ' '||v_totalcars);
DBMS_OUTPUT.PUT_LINE('Most Recent Rental Date: '|| ' '||v_maxdate);
DBMS_OUTPUT.NEW_LINE;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE (‘No rows found’);
END;
/
I am not overly sure about the positioning of my exception. Any advice would be much appreciated.
The immediate problem is that your dbms_output.put_line call is using Microsoft curly quotes rather than the standard single-quote character to delimit the string. You need to use the standard character, not the Microsoft character (all your other strings appear to be using the standard character).
Taking a step back, it makes no sense to catch a no_data_found exception only to call dbms_output. There is no guarantee that data written to dbms_output will ever be sent to the client application or that the client application will ever display it to a user.
It also doesn't appear that you have any code that would potentially raise a no_data_found exception. Opening a cursor that returns 0 rows does not result in an exception. If you expected to receive exactly 1 row, you could write a SELECT INTO statement and that would raise an exception if anything other than 1 row was returned. If your goal here is to determine how many times you iterated through the loop, you could potentially use the %rowcount attribute of the cursor.
Finally, it would make thinks much clearer if you formatted your code so that lines were indented based on what block they were a part of. Code like
FOR rec IN cursor
LOOP
IF <<something>>
THEN
<<do something>>
END IF;
IF <<something else>>
THEN
<<something else>>
END IF;
<<more stuff>>
END LOOP;
is much easier to follow.