Using a variable with Substr in PLSQL - oracle

I am trying to use a variable to set the length of a substr command.
If I explicitly set say 0,13 the code works fine however, I want to be able to use a variable.
Near the end of the code is the area of question:
:new.DOCNO := substr(:new.DESCRIP,0,15); -- success ... but limited
:new.DOCNO := substr(:new.DESCRIP,0,n_len); -- fail
The code overall is looking to find a number pattern from a file name and place it into another column.
Example: HR_1000-0001_A This is a file.pdf
I have tried various data types: integer, number,... etc.
The code compiles but fails when I run the trigger.
CREATE OR REPLACE TRIGGER set_doc_number
BEFORE UPDATE ON external_doc
FOR EACH ROW
WHEN (
new.STATUS = 'Released'
-- current document# is either '' or is ONLY a number
AND (
new.DOC_LIBRARY_ID = ''
OR
REGEXP_LIKE(new.DOC_LIBRARY_ID,'(\d+)')
)
)
DECLARE
-- init variables
b_update boolean;
n_len number := 0;
BEGIN
-- reset for each loop
b_update := FALSE;
-- check if criteria is met
-- AAA_####-####_AA
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 15;
END IF;
-- AA_####-####_AA
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 14;
END IF;
-- AAA_####-####_A
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 15;
END IF;
-- AA_####-####_A
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 14;
END IF;
-- AAA_####-####
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z][A-Z]_(\d{4})-(\d{4}))') THEN
b_update := TRUE;
n_len := 13;
END IF;
-- AA_####-####
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z]_(\d{4})-(\d{4}))') THEN
b_update := TRUE;
n_len := 12;
END IF;
-- can we update?
IF b_update = TRUE THEN
-- update
:new.DOCNO := substr(:new.DESCRIP,0,n_len);
END IF;
END;

I'm not sure why using n_len fails, it looks fine to me. PL/SQL indexes start at 1 and not 0, but it works fine if you use 0, so that's not an issue.
If you're going to use regexp functions, I'd suggest just using regexp_substr:
CREATE OR REPLACE TRIGGER set_doc_number
BEFORE UPDATE ON external_doc
FOR EACH ROW
WHEN (
new.STATUS = 'Released'
-- current document# is either '' or is ONLY a number
AND (
new.DOC_LIBRARY_ID = ''
OR
REGEXP_LIKE(new.DOC_LIBRARY_ID,'(\d+)')
)
)
DECLARE
-- init variables
v_docno varchar2(20);
BEGIN
-- look for a str with (2-3 alpha)_####-####(optional underscore and 1-3 alpha)
v_docno := regexp_substr(:new.DESCRIP,'[A-Z][A-Z][A-Z]?_\d{4}-\d{4}(_[A-Z][A-Z]?[A-Z]?)?');
-- can we update?
IF v_docno is not null THEN
-- update
:new.DOCNO := v_docno;
END IF;
END;

Related

PL/SQL Get Dimensions of JPEG Image

Support for Oracle Multimedia was dropped in Oracle 19c, so my code to extract dimensions from a JPEG image is throwing an error. Is there a workaround to this issue?
For Oracle 12, my code looked like this:
BEGIN
img := ORDSYS.ORDImage.init('FILE', my_dir, my_img_name);
img.setProperties();
w := img.getWidth();
h := img.getHeight();
EXCEPTION
WHEN OTHERS THEN
w := NULL;
h := NULL;
END;
Based on code found in a response to "Getting Image size of JPEG from its binary" (I'm not sure which language), I came up with this procedure:
PROCEDURE p_jpegstats(directory_in IN VARCHAR2,
filename_in IN VARCHAR2,
height_out OUT INTEGER,
width_out OUT INTEGER,
bpc_out OUT INTEGER, -- bits per channel
cps_out OUT INTEGER -- colors per component
) IS
file bfile;
pos INTEGER:=1;
h VARCHAR2(4);
w VARCHAR2(4);
mrkr VARCHAR2(2);
len VARCHAR2(4);
bpc VARCHAR2(2);
cps VARCHAR2(2);
-- Declare a quick helper procedure for readability
PROCEDURE next_byte(buf out varchar2, amt INTEGER:=1) IS
cnt INTEGER;
BEGIN
cnt := amt;
dbms_lob.read(file, cnt, pos, buf);
pos := pos + cnt;
END next_byte;
BEGIN
-- This code is based off of code found here: https://stackoverflow.com/a/48488655/3303651
-- Open the file
file := bfilename(directory_in, filename_in);
dbms_lob.fileopen(file);
-- Init the output variables in case something goes awry.
height_out := NULL;
width_out := NULL;
bpc_out := NULL;
cps_out := NULL;
LOOP
BEGIN
LOOP
next_byte(mrkr);
EXIT WHEN mrkr <> 'FF';
END LOOP;
CONTINUE WHEN mrkr = 'D8'; -- Start of image (SOI)
EXIT WHEN mrkr = 'D9'; -- End of image (EOI)
CONTINUE WHEN mrkr BETWEEN 'D0' AND 'D7';
CONTINUE WHEN mrkr = '01'; -- TEM
next_byte(len, 2);
IF mrkr = 'C0' THEN
next_byte(bpc); -- bits per channel
next_byte(h, 2); -- height
next_byte(w, 2); -- width
next_byte(cps); -- colors per component
EXIT;
END IF;
pos := pos + to_number(len, 'XXXX') - 2;
EXCEPTION WHEN OTHERS THEN EXIT; END;
END LOOP;
-- Write back the values we found
height_out := to_number(h, 'XXXX');
width_out := to_number(w, 'XXXX');
bpc_out := to_number(bpc, 'XX');
cps_out := to_number(cps, 'XX');
-- close out the file
dbms_lob.fileclose(file);
END p_jpegstats;
This will throw an error if the directory is invalid or the file can't be opened. If the outputs are NULL, then there was some other issue.
It's probably not the most efficient or elegant code (I'm not a pro with PL/SQL [yet!]), but it works. Here is an example usage:
DECLARE
h INTEGER;
w INTEGER;
bpc INTEGER;
cps INTEGER;
BEGIN
p_jpegstats('MY_DIR', 'my_image.jpg', h, w, bpc, cps);
DBMS_OUTPUT.PUT_LINE(w || ' x ' || h || ' ' || bpc || ' ' || cps);
END;
/
This ought to return something like
800 x 200 8 3
Edit: Removed unused variable.

PL/SQL How to compare two associative array?

How can I check if two associative arrays are same or not?
E.g.
ARRAY1('ID') := 1;
ARRAY1('NAME') := 'Joe';
ARRAY2('ID') := 1;
ARRAY2('NAME') := 'Joe';
-- and i want to do like this
IF ARRAY1 = ARRAY2 THEN
-- Do something.
END IF;
You can compare two associative array using a function to compare the keys and values:
DECLARE
TYPE test_array IS TABLE OF VARCHAR2(200) INDEX BY VARCHAR2(10);
array1 test_array;
array2 test_array;
array3 test_array;
FUNCTION is_equal(
arr1 test_array,
arr2 test_array
) RETURN BOOLEAN
IS
i VARCHAR2(10);
BEGIN
i := arr1.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if the keys in the first array do not exists in the
-- second array or if the values are different.
IF ( NOT arr2.EXISTS( i ) )
OR ( arr1(i) <> arr2(i) )
OR ( arr1(i) IS NULL AND arr2(i) IS NOT NULL )
OR ( arr1(i) IS NOT NULL AND arr2(i) IS NULL )
THEN
RETURN FALSE;
END IF;
i := arr1.NEXT(i);
END LOOP;
i := arr2.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if there are any keys in the second array that do not
-- exists in the first array.
IF ( NOT arr1.EXISTS( i ) )
THEN
RETURN FALSE;
END IF;
i := arr2.NEXT(i);
END LOOP;
RETURN TRUE;
END;
BEGIN
array1('ID') := '1';
array1('NAME') := 'Joe';
array2('ID') := '1';
array2('NAME') := 'Joe';
array3('ID') := '1';
array3('NAME') := 'Joe';
array3('XYZ') := 'ABC';
IF is_equal( array1, array2 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 2 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 2 are different' );
END IF;
IF is_equal( array1, array3 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 3 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 3 are different' );
END IF;
END;
/
Outputs:
1 and 2 are the same
1 and 3 are different
Oracle documentation is your friend.
https://docs.oracle.com/database/121/LNPLS/composites.htm#LNPLS99915
In particular:
You cannot compare associative array variables to the value NULL or to each other.
Which means, if you need to compare associative arrays to each other, you have only two choices... check them element by element, for example in a loop (on the elements of one array - and make sure you check to see if they have the same number of elements); or, if you need to do this often, write your own function that compares arrays in that manner, and call it whenever you need to.
Just don't ask why Oracle is not providing a native PL/SQL function for this; you will very likely get no answers.

Validation in Delphi

I'm a student and stuck on Delphi Validation. Here is my code:
begin
valid := true;
for I:=1 to Length(edtvalue.Text) do
if not (edtvalue.Text[I] in ['0'..'9','.'] )then
valid:= false;
if not valid then
begin
showmessage ('This item is not within the range');
DataItem1 := 0;
end
else
dataitem1 := strtofloat(edtvalue.Text);
This code reads in a value that the user inputs and checks whether it actually is an integer and detects when a user inputs letters.
However when the user inputs something else (e.g. + or #) the code doesn't work and breaks the system. Is there a way I can fix this please?
Thanks in advance
Use TryStrToFloat :
var
F: Double;
begin
if not TryStrToFloat(edtvalue.Text, F) then
showmessage ('This item is not within the range');
else
dataitem1 := F;
end;
Or if you want to set DataItem1 to 0 when error :
var
F: Double;
begin
if not TryStrToFloat(edtvalue.Text, F) then
begin
showmessage ('This item is not within the range');
DataItem1 := 0;
end
else
dataitem1 := F;
end;
Also you can create a Function to do that , like :
function IsFloat(Str: string): Boolean;
var
I: Double;
C: Integer;
begin
Val(Str, I, C);
Result := C = 0;
end;
I changed to use TryStrToFloat as recommended by David in the comments, you just need to declare that val variable:
var
val: Extended;
begin
val := 0;
if not TryStrToFloat(edtvalue.Text, val) then
showmessage ('This item is not within the range');
dataitem1 := val;
end;

Checking if word is palindrome with function

I have to write a program in Pascal which checks whether a word is a palindrome.
For example:
if I input "abba" then write 'TRUE'
input 'abb a' then write 'TRUE'
input 'abca' write 'FALSE'
I wrote this:
program palindromek;
var i,j,delka,pul:integer;
str:string;
function palindrom(slovo:string):boolean;
const mezera=32;
begin
delka:=length(str);
if (delka mod 2) = 0 then pul:=delka div 2
else pul:=(delka-1) div 2;
for i:=1 to delka do
begin
if (ord(slovo[i])>=ord('a')) and (ord(slovo[i])<=ord('z')) then
begin
if (delka>=4)and(delka<=100) then
begin
if (length(str) mod 2) = 0 then {slovo se sudym poctem pismen}
begin
for j:=1 to pul do
begin
if slovo[j]=slovo[length(str)-j+1]
then palindrom:=true else palindrom:=false
end
end else
begin
for j:=1 to pul do
begin
if slovo[j]=slovo[length(str)-j+1]
then palindrom:=true else palindrom:=false
end
end
end else if slovo[1]=slovo[delka]
then palindrom:=true else palindrom:=false
end
end;
end;
begin
readln(str);
writeln(palindrom(str));
end.
but it has to ignore spaces. Do you have any idea please?
To remove all spaces, you can use function like this:
procedure RemoveSpacesInplace(var s: string);
var
i, SpaceCount: Integer;
begin
SpaceCount := 0;
for i := 1 to Length(s) do
if s[i] = ' ' then
Inc(SpaceCount)
else
s[i - SpaceCount] := s[i];
SetLength(s, Length(s) - SpaceCount);
end;
You can modify it for other non-letter chars.
Note that your logic for odd and even length is excessive. Try to simplify it.
You can use the functions StringReplace and ReverseString for your task.
program palindromek;
uses SysUtils, StrUtils;
var
str:string;
function palindrom(slovo:string):boolean;
begin
slovo := StringReplace(slovo, ' ', '', [rfReplaceAll]);
Result := slovo = ReverseString(slovo)
end;
begin
readln(str);
writeln(palindrom(str));
readln;
end.
If you are not allowed to use SysUtils and StrUtils then you can manually reverse your string and then compare if the original string and the reversed string are equal.
This would look something like this: (not tested!)
function palindrom(slovo:string):boolean;
var slovofor: string;
slovorev: string;
i: integer;
begin
for i:= length(slovo) downto 1 do begin
if slovo[i] <> ' ' then begin
slovofor := slovofor + slovo[length(slovo)-i+1];
slovorev := slovorev + slovo[i];
end;
end;
writeln(slovofor);
Result := slovofor = slovorev
end;

Inno Setup - How to display notifying message while installing if application is already installed on the machine?

I am new to Inno Setup. I am creating an installer for my C# application using Inno Setup compiler-5.1.6.
Using my script an installer is created, and it works fine. It installs the application and can be uninstalled from control panel as well.
But my problem is that, if my application is already installed on my machine and I try to install it again it get installed without any message. It replaces the older installation.
So my requirement is that , if application is already installed , it should show me a message that "App already installed {existing version}. Do you want to replace existing installation." with 'Yes' and 'No' buttons. If user clicks 'Yes' button installer should proceed normally otherwise it should exit installation wizard without new installation.
AppVersion: it is changeable as version increases.
AppId: it will remain same for all version.
So, please can someone help me to achieve above..
Thanks in advance . .
Plz refer my question how to terminate installer if unstallation of legacy version of software is cancelled before executing it? , You can use same trick of checking registry for your app to check whether it is installed or not.
and to check version of app you can use following code that i got from https://blog.lextudio.com/2007/08/inno-setup-script-sample-for-version-comparison-2/:
[Code]
function GetNumber(var temp: String): Integer;
var
part: String;
pos1: Integer;
begin
if Length(temp) = 0 then
begin
Result := -1;
Exit;
end;
pos1 := Pos('.', temp);
if (pos1 = 0) then
begin
Result := StrToInt(temp);
temp := '';
end
else
begin
part := Copy(temp, 1, pos1 - 1);
temp := Copy(temp, pos1 + 1, Length(temp));
Result := StrToInt(part);
end;
end;
function CompareInner(var temp1, temp2: String): Integer;
var
num1, num2: Integer;
begin
num1 := GetNumber(temp1);
num2 := GetNumber(temp2);
if (num1 = -1) or (num2 = -1) then
begin
Result := 0;
Exit;
end;
if (num1 > num2) then
begin
Result := 1;
end
else if (num1 < num2) then
begin
Result := -1;
end
else
begin
Result := CompareInner(temp1, temp2);
end;
end;
function CompareVersion(str1, str2: String): Integer;
var
temp1, temp2: String;
begin
temp1 := str1;
temp2 := str2;
Result := CompareInner(temp1, temp2);
end;
function InitializeSetup(): Boolean;
var
oldVersion: String;
uninstaller: String;
ErrorCode: Integer;
begin
if RegKeyExists(HKEY_LOCAL_MACHINE,'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F768F6BA-F164-4599-BC26-DCCFC2F76855}_is1') then
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F768F6BA-F164-4599-BC26-DCCFC2F76855}_is1','DisplayVersion', oldVersion);
if (CompareVersion(oldVersion, '6.0.0.1004') < 0) then
begin
if MsgBox('Version ' + oldVersion + ' of Code Beautifier Collection is already installed. Continue to use this old version?',mbConfirmation, MB_YESNO) = IDYES then
begin
Result := False;
end
else
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F768F6BA-F164-4599-BC26-DCCFC2F76855}_is1','UninstallString', uninstaller);
ShellExec('runas', uninstaller, '/SILENT', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
Result := True;
end;
end
else
begin
MsgBox('Version ' + oldVersion + ' of Code Beautifier Collection is already installed. This installer will exit.',mbInformation, MB_OK);
Result := False;
end;
end
else
begin
Result := True;
end;
end;
GetNumber function returns only 'major' release.
To apply full Version comparison, you must concatenate Major and Minor release parts.
function GetNumber(var temp: String): Integer;
var
part: String;
pos1: Integer;
begin
if Length(temp) = 0 then
begin
Result := -1;
Exit;
end;
pos1 := Pos('.', temp);
if (pos1 = 0) then
begin
Result := StrToInt(temp);
temp := '';
end
else
begin
part := Copy(temp, 1, pos1 - 1);
temp := Copy(temp, pos1 + 1, Length(temp));
insert(temp, part, pos1);
Result := StrToInt(part);
end;
end;

Resources