How can I optimize my code so that I dont duplicate it - if-statement

I'm trying to create a procedure that puts "-" between different dates and "0" if the is single digit, but i'm having a very hard time not duplicating my code.
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 1);
Put("-");
if Date.M <= 9 then
Put("0");
end if;
Put(Date.M, Width => 1);
Put("-");
if Date.D <= 9 then
Put("0");
end if;
Put(Date.D, Width => 1);
end put;
This is the best solution I came up with

An example of a nested procedure is:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
subtype Year_Num is Integer range 1_900 .. 2_040;
subtype Month_Num is Integer range 1 .. 12;
subtype Day_Num is Integer range 1 .. 31;
type Date_Type is record
Y : Year_Num;
M : Month_Num;
D : Day_Num;
end record;
procedure Put (Date : Date_Type) is
procedure zerofill (Val : in Integer) is
begin
Put ("-" & (if (Val < 10) then "0" else ""));
Put (Item => Val, Width => 0);
end zerofill;
begin
Put (Item => Date.Y, Width => 0);
zerofill (Date.M);
zerofill (Date.D);
end Put;
A_Date : Date_Type := (2022, 12, 8);
begin
Put (A_Date);
end Main;
The nested nature of this answer is because the zerofill procedure is defined within the put procedure.

Came to this solution, I didnt duplicate my code but I somehow feel like I made it more complicated
procedure Zero(item : in integer) is
begin
Put("-");
if item < 10 then
Put('0');
end if;
Put(Item,Width =>0);
end Zero;
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 0);
Zero(Date.M);
Zero(Date.D);
end put;

Related

How to return multiple values through function

I have created the below function that will return workspace details which the loggedin user has access to.
But this function is returning only the first record from the select list.
I need all the records to be displayed as output.
Please modify it and let me know.
CREATE OR REPLACE FUNCTION "F_WORKSPACE_LOGIN_USERS" (
p_email VARCHAR2
) RETURN VARCHAR2 IS
l_error VARCHAR2(1000);
l_workspace VARCHAR2(1000);
l_teams VARCHAR2(1000);
l_team VARCHAR2(1000);
BEGIN
FOR i IN ( SELECT a.name workspace,
a.team_id id
FROM slackdatawarehouse.teams a,
( SELECT TRIM(workspaces) workspaces
FROM alluser_workspaces_fact
WHERE lower(email) = lower(p_email)
) b
WHERE a.team_id IN ( SELECT c.team_id
FROM slackdatawarehouse.team_tokens c
)
OR instr(', '
|| lower(b.workspaces),', '
|| lower(a.name) ) > 0
ORDER BY 1 ) LOOP
l_teams := l_team
|| ','
|| i.id;
l_teams := ltrim(rtrim(l_teams,','),',');
RETURN l_teams;
END LOOP;
END;
Current output is :
T6HPQ5LF7,T6XBXVAA1,T905JLZ62,T7CN08JPQ,T9MV4732M,T5PGS72NA,T5A4YHMUH,TAAFTFS0P,T69BE9T2A,T85D2D8MT,T858U7SF4,T9D16DF5X,T9DHDV61G,T9D17RDT3,T5Y03HDQ8,T5F5QPRK7
Required output is :
T6HPQ5LF7
T6XBXVAA1
T905JLZ62
i need output like above as one by one
I don't know what that code really does (can't test it), but this might be the culprit:
...
RETURN l_teams;
END LOOP;
As soon as code reaches the RETURN statement, it exits the loop and ... well, returns what's currently in L_TEAMS variable. Therefore, move RETURN out of the loop:
...
END LOOP;
RETURN l_teams;
If it still doesn't work as expected (which might be the case), have a look at pipelined functions (for example, on Oracle-base site) as they are designed to return values you seem to be looking for.
A simple example:
SQL> create or replace type t_dp_row as object
2 (deptno number,
3 dname varchar2(20));
4 /
Type created.
SQL> create or replace type t_dp_tab is table of t_dp_row;
2 /
Type created.
SQL> create or replace function f_depts
2 return t_dp_tab pipelined
3 is
4 begin
5 for cur_r in (select deptno, dname from dept)
6 loop
7 pipe row(t_dp_row(cur_r.deptno, cur_r.dname));
8 end loop;
9 return;
10 end;
11 /
Function created.
SQL> select * from table(f_depts);
DEPTNO DNAME
---------- --------------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
SQL>

Passing array of aliased records to a procedure

In Ada 2012, I want to have a linked list inside a declared array, rather than allocated. I want to have the linking pointers set by a procedure.
I have simplified my program to the following that demonstrates the principle I want to use but I cannot get this to compile in Gnat 4.9.2 (Debian Jessie) running on Raspi...
procedure Arr is
type Cell;
type Cell_Ptr is access all Cell;
type Cell is
record
Number : Integer := 0;
Next : Cell_Ptr := null;
end record;
type Chain is array (1 .. 100) of aliased Cell;
procedure Make_Links (Ch : in out Chain);
procedure Make_Links (Ch : in out Chain) is
begin
for I in Ch'First .. Ch'Last - 1 loop
Ch (I).Next := Ch (I + 1)'Access; -- ERROR HERE
end loop;
end Make_Links;
My_Chain : Chain;
begin
Make_Links (My_Chain);
end Arr;
I get this compiler error:
"non-local pointer cannot point to local object" at the line indicated above.
I know I'm trying to do something a bit odd but I plan on having a few other functions that perform the linking in different ways (backwards, or randomly etc) based on which procedure I pass this array of cells to.
How do I fix this code so that it compiles? Can't quite get my head around this one (I'm still a novice but enjoying the learning process).
As you are not actually allocating & freeing memory, in dont see the need for pointers. I would achieve the same functionality by doing something like this:
procedure Arr is
type Cell_Index is new Integer range 0 .. 100;
subtype Valid_Cell_Index is Cell_Index range 1 .. Cell_Index'Last;
type Cell is
record
Number : Integer := 0;
Next : Cell_Index := 0;
end record;
type Chain is array (Valid_Cell_Index) of Cell;
procedure Make_Links (Ch : in out Chain);
procedure Make_Links (Ch : in out Chain) is
begin
for I in Valid_Cell_Index'First .. Valid_Cell_Index'Last - 1 loop
Ch (I).Next := I+1;
end loop;
end Make_Links;
My_Chain : Chain;
begin
Make_Links (My_Chain);
end Arr;
This way you are still using Next as an index into the same array, and can pre-load your array with whatever linking pattern you want.
Instead of using ’Access, use ’Unrestricted_Access. This is one of GNAT’s “implementation-defined” attributes:
The Unrestricted Access attribute is similar to Access except that all accessibility and aliased view checks are omitted. This is a user-beware attribute.
I figured it out in the end. The following code is a modified version of the one in the OP. It does what I originally wanted without doing anything unpleasant...
with Ada.Integer_Text_IO, Ada.Text_IO;
use Ada.Integer_Text_IO, Ada.Text_IO;
procedure Arr is
type Cell;
type Cell_Ptr is access all Cell;
type Cell is
record
Number : Integer := 0;
Next : Cell_Ptr := null;
end record;
type Chain is array (1 .. 100) of aliased Cell;
type Chain_Ptr is access all Chain;
procedure Make_Links (CP : in out Chain_Ptr);
procedure Make_Links (CP : in out Chain_Ptr) is
begin
for I in CP'First .. CP'Last - 1 loop
CP.all (I).Next := CP.all (I + 1)'Access;
end loop;
end Make_Links;
My_Chain : aliased Chain;
My_CP : Chain_Ptr := null;
My_C : Cell_Ptr := null;
begin
My_CP := My_Chain'Access;
Make_Links (My_CP);
-- verify that the code works by writing values into the array
for I in My_Chain'Range loop
My_Chain (I).Number := 1000 * I;
end loop;
-- and read them back out using the pointer links
My_C := My_Chain (My_Chain'First)'Access;
while My_C /= null loop
Put (My_C.Number);
Put_Line ("");
My_C := My_C.Next;
end loop;
end Arr;
Instead of passing the array directly, I passed a pointer to the array instead, which Gnat seems happy with. I think what I was trying to do before was being scuppered by the "pass by copy" rules for procedure parameters.

Pascal - doubly linket list

I'm trying compile Doubly linked list but I have one small problem with this part of code :
It's a "insert after"
When I insert data to the list : 1 2 3 4 5 and want insert after number 4 for example number 9, number 9 is inserted like this : 1 9 2 3 4 5. I really dont know where I have wrong code:(
everytime i insert new number to second position..
procedure insertAfter(var L:Plist; n_p:integer);
var novy_za:Plist;
begin
new(novy_za);
novy_za^.info:=n_p;
novy_za^.next:=L^.next;
novy_za^.prev:=L;
L^.next:=novy_za;
if novy_za^.next<>nil
then novy_za^.next^.prev:=novy_za;
end;
procedure call :
begin
writeln;
write('which number insert : '); readln(x);
writeln;
write('insert after : '); readln(Y);
InsertAfter(P,x);
end;
Here's a corrected version of your routine. Your original failed to accept the value you want to insert after as an argument. I made it a function so that it returns the pointer to the new added element if it was able to find the one you wanted to insert after. Otherwise, it returns nil. You call it with a second argument which tells it what value you want to insert after.
function insertAfter(var L: Plist; n_a, n_p: integer): Plist;
var novy_za, cur_za: Plist;
begin
cur_za := L;
novy_za := nil;
{ Traverse the list, looking for value n_a and, if found, insert the new
element n_p after it }
while cur_za <> nil do begin
{ Insert the new element after the one with value n_a }
if cur_za^.info = n_a then begin
new(novy_za);
novy_za^.info := n_p;
novy_za^.next := cur_za^.next;
novy_za^.prev := cur_za;
cur_za^.next := novy_za;
if novy_za^.next <> nil then
novy_za^.next^.prev := novy_za;
break; { Exit the loop }
end;
cur_za := cur_za^.next;
end;
insertAfter := novy_za;
end;
Function call (this assumes P is defined somewhere as a valid Plist, and x and y are defined as info type):
begin
writeln;
write('which number insert : '); readln(x);
writeln;
write('insert after : '); readln(y);
{ Insert the value x after the number y }
if insertAfter(P, y, x) = nil then
writeln('The value ', y, ' was not found')
else
writeln('The value ', x, ' was successfully inserted after ', y);
end;

Oracle: Split many strings into separate words

I'm using Oracle 11g, and I would like to split a column (JobDescription) from the Persons table into separate words.
I.e. If Person A's Job Description is "Professional StackOverflow Contributor", I would like to populate another table with 3 rows containing the 3 words from the Job Description.
From another post here, I managed to get the following which is working for smaller sets of data. But my table contains just less that 500 000 records and the statement has now been running for 2 days and it's still going.
INSERT INTO WORDS (PersonID, Department, Word)
SELECT distinct PersonID, Department, trim(regexp_substr(str, '[^,]+', 1, level))
FROM (SELECT PersonID, Department, trim(Replace(JobDescription, ' ', ',')) str
FROM Persons) t
CONNECT BY instr( str , ',', 1, level - 1) > 0;
Are there another option that might result in quicker results?
For a one-off job, I see no reason not to go procedural. This should be quick enough (250 seconds for a 2.5 million row table on my system). Change the size of the varchar2 variables if your words can be longer than 40 characters.
create or replace procedure tmp_split_job as
TYPE wtype IS TABLE OF NUMBER INDEX BY VARCHAR2(40);
uwords wtype;
w varchar2(40);
i pls_integer;
n pls_integer;
p pls_integer;
cursor c_fetch is select PersonID, Department, JobDescription from Persons where JobDescription is not null;
begin
for v_row in c_fetch loop
n := length(v_row.JobDescription);
i := 1;
while i <= n loop
p := instr(v_row.JobDescription, ' ', i);
if p > 1 then
w := substr(v_row.JobDescription, i, p-i);
i := p + 1;
else
w := substr(v_row.JobDescription, i);
i := n + 1;
end if;
uwords(w) := 1;
end loop;
w := uwords.FIRST;
while w is not null loop
insert into words (PersonID, Department, Word) values (v_row.PersonID, v_row.Department, w);
w := uwords.next(w);
end loop;
uwords.DELETE;
end loop;
end;
/
exec tmp_split_job;
drop procedure tmp_split_job;

get last number part of a String in PL/SQL

In PL/SQL, is there any way to calculate next serial number from another one like 'A1B0010C'. Next serial no will be A1B0011C (+1). My idea is retrieve number part, increase it get return string back.
I can do this in java, but passing more than 1000 elements to Oracle will cause problems in IN clause.
So please help, any suggestion is appreciated.
Try to write some recursive function like this
This Function returns next character. ex: D after C. 0 after Z.
create or replace function get_next_character(ch char)
return char
is
result_ch char(1) := ch;
begin
IF UPPER(ch) = 'Z' or ch = '9' THEN
result_ch := 'A';
ELSE
result_ch := chr(ascii(ch) + 1);
END IF;
return upper(result_ch);
end get_next_character;
and this is your actual function which returns the next serial number
So, it generates 'A1B0010D' if your input is 'A1B0010C'
create or replace function get_next_serial(p_serial IN varchar2) -- PASS THE REQUIRED ARGUMENTS
return varchar2
IS
v_ret_serial varchar2(100);
v_in_serial varchar2(100) := p_serial;
tmp varchar2(100);
last_char char(1);
begin
tmp := v_in_serial;
for i in reverse 1..length(v_in_serial) loop
last_char := substr(tmp, length(tmp));
last_char := get_next_character(last_char);
tmp := substr(v_in_serial, 1, length(tmp)-1);
v_in_serial := substr(v_in_serial, 1, i-1) || last_char || substr(v_in_serial, i+1);
IF last_char <> 'A' then
exit;
END IF;
end loop;
IF last_char = 'A' THEN
v_in_serial:= 'A'||v_in_serial;
END IF;
return UPPER(v_in_serial);
exception
when NO_DATA_FOUND then
return 'AA';
when others then
return null;
end get_next_serial;
you can call this function (get_next_serial('abc')) where ever you want;
select get_next_serial('ALK0989KJ') from dual
You can place these two functions in a package and use at your convenience.
You can achieve it by using the following mix of Regexp_* functions:
SQL> with t1 as(
2 select 'A1B0010C' col from dual
3 )
4 select regexp_replace(col, '[[:digit:]]+'
5 , to_char(to_number(regexp_substr(col, '[[:digit:]]+',1,2) + 1), 'fm0000')
6 , 1
7 , 2
8 ) as Res
9 from t1
10 ;
RES
------------
A1B0011C
UPDATE In response to the comment.
SQL> with t1 as(
2 select 'A1B0010C' col from dual union all
3 select 'A1B0010C2' col from dual union all
4 select 'A1B0012C001' col from dual union all
5 select 'S000001' col from dual
6 )
7 select col
8 , regexp_replace(col
9 , '([[:digit:]]+)([[:alpha:]]+$|$)'
10 , LPad(to_char(to_number(num) + 1), length(num), '0') || '\2'
11 ) as Res
12 from (select col
13 , regexp_substr(col, '([[:digit:]]+)([[:alpha:]]+$|$)', 1, 1, 'i', 1) as num
14 from t1
15 )
16 ;
COL RES
----------- -----------------
A1B0010C A1B0011C
A1B0010C2 A1B0010C3
A1B0012C001 A1B0012C002
S000001 S000002
create or replace function nextSerial(s in varchar2) return varchar2 as
i integer;
done boolean := false;
c char;
newserial varchar2(4000);
begin
newserial := s;
i := length(newserial);
while i>0 and not done loop
c := substr(newserial, i, 1);
if c='9' then
newserial := substr(newserial, 1, i-1) || '0' || substr(newserial, i+1);
elsif c>='0' and c<='8' then
c := chr(ascii(c)+1);
newserial := substr(newserial, 1, i-1) || c || substr(newserial, i+1);
done := true;
end if;
i := i-1;
end loop;
if not done then
newserial := '1' || newserial;
end if;
return newserial;
end;