Oracle: Update Every Row in a Table based off an Array - oracle

So i'm trying to create some seed data for a database that uses zip codes. I've created an array of 22 arbitrary zip code strings, and i'm trying to loop through the array and update one of the zips to every row in a table. Based on what I read and tried (I'm a 1st year, so I'm probably missing something), this should work, and does when I just output the array value based on the count of the table. this issue is in the row id subquery. When I run it in my console, it doesn't throw any errors, but it never completes and I think it's stuck in an infinite loop. How can I adjust this so that it will update the field and not get stuck?
declare
t_count NUMBER;
TYPE zips IS VARRAY(22) OF CHAR(5);
set_of_zips zips;
i NUMBER;
j NUMBER :=1;
BEGIN
SELECT count(*) INTO t_count FROM T_DATA;
set_of_zips:= zips('72550', '71601', '85920', '85135', '95451', '90021', '99611', '99928', '35213', '60475', '80451', '80023', '59330', '62226', '27127', '28006', '66515', '27620', '66527', '15438', '32601', '00000');
FOR i IN 1 .. t_count LOOP
UPDATE T_DATA
SET T_ZIP=set_of_zips(j)
---
WHERE rowid IN (
SELECT ri FROM (
SELECT rowid AS ri
FROM T_DATA
ORDER BY T_ZIP
)
) = i;
---
j := j + 1;
IF j > 22
THEN
j := 1;
END IF;
END LOOP;
COMMIT;
end;

You don't need PL/SQL for this.
UPDATE t_data
SET t_zip = DECODE(MOD(ROWNUM,22)+1,
1,'72550',
2,'71601',
3,'85920',
4,'85135',
5,'95451',
6,'90021',
7,'99611',
8,'99928',
9,'35213',
10,'60475',
11,'80451',
12,'80023',
13,'59330',
14,'62226',
15,'27127',
16,'28006',
17,'66515',
18,'27620',
19,'66527',
20,'15438',
21,'32601',
22,'00000')

Related

Oracle how to address column in PL/SQL FOR LOOP by it's position instead of column name

Is there a way to address column by it's position in PL/SQL, something like this
BEGIN
FOR r IN (SELECT * FROM table)
LOOP
DBMS_OUTPUT.PUT_LINE(r.(1));
END LOOP;
END;
I tried both bracket types but always get
PLS-00103: Encountered the symbol "(" when expecting one of the following
I also tried BULK COLECT INTO with table of varray but then I get
PLS-00642: local collection types not allowed in SQL statements
And if I declare table with %ROWTYPE and bulk it then it seems to behave the same way as the normal loop.
I tried to look into documentation but couldn't find any example of this scenario.
Using the DBMS_SQL package and the COLUMN_VALUE procedure: but that's a lot of work... probably overkilling for your actual needs: you have to open a sys_refcursor for the query, convert it into a cursor number with DBMS_SQL.TO_CURSOR_NUMBER, collect the descriptions of the columns with DBMS_SQL.DESCRIBE_COLUMNS and loop on the column count and fetch the value with DBMS_SQL.COLUMN_VALUE into a specific variable according to its type...
In general, no. Oracle does not support accessing records by position; only by identifier.
You can approach the problem slightly differently and, instead of using SELECT *, you can explicitly list the columns to select and give them numeric column aliases:
BEGIN
FOR r IN (SELECT dummy AS "1" FROM DUAL)
LOOP
DBMS_OUTPUT.PUT_LINE(r."1");
END LOOP;
END;
/
You are still referring to the columns by name; only now their names are the "numeric" aliases.
Note: Personally, I would stick to using the column names and not try to find work-arounds to use positions as positions can change if columns are deleted from the table.
If you want to have a matrix in PL/SQL you could define one as an indexed table of an indexed table of whatever data type you're dealing with, something like:
type t_row is table of varchar2(1) index by pls_integer;
type t_tab is table of t_row index by pls_integer;
l_matrix t_tab;
The tricky part is populating it. DBMS_SQL is certainly one approach, but partly for fun, you could use XML. The DBMS_XMLGEN package lets you run a query and get the results back as an XML document with a ROWSET root node. You can then count the ROW nodes under that, and assuming there are any, count the nodes within the first ROW - which gives the matrix dimensions.
You can then use nested loops to populate the matrix, and once done, you can refer to an element as for example l_matrix(3)(2).
This is a working example, where the table being queried has values that are all a single character, for simplicity; you would need to change the matrix definition to handle the data you expect.
DECLARE
-- elements of matrix need to be data type and size suitable for your data
type t_row is table of varchar2(1) index by pls_integer;
type t_tab is table of t_row index by pls_integer;
l_matrix t_tab;
l_xml xmltype;
l_rows pls_integer;
l_cols pls_integer;
BEGIN
l_xml := dbms_xmlgen.getxmltype('SELECT * FROM your_table');
SELECT XMLQuery('count(/ROWSET/ROW)'
PASSING l_xml
RETURNING CONTENT).getnumberval()
INTO l_rows
FROM DUAL;
IF l_rows = 0 THEN
dbms_output.put_line('No data');
RETURN;
END IF;
SELECT XMLQuery('count(/ROWSET/ROW[1]/*)'
PASSING l_xml
RETURNING CONTENT).getnumberval()
INTO l_cols
FROM DUAL;
dbms_output.put_line('Rows: ' || l_rows || ' Cols: ' || l_cols);
-- populate matrix
FOR i IN 1..l_rows LOOP
FOR j in 1..l_cols LOOP
l_matrix(i)(j) := l_xml.extract('/ROWSET/ROW[' || i || ']/*[' || j || ']/text()').getstringval();
END LOOP;
END LOOP;
-- refer to a specific element
dbms_output.put_line('Element 3,2 should be H; is actually: ' || l_matrix(3)(2));
-- display all elements as list
FOR i IN 1..l_matrix.COUNT LOOP
FOR j IN 1..l_matrix(i).COUNT LOOP
dbms_output.put_line('Row ' || i || ' col ' || j || ': ' || l_matrix(i)(j));
END LOOP;
END LOOP;
-- display all elements as grid
FOR i IN 1..l_matrix.COUNT LOOP
FOR j IN 1..l_matrix(i).COUNT LOOP
IF j > 0 THEN
dbms_output.put(' ');
END IF;
-- format/pad real data to align it; simple here with single-char values
dbms_output.put(l_matrix(i)(j));
END LOOP;
dbms_output.new_line;
END LOOP;
END;
/
With my sample table that outputs:
Rows: 4 Cols: 3
Element 3,2 should be H; is actually: H
Row 1 col 1: A
Row 1 col 2: B
Row 1 col 3: C
Row 2 col 1: D
Row 2 col 2: E
Row 2 col 3: F
Row 3 col 1: G
Row 3 col 2: H
Row 3 col 3: I
Row 4 col 1: J
Row 4 col 2: K
Row 4 col 3: L
A B C
D E F
G H I
J K L
fiddle
As #astenx said in a comment, you can use JSON instead of XML; which would allow you to populate the matrix like this.
That has raised something else I'd overlooked. My dummy table doesn't have an ID column or an obvious way of ordering the results. For the JSON version I used rownum, but in both that and the original query the order of the rows (in the result set, and thus in the matrix) is currently indeterminate. You need to have a column you can order by in the query, or some other way to determine the sequence of rows.

In below program only for first two rows it is working and for other rows it is showing incorrect values in plsql

Here for the below program i need to print the amt_running_bal from the previous value. but it is not working and showing error. what is the error in the below program.Please provide any solution for this.
DECLARE
total Number := 1000000;
c_cod_acct_no Char;
c_amt_txn Number;
c_cod_drcr Char;
c_amt_running_bal Number;
amt_running_bal Number;
CURSOR c_chnos1 is
SELECT cod_drcr, amt_txn,amt_running_bal FROM chnos1;
BEGIN
OPEN c_chnos1;
FOR k IN 1..2 LOOP
FETCH c_chnos1 into c_cod_drcr,c_amt_txn,c_amt_running_bal;
if c_cod_drcr = 'C' then
total := total + c_amt_txn;
Update chnos1 SET amt_running_bal = total where cod_drcr='C' ;
elsif
c_cod_drcr = 'D' then
total := total - c_amt_txn;
Update chnos1 SET amt_running_bal = total where cod_drcr='D';
else
total := total + c_amt_txn;
Update chnos1 SET amt_running_bal = total where cod_drcr='C';
end if;
END LOOP;
CLOSE c_chnos1;
END;
/
Your query does not work as you limit the loop to k IN 1..2 so it will only read two rows from the cursor and there is no correlation between the row you are reading from the cursor and what you are updating; in fact, you are updating all the rows WHERE cod_drcr = 'C' or WHERE cod_drcr = 'D' and not just the current row. You could fix it by correlating the updates to the current row using the ROWID pseudo-column but it is an inefficient solution to use cursors as it will be slow and generate redo/undo log entries for each iteration of the cursor loop.
Instead, do it all in a single MERGE statement using an analytic SUM and a CASE expression:
MERGE INTO chnos1 dst
USING (
SELECT rowid AS rid,
1000000
+ SUM(
CASE cod_drcr
WHEN 'C' THEN +amt_txn
WHEN 'D' THEN -amt_txn
ELSE 0
END
)
OVER (
-- Use something like this to update each account
-- PARTITION BY cod_acct_no ORDER BY payment_date
-- However, you haven't said how to partition or order the rows so use this
ORDER BY ROWNUM
) AS total
FROM chnos1
) src
ON (dst.ROWID = src.rid)
WHEN MATCHED THEN
UPDATE SET amt_running_bal = src.total;
fiddle

No data found in oracle

I have a procedure in oracle that looks like this
create or replace procedure check_display_stock as
id_brg number(20);
rowcount number (20);
begin
-- 1. insert datas from display into temp_display
insert into temp_display
select id_barang,stok,min_stok
from display
where stok <= min_stok;
--2. select the number of datas in temp_display
select count(rownum)
into rowcount
from temp_display;
while(rowcount != 0)
loop
-- Error: no data found
select id_barang
into id_brg
from temp_display
where rownum = 1;
--just another procedure to do other things
insert_spb(id_brg);
delete from temp_display where rownum = 1;
end if;
end loop;
end check_display_stock;
An error occurs when I tried to select into that says no data found.
I don't understand why this happened.
You never decrement rowcount so you will end up deleting the rows in temp_display one-by-one and then keep going (potentially forever) and on the next iteration after you have already emptied the table you will try to select id_barang into id_brg ... and it will fail as you have already emptied the table.
Instead, you could use BULK COLLECT INTO or a CURSOR to bypass the temporary table:
create or replace procedure check_display_stock
as
begin
FOR cur IN ( select id_barang,
stok,
min_stok
from display
where stok <= min_stok
)
LOOP
insert_spb(cur.id_barang);
END LOOP;
END;
/
db<>fiddle here

Inserting values into a created table with while loop

I'm working on a while loop exercise on oracle. I have created a table with two columns.
What I want to do is; inserting values into first column with a sequence of from 1 to 1 million(1,2,3,4,5....1000000).
I've tried
DECLARE
a int := 0;
BEGIN
WHILE a < 1000000 LOOP
a := a + 1;
END LOOP;
END;
insert into Schema_name.table_name
(column_1)
values('a')
P.S: I'm working on Toad 12.9
Would you like to give a hand to me for this?
Just insert values(a), when you write 'a' you insert the character 'a' and not the variable a
DECLARE
a int := 0;
BEGIN
WHILE a < 1000000 LOOP
a := a + 1;
insert into Schema_name.table_name
(column_1)
values(a);
END LOOP;
END;

PL/SQL cannot delete multiple rows

I have written simple code using PL/SQL to delete multiple rows from a table, but below code only deletes one row every i trigger it.
DECLARE
i number(2);
BEGIN
FOR i IN 1..4 LOOP
DELETE FROM table_name WHERE rownum = i;
dbms_output.put_line('i is: '|| i);
END LOOP;
END;
Can someone please suggest what is wrong with code?
ROWNUM is the nth row read.
select * from table_name where rownum = 1;
gets you the first row.
select * from table_name where rownum <= 2;
gets you the first two rows.
select * from table_name where rownum = 2;
gets you no rows, because you cannot read a second row without having read a first one.
This said, you'd have to replace
DELETE FROM table_name WHERE rownum = i;
with
DELETE FROM table_name WHERE rownum = 1;
But why would you do this anyway? Why delete arbitrarily picked records? Why use PL/SQL at all, rather than a mere DELETE FROM table_name WHERE rownum <= 4;?
What you need to understand is how Oracle processes ROWNUM. When assigning ROWNUM to a row, Oracle starts at 1 and only only increments the value when a row is selected; that is, when all conditions in the WHERE clause are met. Since our condition requires that ROWNUM is greater than 2 or equal to nth value, no rows are selected and ROWNUM is never incremented beyond 1.
If you really do wanna achieve it using PLSQL anot using SQL query as my friend Throsten has stated then please find a work around below.
I Created a dummy table test_c which holds 1 column (ID with number as its type).
set serveroutput on ;
DECLARE
i number(2);
j number(2);
counter number(10):=0;
BEGIN
FOR i IN 5..11 LOOP
if counter = 0 then
j:=i;
end if;
DELETE FROM test_c WHERE ID = (select id from (select id,rownum as ro from test_c order by id) where ro =j);
dbms_output.put_line('i is: '|| i);
counter:=counter+1;
END LOOP;
END;
Please note that this is not the right way to do it, but will work for your requirement.

Resources