Query from array - arrays

I am getting error while trying to select values from an array, like following code
declare result CLOB;
myarray selected_pkg.num_array := selected_pkg.num_array();
begin
myarray.extend(3);
myarray(1) := 1; myarray(2) := 5; myarray(3) := 9;
EXECUTE IMMEDIATE 'select column_value from table (cast(myarray AS selected_pkg.num_array))';
COMMIT;
end;
ORA-00904: "MYARRAY": invalid identifier
Please suggest.
Thanks, Alan

First off, there doesn't appear to be any reason to use dynamic SQL here.
Second, if you want to run a SELECT statement, you need to do something with the results. You'd either need a cursor FOR loop or you'd need to BULK COLLECT the results into a different collection or otherwise do something with the results.
Third, if you want to use a collection in SQL, that collection must be defined in SQL not in PL/SQL.
Something like this will work (I'm not sure if that's what you want to do with the results)
SQL> create type num_arr is table of number;
2 /
Type created.
SQL> declare
2 l_nums num_arr := num_arr( 1, 2, 3, 7 );
3 begin
4 for i in (select column_value from table( l_nums ))
5 loop
6 dbms_output.put_line( i.column_value );
7 end loop;
8 end;
9 /
1
2
3
7
PL/SQL procedure successfully completed.

execute immediate is not need at this point.
Use fetch or loop cursors in proc.

Related

How to update multiple rows with values of two arrays in Postgres?

I need to update a set of records in the x table with values stored in arrays. Anyone know how I could do it?
IDS_NEW_SPONSORS INTEGER[] := ARRAY[]::INTEGER[]; --possible values [1,2,3,4,5,6]
IDS_DEBTORS INTEGER[] := ARRAY[]::INTEGER[]; --possible values [20,21,22,23,24,25]
-- the arrays will be the same size--
UPDATE x
SET id_sponsor = [IDS_NEW_SPONSORS],-- HOW ADD
id_sponsor_historial = [IDS_DEBTORS], -- HOW ADD
id_patron = 5 -- ALWAYS SAME
WHERE id_sponsor = ANY(IDS_DEBTORS); -- this line working!!! loop each row
I'm working with postgres 11...
After update. the table would look like this:
id_sponsor | id_sponsor_historial | id_patron
1 20 5
2 21 5
3 22 5
4 23 5
5 24 5
6 25 5
The next a little tricky query do this work:
(The trick is match value in new values array by position in old values array)
UPDATE sponsors
SET
id_sponsor_historial = id_sponsor, -- save previous value as historical
id_sponsor = (array[1,2,3,4,5,6])[array_position(array[20,21,22,23,24,25], id_sponsor)],-- update new value
id_patron = 5
WHERE id_sponsor = ANY(array[20,21,22,23,24,25]); -- find rows
I rest the integers array for example. In your case it should be changed to variables. The example works only if new and old values arrays have equals lengths.
You can use a function to do that. Here is an example:
create table x(
id_sponsor int primary key,
id_sponsor_historial int,
id_patron int);
CREATE OR REPLACE FUNCTION update_x(
integer,
integer[],
integer[])
RETURNS integer AS
$BODY$
DECLARE
in_pat ALIAS FOR $1;
in_sp_arr ALIAS FOR $2;
in_sp_hi_arr ALIAS FOR $3;
BEGIN
IF in_sp_arr ISNULL
THEN
RETURN 0;
END IF;
IF array_lower(in_sp_arr,1) ISNULL
OR array_upper(in_sp_arr,1) ISNULL
THEN
RETURN 0;
END IF;
FOR index IN array_lower(in_sp_arr,1) .. array_upper(in_sp_arr,1) LOOP
BEGIN
INSERT INTO x(id_sponsor, id_sponsor_historial, id_patron )
VALUES ( in_sp_arr[index], in_sp_hi_arr[index], in_pat);
--EXCEPTION WHEN unique_violation THEN
--do nothing ?
END;
END LOOP;
RETURN 1;
END;
$BODY$
LANGUAGE plpgsql ;
select update_x(5, ARRAY[1,2,3,4,5,6],ARRAY[20,21,22,23,24,25]);
This would probably need more error checking: It assumes for example, that the arrays are of the same size, have the same bounds and all the elements are defined.
What I was trying to do is update the records as follows
A: Replacing the old sponsors (old_sponsor) with the new ones (new_sponsor)
B: Updating the historical_sponsor_id with the values ​​of old_sponsor
C: Adding all of them the same id_patron
I could do it this way based on the #clamp response..
DECLARE
new_sponsor INTEGER[] := array[2300,2034,2032];
old_sponsor INTEGER[] := array[1500,1501,1502];
BEGIN
FOR i IN array_lower(new_sponsor, 1) .. array_upper(new_sponsor, 1)
LOOP
UPDATE x SET id_sponsor = new_sponsor[i],
id_sponsor_historial = old_sponsor[i], id_patron = 5
WHERE id_sponsor = old_sponsor[i];
END LOOP;
END
I would like to know if it is useful for processing more than 10,000 records or if there is a better way to do it efficiently.
The most efficient way to achieve this would be using UNNEST:
UPDATE sponsors
SET
-- save previous value as historical
id_sponsor_historial = bulk_query.id_sponsor,
-- update new value
id_sponsor = bulk_query.updated_id_sponsor,
-- set another field
id_patron = 5
FROM
(
SELECT * FROM UNNEST(
array[20,21,22,23,24,25]::INT[],
array[1,2,3,4,5,6]:INT[]
) AS t(id_sponsor, updated_id_sponsor)
) AS bulk_query
WHERE
sponsors.id_sponsor=bulk_query.id_sponsor
This effectively creates a temporary table from the two arrays, and then uses that to perform the update.
I've written more about using UNNEST with Postgres in this blog post: https://www.atdatabases.org/blog/2022/01/21/optimizing-postgres-using-unnest

How to use Oracle DB sequences without losing the next sequence number in case of roll-back

Question
How to use Oracle DB sequences without losing the next sequence number in case of roll-back?
Facts collected
1 - In Oracle, we can create a sequence and use two main calls (NEXTVAL) to get the next sequence value and (CURRVAL) to get the current sequence value.
2 - When we call (NEXTVAL) will always get the next number and we will lose it if there is a rollback. In other words, Oracle sequence does not care if there is a roll-back or commit; whenever you are calling it, it will give a new number.
Possible answers I found so far
1 - I was thinking to create a simple table with one column of type (NUMBER) to service this purpose.
Simply pick the value and use it. If operation succeeded I will increment column value. Otherwise, I will keep it as it is for the next application call.
2 - Another way I found here (How do I reset a sequence in Oracle?) is to use (ALTER SEQUENCE) like the following if I want to go one step back.
That is, if the sequence is at 101, I can set it to 100 via
ALTER SEQUENCE serial INCREMENT BY -1;
SELECT serial.NEXTVAL FROM dual;
ALTER SEQUENCE serial INCREMENT BY 1;
Conclusion
Are any of the suggested solutions is good? Is their any better approach?
From my point of view, you should use a sequence and stop worrying about gaps.
From your point of view, I'd say that altering the sequence is worse than having a table. Note that access to that table must be restricted to a single user, otherwise you'll get duplicate values if two (or more) users access it simultaneously.
Here's a sample code; have a look, use/adjust it if you want.
SQL> create table broj (redni_br number not null);
Table created.
SQL>
SQL> create or replace function f_get_broj
2 return number
3 is
4 pragma autonomous_transaction;
5 l_redni_br broj.redni_br%type;
6 begin
7 select b.redni_br + 1
8 into l_redni_br
9 from broj b
10 for update of b.redni_br;
11
12 update broj b
13 set b.redni_br = l_redni_br;
14
15 commit;
16 return (l_redni_br);
17 exception
18 when no_data_found
19 then
20 lock table broj in exclusive mode;
21
22 insert into broj (redni_br)
23 values (1);
24
25 commit;
26 return (1);
27 end f_get_broj;
28 /
Function created.
SQL> select f_get_broj from dual;
F_GET_BROJ
----------
1
SQL> select f_get_broj from dual;
F_GET_BROJ
----------
2
SQL>
You can create a sequence table.
CREATE TABLE SEQUENCE_TABLE
(SEQUENCE_ID NUMBER,
SEQUENCE_NAME VARCHAR2(30 BYTE),
LAST_SEQ_NO NUMBER);
And in your PL/SQL block, you can get the sequence using below lines of code,
declare
CURSOR c1 IS
SELECT last_seq_no
FROM sequence_table
WHERE sequence_id = 21
FOR UPDATE NOWAIT;
v_last_seq_no NUMBER;
v_new_seq_no NUMBER;
resource_busy EXCEPTION;
PRAGMA EXCEPTION_INIT(resource_busy, -54);
BEGIN
LOOP
BEGIN
OPEN c1;
FETCH c1 INTO v_last_seq_no;
CLOSE c1;
v_new_seq_no := v_last_seq_no+1;
EXIT;
EXCEPTION
WHEN resource_busy THEN
NULL;
--or something you want to happen
END;
END LOOP;
--after the this line, you can put an update to the sequence table and be sure to commit/rollback at the end of the pl/sql block;
END;
/
ROLLBACK;
--or
COMMIT;
Try to run the PL/SQL code above in two oracle sessions to understand. Basically, if Oracle DB session 1 will run the code, the record queried from the cursor will be lock. So, if other session will run the same code, that session will wait for session 1's rollback/commit to finish running the code. Through this, two sessions won't have the same sequence_no and you have a choice not to update the sequence if you issue a rollback for some reasons.

How i can initialize a array with record items in pl/sql?

I'm relatively new to pl/sql and i'm trying to make a list with records objects but i dont know how to initialize for each item of the list both fields from record item. For example : in procedure "new item" how i can initialize example(1) ? with example(1).id_std := integer and example(1).procent := integer ? Thanks!
This is how my code looks like :
set serveroutput on;
CREATE OR REPLACE PACKAGE newExercise IS
TYPE item IS RECORD(
id_std INTEGER,
procent INTEGER
);
TYPE tabel IS VARRAY(5) OF item;
PROCEDURE newItem (example tabel);
example2 tabel := tabel();
end newExercise;
/
CREATE OR REPLACE PACKAGE BODY newExercise IS
PROCEDURE newItem (example tabel) IS
BEGIN
FOR i IN 1..example.LIMIT LOOP
DBMS_OUTPUT.PUT_LINE(example(i));
end loop;
end newItem;
end newExercise;
/
Record types are for storing the results of queries. So you could do this:
declare
recs newExercise.tabel;
begin
select level, level * 0.25
bulk collect into recs
from dual
connect by level <= 5;
newExercise.newItem (recs);
end;
/
Note that VARRAY is not a suitable collection type for this purpose, because it's not always possible to predict how many rows a query will return. It's better to use
TYPE tabel IS table OF item;
When you refer to the record you usually have to specify specific fields. This populates the records with calculated values; to be able to do that I've had to changed the procedure argument from the default IN direction to IN OUT, both in the specification:
CREATE OR REPLACE PACKAGE newExercise IS
TYPE item IS RECORD(
id_std INTEGER,
procent INTEGER
);
TYPE tabel IS VARRAY(5) OF item;
PROCEDURE newItem (example IN OUT tabel);
-- ^^^^^^ make in/out to be updateable
-- example2 tabel := tabel(); -- not used
END newExercise;
/
and in the body:
CREATE OR REPLACE PACKAGE BODY newExercise IS
PROCEDURE newItem (example IN OUT tabel) IS
-- ^^^^^^ make in/out to be updateable
BEGIN
FOR i IN 1..example.LIMIT LOOP
-- extend collection to create new record
example.extend();
-- assign values to record fields
example(i).id_std := i;
example(i).procent := 100 * (1/i);
END LOOP;
END newItem;
END newExercise;
/
The LIMIT is five, from the definition, but the varray instance is initially empty (from tabel()). For population you can loop from 1 to that limit of five, but you have to extend() the collection to actually create the record in that position. Records are created with all fields set to null by default. You can then assign values to the fields of each record. (I've just made something up, obviously).
You can then test that with an anonymous block:
declare
example newExercise.tabel := newExercise.tabel();
begin
-- call procedure
newExercise.newItem(example);
-- display contents for debuggibg
FOR i IN 1..example.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Item ' || i
|| ' id_std: ' || example(i).id_std
-- ^^^^^^^ refer to field
|| ' procent: ' || example(i).procent);
-- ^^^^^^^ refer to field
END LOOP;
end;
/
Item 1 id_std: 1 procent: 100
Item 2 id_std: 2 procent: 50
Item 3 id_std: 3 procent: 33
Item 4 id_std: 4 procent: 25
Item 5 id_std: 5 procent: 20
PL/SQL procedure successfully completed.
I've put the original loop to display the contents of the array in that block, as you wouldn't generally have that as part of a procedure. You could still use LIMIT for that loop, but COUNT is safer in case the procedure doesn't fully populate it.
You can also extend once before the loop:
PROCEDURE newItem (example IN OUT tabel) IS
BEGIN
-- extend collection to create all new records
example.extend(example.LIMIT);
FOR i IN 1..example.LIMIT LOOP
example(i).id_std := i;
example(i).procent := 100 * (1/i);
END LOOP;
END newItem;
If you already know the values you want to assign - and they aren't coming from a table, in which case you'd use APC's approach - you can just assign to the last created record; this is a rather contrived example:
PROCEDURE newItem (example IN OUT tabel) IS
BEGIN
example.extend(); -- first record
example(example.LAST).id_std := 1;
example(example.LAST).procent := 7;
example.extend(); -- second record, left with null fields
example.extend(); -- third record
example(example.LAST).id_std := 3;
example(example.LAST).procent := 21;
example.extend(); -- fourth record, left with null fields
END newItem;
and the same anonymous block now gives:
Item 1 id_std: 1 procent: 7
Item 2 id_std: procent:
Item 3 id_std: 3 procent: 21
Item 4 id_std: procent:
PL/SQL procedure successfully completed.
Notice the null values, and that there is no 5th row.
Or again extend the collection once, and refer to the numbered records directly:
PROCEDURE newItem (example IN OUT tabel) IS
BEGIN
example.extend(4);
example(1).id_std := 1;
example(1).procent := 7;
example(3).id_std := 3;
example(3).procent := 21;
END newItem;
which gets the same result from the anonymous block.

Populate a database gives strange results

I have a database and I need to populate it's first 2 columns on every row. The first column is the date and the second column is an id.
My code is as follows:
.......
febr29:array[1..12] of byte = (31,29,31,30,31,30,31,31,30,31,30,31);
.......
procedure TForm.populate_database;
var
i,j,m,n: Integer;
begin
for i := 1 to 12 do
for j := 1 to febr29[i] do
for m := 1 to 9 do
for n := 1 to 15 do begin
database.tbl1.Append;
database.tbl1['date']:= inttostr(j)+'.'+inttostr(i)+'.2016';
database.tbl1['id']:='a'+inttostr(m)+inttostr(n);
database.tbl1.Post;
end;
end;
So basically I need to have all the ids on all the days of the year. But I have a problem with the code above: it gives me some strange output in the database, as in the following picture:
What am I doing wrong?
If your ID field is supposed to identify the data row, it would be better to declare it in the database as an integer column, not a character/string one.
It would also be better & less error prone not to try and calculate it from your loop variables, but use a running counter instead
procedure TForm.populate_database;
var
i,j,m,n: Integer;
ID : Integer;
begin
ID := 0;
for i := 1 to 12 do
for j := 1 to febr29[i] do
for m := 1 to 9 do
for n := 1 to 15 do begin
Inc(ID);
database.tbl1.Append;
database.tbl1['date']:= inttostr(j)+'.'+inttostr(i)+'.2016';
database.tbl1['id'].AsInteger :=ID;
database.tbl1.Post;
Of course, if you must have the 'a' prefix and a character coumn type for some reason, you could do
database.tbl1['id'].AsString :='a' + IntToStr(ID);
but even that may give you results you aren't expecting, unless to pad the result of IntToStr(ID) to a fixed length with leading zeroes.

How to use dbms_output to display an array of values assigned to a variable?

I have something like this, but got an error says ORA-06533: Subscript beyond count. I want to see all the values from the "select distinct" statement in the output tab. Anyone can help? thanks!
DECLARE
TYPE v_chks_array IS VARRAY (10) OF VARCHAR2 (50);
arrSRCs v_chks_array;
BEGIN
arrSRCs := v_chks_array ();
arrSRCs.EXTEND (10);
SELECT /*+parallel (a,4)*/
DISTINCT a.src_table
BULK COLLECT INTO arrSRCs
FROM hcr_dm.hcr_dm_fact a;
DBMS_OUTPUT.put_line (arrSRCs (10));
END;
Collections are not needed here:
begin
for results in
(
select /*+ parallel (a,4) */ distinct a.src_table
from hcr_dm.hcr_dm_fact a;
) loop
dbms_output.put_line(results.src_table);
end loop;
end;
/
Instead of VARRAY, you may try a TABLE type collection. You'll have more flexibility with number of records to hold while doing BULK COLLECT.
Create or replace type v_chks_array IS TABLE OF VARCHAR2 (500);
More information can be found at http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/collections.htm

Resources