Not able to create a matrix in PL/SQL using nested tables - sql

I was trying to create a matrix in PL/SQL using nested tables as such:
firstly, I create a table type that is able to store varchars (that in my case is name lista_principala)
secondly, using the previously declare table type lista_principala I try to create a table type that contains items of type lista_principala.
I don't think that I'm wrong about my logic, but my PL/SQL implementation looks something like this:
set serveroutput on;
DECLARE
TYPE lista_principala_tip IS TABLE OF varchar2(30);
TYPE lista_liste_tip IS TABLE OF lista_principala_tip;
lista lista_liste_tip;
BEGIN
lista := lista_liste_tip();
lista.extend(20);
lista(1) := lista_principala_tip('Gorila', 'Babuin', 'Urangutan', 'Cimpanzeu', 'Gibon');
lista(2) := lista_principala_tip('Labrador', 'Bulldog', 'Bichon', 'Ciobanesc German');
lista(3) := lista_principala_tip('British Shorthair', 'Siamese', 'Scottish Fold', 'Chartreux');
for i in lista.first..lista.last loop
for j in lista(i).first..lista(i).last loop
DBMS_OUTPUT.PUT_LINE(i||' - '||lista(i)(j));
end loop;
end loop;
END;
And the problem that I have is that when I try to run this script I get the following errors:
Error report -
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 12

You use lista.extend(20); to create a list with 20 items and these will all be initialised to NULL.
Then you set the values for the first 3 elements of the collection.
Then you loop through all 20 items and try to loop through the sub-list in each element; however, after the first 3, there is no sub-list contained in the element as the element is NULL.
Either:
Just lista.EXTEND(3); as you only want 3 elements to the array; or
Add a check if the list element is NULL and then skip looping through the sub-list if it is.
The second option can be implemented as:
DECLARE
TYPE lista_principala_tip IS TABLE OF varchar2(30);
TYPE lista_liste_tip IS TABLE OF lista_principala_tip;
lista lista_liste_tip;
BEGIN
lista := lista_liste_tip();
lista.extend(20);
lista(1) := lista_principala_tip('Gorila', 'Babuin', 'Urangutan', 'Cimpanzeu', 'Gibon');
lista(2) := lista_principala_tip('Labrador', 'Bulldog', 'Bichon', 'Ciobanesc German');
lista(3) := lista_principala_tip('British Shorthair', 'Siamese', 'Scottish Fold', 'Chartreux');
for i in lista.first..lista.last loop
IF lista(i) IS NOT NULL THEN
for j in lista(i).first..lista(i).last loop
DBMS_OUTPUT.PUT_LINE(i||' - '||lista(i)(j));
end loop;
END IF;
end loop;
END;
/
db<>fiddle here

Related

Error while writing to array plsql how to fix? Extend doesn't work also

so I am trying to write to an array in PL/SQL, and I always get the subscript outside of limit error. I've seen similar posts and implemented everything based on those answers, I can't seem to find what I'm doing wrong. The line giving the error is "arr_quartosLivres(counter) := q.id;" I've tried to extend the array and it still doesn't work, however, either way, the look only runs 21 times (because there are only 21 values in the table quarto) so it shouldn't even need to be extended. Any help would be highly appreciated! Thank you
SET SERVEROUTPUT ON;
DECLARE
p_idReserva reserva.id%type := 408;
v_dataEntradaReserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux IS varray(21) of quarto.id%type;
arr_quartosLivres arr_aux := arr_aux();
BEGIN
SELECT data_entrada INTO v_dataEntradaReserva FROM reserva WHERE id = p_idreserva;
FOR q IN (SELECT * FROM quarto)
LOOP
BEGIN
IF isQuartoIndisponivel(q.id, v_dataEntradaReserva)
THEN DBMS_OUTPUT.PUT_LINE('nao disponivel' || counter);
arr_quartosLivres(counter) := q.id;
ELSE DBMS_OUTPUT.PUT_LINE('disponivel' || counter);
END IF;
counter := counter + 1;
END;
END LOOP;
END;
The index values for varray begin with 1. Your logic is trying to use index value 0. Thus index out of range. BTW extend does not apply to varray, when declared a varray has a fixed size. You have 3 solutions: initialize counter to 1 instead of 0, or move incrementing it prior to its use as an index. Since as it stands you increment every time through the loop, even when the IF condition returns false and you do not use the counter as an index, leaving a NULL value in the array.But you use counter for 2 different purposes: Counting rows processed and index into the array. Since the row value may not be put into the array then your 3rd option is to introduce another variable for the index. Further there is no need for the BEGIN ... End block in the loop.
declare
p_idreserva reserva.id%type := 408;
v_dataentradareserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux is varray(21) of quarto.id%type;
arr_quartoslivres arr_aux := arr_aux();
varray_index integer := 1 ; -- index varaibal for varray.
begin
select data_entrada into v_dataentradareserva from reserva where id = p_idreserva;
for q in (select * from quarto)
loop
if isquartoindisponivel(q.id, v_dataentradareserva)
then
dbms_output.put_line('nao disponivel' || counter || ' at index ' || varray_index);
arr_quartoslivres(varray_index) := q.id;
varray_index := varray_index + 1;
else
dbms_output.put_line('disponivel' || counter);
end if;
counter := counter + 1;
end loop;
end;

How to get insert values from a table into an array using SQL?

with dmHospital do
begin
qryHospital.SQL.Clear;
qryHospital.SQL.Add('SELECT * FROM Patients ') ;
qryHospital.SQL.Add('WHERE DoctorID = :DoctorID');
qryHospital.Parameters.ParamByName('DoctorID').Value := StrToInt(sID);
qryHospital.Open;
iCount := qryHospital.RecordCount
end;
This code displays the values I want to put into the array. However I'm not sure how to cycle through each record and to get each value from the record into the correct array. For example: I want the names from 'PatientName' and the surnames from 'PatientSurname'.
iCount is the array size.
You must loop from TDataset using While.
Some sample code:
...
var
fieldCod:TField;
Str1:String;
i, Cod:Integer;
begin
...
qryHospital.Open;
iCount := qryHospital.RecordCount;
// Create pointer to field
fieldCod := qryHospital.FieldByName('PatientCode');
// loop the recordset (while not arrive at end)
While (not qryHospital.eof) do begin
// Different modes to access table fields content
Str1 := qryHospital.FieldByName('PatientName').AsString;
i := qryHospital.Fields[1].AsInteger;
Cod := fieldCod.AsInteger;
// Add the values to your array
//...
// Next Record
qryHospital.Next;
end;
NOTE: For better performance, don't use FieldByName inside the loop (for big number of records). You can use Fields[index] or create a variable out of the loop and reference to fields.

How to bulk copy the values from a TSQLQuery record to a TdxMemData record?

Just to make it clear, I do not want to copy the entire TSQLQuery into the TdxMemData, as I would use memds.CopyFromDataSet(qry) for that.
I am interating through each record from the TSQLQuery, and I may or may not be adding a record(s) to the TdxMemData. Generally the record in memds matches that in qry, but sometimes the values are altered and sometimes additional records are added to memds. My example did not make this clear since all it seemed to do was copy over each record.
So given an active record in the TSQLQuery, I want to copy over the values into an active editable record in the TdxMemData.
The following code works in so far as it creates a copy of the record:
qry := TSQLQuery.Create(nil);
memds := TdxMemData.Create(nil);
try
qry.SQLConnection := cn;
qry.Text := 'SELECT Field1, Field2, Field3 FROM Table1';
qry.Open
memds.CreateFieldsFromDataSet(qry);
memds.Open;
while not qry.Eof do
begin
if {some condition} then
begin
memds.Append;
for i := 0 to qry.FieldCount-1 do
memds.Fields[i+1].Value := qry.Fields[i].Value; //First field is RecID
//Do something with the current memds record
end
else if {some other condition} then
begin
memds.Append;
//change values
memds.Append;
//change values
memds.Append;
//change values
end
else if {a third condition} then
; //Skip any work on memds
qry.next;
end;
qry.Close;
//Do something with memds
memds.Close;
finally
memds.Free;
qry.Free;
end;
Is there a better way? I had looked at AppendRecord but creating the array of TVarRec doesn't seem to be straightforward.
EDIT:
Let's use these examples with very simplified criteria. Note that the actual conditions that determine how many records to append and the changes to the field values in the destination are complex and not in any database.
Method 1:
While not tblSource.Eof do
Begin
If (iCondition = 1) Then
Begin
// Add one record
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bSomethingCondition then
tblDestination.FieldByName('Field4').Value := 'Something';
End
Else If (iCondition = 2) Then
Begin
// Add two records
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bAnotherThingCondition then
tblDestination.FieldByName('Field4').Value := 'Another thing';
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bSomethingElseCondition then
tblDestination.FieldByName('Field4').Value := 'Something else';
End
Else If (iCondition = 0) Then
Begin
// Add no records
End;
tblSource.Next;
End;
Since the number of fields in the source and destination tables can vary, hard-coding field names as in Method 1 is not suitable.
Method 2:
While not tblSource.Eof do
Begin
If (iCondition = 1) Then
Begin
// Add one record
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
End
Else If (iCondition = 2) Then
Begin
// Add two records
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bAnotherThingCondition then
tblDestination.Fields(iAnotherThingConditionFieldINdex).Value := 'Another thing';
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingElseCondition then
tblDestination.Fields(iSomethingElseConditionFieldIndex).Value := 'Something else';
End
Else If (iCondition = 0) Then
Begin
// Add no records
End;
tblSource.Next;
End;
While method 2 above does work, and is the way it is currently done, this question is whether there is a way to pass the variant array of field values from tblSource to tblDestination using AppendRecord.
Instead of this:
// Add one record
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
Do this:
tblDestination.AppendRecord({tblSource fields var array);
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
Of course, it might be that there is no answer, and that the method I currently employ is the best solution.
Try the following
Query1.Open();
dxMemData1.AddFieldsFromDataSet(Query1);
dxMemData1.Open;
dxMemData1.LoadFromDataSet(Query1);
Creating an array of TVarRec isn't difficult, and AppendRecord may indeed help. The following code adds a record to a TClientDataSet (named CDS for brevity) that has 4 fields of type string, float, boolean, and string in that order:
CDS.AppendRecord(['Smith', 123.45, False, 'Test text']);
Note that you have to create a value for every single field (column) in the dataset, in the order that they exist in the FieldDefs collection, or you'll get an exception.
(Of course, the real question is why you're returning extra rows and iterating through them, instead of testing the conditions in your SQL statement WHERE clause and only returning the rows you actually need. This can almost always be done using parameters.)
If you don't want to use AddFieldsFromDataSet -> LoadFromDataSet , and want to load fields and records manually, you also can do it. You can create fields programmatically and append records by iterations. Look example:
MD.Fields.Clear;
MD.FieldDefs.Clear;
MD.Close;
with MD.FieldDefs.AddFieldDef do
begin
Name := 'UserID';
DataType := TFieldType.ftInteger;
CreateField(MD);
Name := 'GridName';
DataType := TFieldType.ftString;
Size := 255;
CreateField(MD);
Name := 'TemplateGrid';
DataType := TFieldType.ftBlob;
CreateField(MD);
end;
MD.Close;
MD.Open;
MD.Append;
MD.FieldByName('UserID').AsInteger := 1;
MD.FieldByName('GridName').AsString := Self.Name
TBlobField(MD.FieldByName('TemplateGrid')).LoadFromStream(LStream);
MD.Post;
You can easily modify it to load fields with names and types from your dataset as they are.

How to add only checked rows in APEX tabular forms to another table?

I've created a tabular form from a view - so only the checked items be added to a table. So far I can only insert all the records not individually selected rows.
This is the code I used:
DECLARE
v_insertcount NUMBER := 0;
BEGIN
FOR i IN 1 .. apex_application.g_f02.COUNT
LOOP
IF apex_application.g_f01 (i) IS NOT NULL THEN -- this is the checkbox
insert into my_table (pme_id, MYREF, STAC, START_DATE)
values ( SEQ_PME.NEXTVAL,
:P5_MYREF
apex_application.g_f02(i),
apex_application.g_f03(i)
)
v_insertcount := v_insertcount + 1;
END IF;
END LOOP;
END;
First you have to remember that only checked checkboxes are submitted. So you cannot test for NULL in order to identify unchecked boxes. That are simply not in the array.
So, as the checkbox array size is lower or equal to the other fields array size, you cannot use index to find corresponding values of the same row.
There are some clever tricks to overcome that limitations. However, for simple cases, a solution is simply to use a look-up function to map id to index.
FUNCTION array_search(value IN VARCHAR2, arr IN apex_application_global.vc_arr2)
RETURN PLS_INTEGER
IS
BEGIN
FOR i IN 1 .. arr.COUNT
LOOP
IF arr(i) = value THEN
RETURN i;
END IF;
END LOOP;
return 0;
END;
This is a linear search. So the performances are not very good. Anyway for form processing it is probably sufficient. Given that function, you can now write:
Region source
SELECT APEX_ITEM.CHECKBOX(1,pme_id) " ",
...
on-submit process
FOR i IN 1 .. apex_application.g_f01.COUNT -- your checkboxes
LOOP
-- identify the "row index"
idx := array_search(apex_application.g_f01(i), apex_application.g_f02);
-- ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
-- checkboxes array id array
-- assuming now idx > 0
-- (in production code, should check and log in case of `0`)
-- do whatever you want, using `idx` as index
insert into my_table
(pme_id,
MYREF,
STAC,
START_DATE
)
values (
SEQ_PME.NEXTVAL,
:P5_MYREF
apex_application.g_f02 (idx),
apex_application.g_f03 (idx)
);
END LOOP;

What is << some text >> in oracle

I found this character while reading some blog of pl sql << some text >> .
I found this character from following blog http://www.oracle-base.com/articles/8i/collections-8i.php
As others have said, <<some_text>> is a label named "some_text". Labels aren't often used in PL/SQL but can be helpful in a variety of contexts.
As an example, let's say you have several nested loops, execution has reached the very inner-most level, and the code needs to exit from all the nested loops and continue after the outer-most one. Here a label can be used in the following fashion:
<<outer_most_loop>>
LOOP
...
<<next_inner_loop>>
LOOP
...
<<inner_most_loop>>
LOOP
...
IF something <> something_else THEN
EXIT outer_most_loop;
END IF;
...
END LOOP inner_most_loop;
...
END LOOP next_inner_loop;
...
END LOOP outer_most_loop;
-- Execution continues here after EXIT outer_most_loop;
something := something_else;
...
Next, let's say that you've got some code with nested blocks, each of which declares a variable of the same name, so that you need to instruct the compiler about which of the same-named variables you intend to use. In this case you could use a label like this:
<<outer>>
DECLARE
nNumber NUMBER := 1;
BEGIN
<<inner>>
DECLARE
nNumber NUMBER := 2;
BEGIN
DBMS_OUTPUT.PUT_LINE('outer.nNumber=' || outer.nNumber);
DBMS_OUTPUT.PUT_LINE('inner.nNumber=' || inner.nNumber);
END inner;
END outer;
Labels can also be useful if you insist on giving a variable the same name as a column in a table. As an example, let's say that you have a table named PEOPLE with a non-nullable column named LASTNAME and you want to delete everyone with LASTNAME = 'JARVIS'. The following code:
DECLARE
lastname VARCHAR2(100) := 'JARVIS';
BEGIN
DELETE FROM PEOPLE
WHERE LASTNAME = lastname;
END;
will not do what you intended - instead, it will delete every row in the PEOPLE table. This occurs because in the case of potentially ambiguous names, PL/SQL will choose to use the column in the table instead of the local variable or parameter; thus, the above is interpreted as
DECLARE
lastname VARCHAR2(100) := 'JARVIS';
BEGIN
DELETE FROM PEOPLE p
WHERE p.LASTNAME = p.lastname;
END;
and boom! Every row in the table goes bye-bye. :-) A label can be used to qualify the variable name as follows:
<<outer>>
DECLARE
lastname VARCHAR2(100) := 'JARVIS';
BEGIN
DELETE FROM PEOPLE p
WHERE p.LASTNAME = outer.lastname;
END;
Execute this and only those people with LASTNAME = 'JARVIS' will vanish.
And yes - as someone else said, you can GOTO a label:
FUNCTION SOME_FUNC RETURN NUMBER
IS
SOMETHING NUMBER := 1;
SOMETHING_ELSE NUMBER := 42;
BEGIN
IF SOMETHING <> SOMETHING_ELSE THEN
GOTO HECK;
END IF;
RETURN 0;
<<HECK>>
RETURN -1;
END;
(Ewwwww! Code like that just feels so wrong..!)
Share and enjoy.
It's often used to label loops, cursors, etc.
You can use that label in goto statements. Else, it is just 'comment'.
Sample from Oracle:
DECLARE
p VARCHAR2(30);
n PLS_INTEGER := 37; -- test any integer > 2 for prime
BEGIN
FOR j in 2..ROUND(SQRT(n)) LOOP
IF n MOD j = 0 THEN -- test for prime
p := ' is not a prime number'; -- not a prime number
GOTO print_now; -- << here is the GOTO
END IF;
END LOOP;
p := ' is a prime number';
<<print_now>> -- << and it executes this
DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p);
END;
/
It is a label, a subgroup of comments in the plsql syntax.
http://ss64.com/oraplsql/operators.html
Its is a label delimeter
<< label delimiter (begin)
label delimiter (end) >>
http://docs.oracle.com/cd/B10501_01/appdev.920/a96624/02_funds.htm