I need to call a lot of procedures with incrementing names. Is it possible to generate the name of a procedure in a string and then call it using that string? Python has its own exec method which would be ideal but I'd be fine if I could dynamically pick the name of a function.
Edit:
To elaborate on what this is for, I have to write a performance test that repeats procedures of multiple codeunits. Depending on the parameters given, I could have 100 codeunits containing 30 procedures each, so to call all of them I currently have to explicitly call each and every one of them.
startTime := Time();
progressDialog.Update(1,'Proc0A');
for i := 1 to 1000 do begin
Set0_No0.Proc0A(1);
progressDialog.Update(2,i);
end;
timeElapsed := Time() - startTime;
if timeElapsed = 0 then begin
timeElapsed := 1;
end;
baseTime := timeElapsed;
FinishTest(timeElapsed, baseTime, 0, false, false, 1, 1, 1000);
startTime := Time();
progressDialog.Update(1,'Proc0B');
for i := 1 to 1000 do begin
Set0_No0.Proc0B(1);
progressDialog.Update(2,i);
end;
timeElapsed := Time() - startTime;
if timeElapsed = 0 then begin
timeElapsed := 1;
end;
FinishTest(timeElapsed, baseTime, 0, true, false, 1, 1, 1000);
Instead, I'd like it to work like this:
procedure CallMethod(setNo:Integer; codeunitNo:Integer; procedureNo:Integer; type:Code[5]; repetitions:Integer)
begin
for i := 1 to repetitions do begin
Exec('Set%1_No%2.Proc%3%4(1)',setNo,codeunitNo,procedureNo,type);
Set0_No0.Proc0A(1);
progressDialog.Update(2,i);
end;
end;
The feature you require is not available in AL.
There are however a couple of options you could consider:
Using a combination of enums, interfaces and codeunits you can remove a lot of boilerplate code.
Using a script (e.g. PowerShell) you could parse all your source files, find the codeunits and their procedures and then generate a new codeunit that invokes each procedure.
Related
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;
I wrote a code where user filters a source number from the table, the code get the source number and open an Excel file with the same number in a folder, write other data into the file and then save to temp folder. The program worked for some of the Excel files in the folder, but it won't work for the majority of the files. When it finishes running it'll say Exception from HRESULT: 0x800A01A8, the file gets saved into temp folder but the data is not written into it.
https://i.stack.imgur.com/kHtbC.png
I have searched all over Google but it seems like no one had encountered the same issue as I am and I have no idea what this exception means. Below is the code.
ProductionOrder - OnAfterGetRecord()
CLEAR(xlApplication);
CLEAR(xlWorkbooks);
CLEAR(xlWorksheet);
CLEAR(xlshape);
// Open excel
IF CREATE(xlApplication, FALSE, TRUE) THEN BEGIN
xlApplication.SheetsInNewWorkbook := 1;
xlApplication.ScreenUpdating(TRUE);
xlWorkbooks := xlApplication.Workbooks;
END ELSE ERROR('Could not start Excel.');
xlWorkbooks.Open('C:\PROCESS CHECKSHEET\' + ProductionOrder."Source No." + '.xlsx');
xlWorkbook := xlApplication.ActiveWorkbook;
xlSheets := xlWorkbook.Worksheets;
FOR i := 1 TO xlSheets.Count DO BEGIN
xlWorksheet := xlSheets.Item(i);
xlWorksheet.Activate;
xlRange := xlWorksheet.Range(xlsCell(14,7));
xlRange.Value := ProductionOrder."No.";
xlRange := xlWorksheet.Range(xlsCell(14,8));
xlRange.Value := FORMAT(ProductionOrder.Quantity);
xlWorkbook._SaveAs('C:\temp\' + ProductionOrder."Source No.");
xlWorkbook.Close(TRUE);
xlApplication.Quit;
END;
CurrReport.QUIT;
LOCAL xlsCol(col : Integer) : Text
IF col > 26 THEN BEGIN
ColFirst := col DIV 26;
col := col MOD 26;
END
ELSE
ColFirst := 0;
Letters := 'ABCDEFGHIJKLMNOPQRSTUVYWXYZ';
IF ColFirst <> 0 THEN
EXIT (STRSUBSTNO('%1%2',Letters[ColFirst],Letters[col]))
ELSE
EXIT (STRSUBSTNO('%1',Letters[col]));
LOCAL xlsCell(col : Integer;row : Integer) : Text[15]
EXIT (STRSUBSTNO('%1%2',xlsCol(col),row));
Edit:
I have tried to debug. Debugger says error is on line "xlWorksheet := xlSheets.Item(i);". There is only one sheet in the Excel file that I am trying to access. What I don't understand is that it would work on other Excel files, just not on this file that I am currently trying to access.
I also found out that if I copy the content into a new Excel file, then the code would work on the new Excel file. Could it also be a problem of Excel version?
I solved this on my own.
Because the Excel files were given to me by my HOD at work (and these Excel files were written by other department), I did not know that there was a hidden sheet inside those Excel files, hence it output the exception error because my code was only indexing Excel file that only has one sheet. I changed my index to 2 and it worked. Note to self, next time gotta check for hidden sheets.
I don't know if there is a for each sheet equivalent for C/AL programming because I'm new to the language, so I used 2 for loops to solve the problem.
CLEAR(xlApplication);
CLEAR(xlWorkbooks);
CLEAR(xlWorksheet);
IF CREATE(xlApplication, FALSE, TRUE) THEN BEGIN
xlApplication.SheetsInNewWorkbook := 1;
xlApplication.ScreenUpdating(TRUE);
xlWorkbooks := xlApplication.Workbooks;
END ELSE ERROR('Could not start Excel.');
xlWorkbooks.Open('C:\13. PROCESS CHECKSHEET\' + ProductionOrder."Source No." + '.xlsx');
xlWorkbook := xlApplication.ActiveWorkbook;
xlSheets := xlWorkbook.Worksheets;
FOR I := 1 TO xlSheets.Count DO BEGIN
xlWorksheet := xlSheets.Item(I);
xlWorksheet.Activate;
xlRange := xlWorksheet.Range(xlsCell(14,7));
xlRange.Value := ProductionOrder."No.";
xlRange := xlWorksheet.Range(xlsCell(14,8));
xlRange.Value := FORMAT(ProductionOrder.Quantity);
END;
FOR I := 2 TO xlSheets.Count DO BEGIN
xlWorksheet := xlSheets.Item(I);
xlWorksheet.Activate;
IF (FORMAT(xlWorksheet.Name) = 'PROCESS CHECKSHEET') OR (FORMAT(xlWorksheet.Name) = 'Process Checksheet') THEN BEGIN
xlRange := xlWorksheet.Range(xlsCell(14,7));
xlRange.Value := ProductionOrder."No.";
xlRange := xlWorksheet.Range(xlsCell(14,8));
xlRange.Value := FORMAT(ProductionOrder.Quantity);
END;
END;
xlWorkbook.SaveAs('C:\13. PROCESS CHECKSHEET\temp\' + ProductionOrder."Source No.");
MESSAGE('Success');
xlWorkbook.Close(TRUE);
xlApplication.Quit;
CurrReport.QUIT;
i am working on a scenario where need to run a particular code block for 5 mins.
I tried various options but no luck till now.
Could you please guide me.
Attaching the half-baked block:
DO
$do$
DECLARE
i int;
startTime TIMESTAMP;
endTime TIMESTAMP;
BEGIN
i := 1;
startTime := now();
endTime := now() + (1 * interval '5 minute');
RAISE NOTICE 'City Name:(%)(%)',statement_timestamp(),now() + (1 * interval '5 minute');
WHILE statement_timestamp() < endTime LOOP
RAISE NOTICE 'City Name:(%)(%)(%)',endTime,now(), statement_timestamp();
-- example:
--create table if not exists test12345(id int,start_dt TIMESTAMP,end_dt TIMESTAMP);
insert into test12345
select i,startTime,endTime;
--RAISE NOTICE startTime;
RAISE NOTICE 'City Name:(%)', statement_timestamp();
--startTime := statement_timestamp();
END LOOP;
END;
$do$;
thanks in advance.
In APEX I created a page and here a region. The region reports a classic report. I have selected "PL/SQL function body that returns an SQL query" as the source. Here I have also deposited the following code
declare
l_date_string varchar2(32000);
l_date_diff number(4);
x NUMBER := 0;
l_script varchar2(32000);
l_script_pivot varchar2(32000);
new_date varchar(256);
begin
l_date_diff:=TO_DATE(:P2066_DATE_UNTIL, 'dd.mm.yyyy') - TO_DATE(:P2066_DATE_FROM, 'dd.mm.yyyy') ;
While X < l_date_diff+1 Loop
new_date := to_char(TO_DATE(:P2066_DATE_FROM, 'dd.mm.yyyy')+X,'dd.mm.yyyy');
l_date_string := l_date_string || ',''' || new_date || '''';
X := X + 1;
End Loop;
l_date_string := substr(l_date_string,2);
l_script := 'Select * from
(Select
pkey,
to_char(createdformat,''dd.mm.yyyy'') business_date,
regexp_substr(statistics, ''business_\w*'') business_statistics
from
gss.business_data
where
statistics like ''%business_%''
and createdformat between :P2066_DATE_FROM and :P2066_DATE_UNTIL
) ';
l_script_pivot := l_script || ' pivot(
count(pkey)
for business_date
in ('||l_date_string||'))';
sys.htp.p('<li>' || l_script_pivot || ' </li>' );
return l_script_pivot;
end;
The first column, Business_Statistis, is always displayed, the date in the subsequent columns should be displayed dynamically - depending on the selection of the period.
I also spent the respective code according to the time period selection and knew it successfully as a classic report with an SQL query. That's working.
How can I now dynamically update the classic report with a PL / SQL action. That means that depending on the result, the Classic Report is always displayed?
I once selected to use Generic Number of Columns in parallel, with a number of 365. Then he shows me the columns, but the column heading is not the date but Col2, Col3, Col4 and so on
There are a few ways to solve this issue.
Option 1: If you're happy to use "Generic Number of Columns", then select PL/SQL as your "Headings Type". Then define your headings with PL/SQL e.g.
DECLARE
v_heading varchar2(2000);
l_date_diff number(4);
BEGIN
v_headings := 'business_statistics';
l_date_diff:=TO_DATE(:P2066_DATE_UNTIL, 'dd.mm.yyyy') - TO_DATE(:P2066_DATE_FROM, 'dd.mm.yyyy') ;
WHILE X < l_date_diff+1 LOOP
new_date := to_char(TO_DATE(:P2066_DATE_FROM, 'dd.mm.yyyy')+X,'dd.mm.yyyy');
v_headings := v_headings ||':' || new_date ;
X := X + 1;
END LOOP;
END;
Option 2: I assume you are using "PL/SQL function body that returns an SQL query" to get around the "in clause" of the pivot? Another way to fix this is to define your pivot clause as a page item. E.g. P2066_PIVOT_CLAUSE. You can then set the pivot clause with a before header process. Then you can use standard SQL in your report.
i.e.
SELECT * FROM
(SELECT pkey,
to_char(createdformat,'dd.mm.yyyy') business_date,
regexp_substr(statistics, 'business_\w*') business_statistics
FROM
gss.business_data
WHERE
statistics like '%business_%'
AND createdformat BETWEEN :P2066_DATE_FROM AND :P2066_DATE_UNTIL)
PIVOT (COUNT(pkey)FOR business_date in (&P2066_PIVOT_CLAUSE.))
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;