I have an array of records from which I am trying to construct a binary tree.
The end result should be as follow. -1 represents null and if both the left and right pointer are -1, it is a leaf node.
| Index | Left Pointer | Data | Right Pointer |
|:-----:|:------------:|:----:|:-------------:|
| 0 | 1 | 17 | 4 |
| 1 | 2 | 8 | 3 |
| 2 | -1 | 4 | 7 |
| 3 | -1 | 12 | 6 |
| 4 | 5 | 22 | 8 |
| 5 | -1 | 19 | -1 |
| 6 | -1 | 14 | -1 |
| 7 | -1 | 5 | -1 |
| 8 | 9 | 30 | -1 |
| 9 | -1 | 25 | -1 |
This is my current code:
type node = record
data : string;
left : integer;
right : integer;
end;
var
binaryTree : array[0..9] of node;
procedure output;
var
i : integer;
begin
for i := 0 to 9 do
begin
writeln('Data: ' , binaryTree[i].data);
writeln('Left: ' , binaryTree[i].left);
writeln('Right: ' , binaryTree[i].right);
end;
end;
procedure takeInput;
var
i : integer;
begin
for i := 0 to 9 do
readln(binaryTree[i].data);
end;
procedure initialisePointers;
var
i : integer;
begin
// initialise to -1 for null
for i := 0 to 9 do
begin
binaryTree[i].left := -1;
binaryTree[i].right := -1;
end;
end;
procedure setPointers;
var
i : integer;
root, currentNode : node;
begin
// first value becomes root
root := binaryTree[0];
for i := 1 to 9 do
begin
currentNode := root;
while true do
begin
if binaryTree[i].data < currentNode.data then
begin
if currentNode.left = -1 then
begin
currentNode.left := i;
break;
end
else
currentNode := binaryTree[currentNode.left]
end
else
begin
if binaryTree[i].data >= currentNode.data then
begin
if currentNode.right = -1 then
begin
currentNode.right := i;
break;
end
else
currentNode := binaryTree[currentNode.right]
end;
end;
end;
end;
end;
begin
takeInput;
initialisePointers;
setPointers;
output;
end.
With the input shown in the table, I get an output of all pointers remaining at -1 as initialised. Any idea why this may be?
In Pascal, records are value types, so when you do e.g.
root := binaryTree[0];
then root is a copy of binaryTree[0].
When you set fields in root you are setting fields in the copy.
If you want chose changes reflected back in the binaryTree array, you have to assign the value back after use:
binaryTree[0] := root;
Or else, use pointers, but I am not sure you have learned about those yet.
Related
Actually, I want to create an oracle procedure to normalize the table and for that we required a multiple queries/task to perform /execute. Below are the steps for which will help to write an oracle procedure:
step 1) Suppose a table name temp and there are nearly millions of records in it. So get the distinct records from whole table and
insert into same table by updating value for ver_id column as -1. Below is the example and table structure to understand
| pid | address | key |ver_id
| 1 | 242 Street | 123 | 1
| 2 | 242 Street | 123 |2
| 3 | 242 Street | 123 |3
| 4 | 242 Long St | 456 |4
Expected Resultis below :
select 2 distinct records for duplicate records from above table and insert that distinct record at the end of the table by updating ver_id as -1 like below:
| pid | address | key |ver_id
| 1 | 242 Street | 123 | 1
| 2 | 242 Street | 123 |2
| 3 | 242 Street | 123 |3
| 4 | 242 Long St | 456 |4
| 5 | 242 Street | 123 |-1
step 2) Find all parents records of given key in ADDRESS_TEMP table and update them with
pid (primary key of temp table) which is newly created in step 1 i.e pid as 5 and 6
ADDRESS_TEMP table (parent of temp table)
pid is the primary key of temp table and foreign key of ADDRESS_TEMP table
|addr_id | ver_id | pid
| 11 | 1 | 1
| 12 | 2 | 2
| 13 | 3 | 3
| 14 | 4 | 4
| 15 | 5 | 5
| 16 | 6 | 6
After Update
|addr_id | ver_id | pid
| 11 | 1 | 1
| 12 | 2 | 2
| 13 | 3 | 3
| 14 | 4 | 4
| 15 | 5 | 5
| 15 | 6 | 6
| 15 | 7 | 7
step 3): Delete all temp table records where key is 123 and 456 (in short delete all duplicate records) whose ver_id is not equal to -1
so the expected result of temp table is like below:
| pid | address | key |ver_id
| 4 | 242 Long St | 456 |4
| 5 | 242 Street | 123 |-1
For the first step ,exeecuted this query as below and its working as expected buti want to perfrom all the above steps in one single procedure .
insert into temp (id, address, key, ver_id)
with data as
(select t.*,
row_number() over (partition by address, key order by id) rn
from temp t
),
data2 as
(select distinct d.address, d.key
from data d where d.rn > 1
)
select seq_temp.nextval, address, key, -1
From data2.`
But above one is just for the step first but we need to create a procedure for all the above 3 steps and also i have tried to add the above insert statement into the procedure and it gets executed but for step2 and 3 it needs to be created
CREATE OR replace PROCEDURE p1 AS
cursor c_temp IS
SELECT * FROM temp ;
r_temp c_temp%ROWTYPE;
BEGIN
OPEN c_temp;
LOOP
FETCH c_temp INTO r_temp;
EXIT WHEN c_temp%NOTFOUND;
insert into temp (id, address, key, ver_id)
with data as
(select t.*,
row_number() over (partition by address, key order by id) rn
from temp t
),
data2 as
(select distinct d.address, d.key
from data d where d.rn > 1
)
select seq_temp.nextval, address, key, -1
From data2;
END LOOP;
CLOSE c_temp;
END;
Your instructions are quite confusing and I'm not sure that I got it completely. Anyway, below is the answer (with comments in the code). Even If it isn't exactly what you wanted - the logic and the structure of the code should be ok. You will have to adjust it to your context anyyway.
Table temp data before and after:
/* TEMP before
P_ID ADDRESS A_KEY VER_ID
1 242 Street 123 1
2 242 Street 123 2
3 242 Street 123 3
4 242 Long St 456 4
TEMP after
P_ID ADDRESS A_KEY VER_ID
4 242 Long St 456 4
5 242 Street 123 -1 */
Table TEMP_ADDRESS before and after:
/* TEMP_ADDRESS before
ADDR_ID VER_ID P_ID
11 1 1
12 2 2
13 3 3
14 4 4
TEMP_ADDRESS after
ADDR_ID VER_ID P_ID
11 1 5
12 2 5
13 3 5
14 4 4 */
Here is the code with comments...
SET SERVEROUTPUT ON
DECLARE
-- Declare cursor to fetch you distinct records from table TEMP
CURSOR c_temp IS SELECT Count(*) "CNT", ADDRESS, A_KEY FROM temp WHERE VER_ID > 0 GROUP BY ADDRESS, A_KEY HAVING Count(*) > 1 ORDER BY A_KEY;
-- ------------- Cursor records below ----------------
-- CNT ADDRESS A_KEY
-- ------ ------------ ----------
-- 3 242 Street 123
-- ----------------------------------------------------
cSet c_temp%ROWTYPE;
sq VarChar2(1) := ''''; -- single quote character (4 single quotes) -- using it to construct Sql commands
mSql VarChar2(512) := '';
mID TEMP.P_ID%TYPE;
mAddr TEMP.ADDRESS%TYPE;
mKey TEMP.A_KEY%TYPE;
BEGIN
Select Nvl(Max(P_ID), 0) Into mID From TEMP; -- Last (max) P_ID from taable TEMP --> 4 (empty table would return 0)
--
OPEN c_temp;
LOOP
FETCH c_temp INTO cSet;
EXIT WHEN c_temp%NOTFOUND;
mID := mID + 1; -- add 1 to mID for insert into TEMP
-- constructing INSERT commands for every cursor record
mSql := 'INSERT INTO TEMP(P_ID, ADDRESS, A_KEY, VER_ID) VALUES(' || mID || ', ' || sq || cSet.ADDRESS || sq || ', ' || cSet.A_KEY || ', -1)';
--
-- --------------- constructed command(s) for cursor record(s) ---------------------------------
-- INSERT INTO TEMP(P_ID, ADDRESS, A_KEY, VER_ID) VALUES(5, '242 Street', 123, -1)
--
Execute Immediate mSql; -- execute created INSERT command(s)
--
-- While you still have m_ID and cursor record's ADDRESS and A_KEY use them to update parent table TEMP_ADDRESS
Begin
UPDATE TEMP_ADDRESS SET P_ID = mID WHERE P_ID IN(Select P_ID FROM TEMP WHERE ADDRESS = cSet.ADDRESS And A_KEY = cSet.A_KEY And VER_ID > 0);
-- after that you can delete records from table temp that are not needed any more
DELETE FROM TEMP WHERE P_ID IN(Select P_ID FROM TEMP WHERE ADDRESS = cSet.ADDRESS And A_KEY = cSet.A_KEY And VER_ID > 0);
Commit;
Exception
WHEN OTHERS THEN
Rollback; -- if anything went wrong - Rollback and send a message
DBMS_OUTPUT.PUT_LINE('ERR - UPDATE or DELETE ' || Chr(10) || SQLERRM);
End;
--
END LOOP;
CLOSE c_temp;
Exception
WHEN OTHERS THEN
Rollback; -- if anything went wrong - Rollback and send a message
DBMS_OUTPUT.PUT_LINE('ERR - INSERT ' || Chr(10) || SQLERRM);
END;
Regards...
I am still trying to insert values into a ratings table, I have tried 2 different approaches and both do the same thing my first approach is as follows:
DECLARE
rate NUMBER;
lv_cat NUMBER;
rate_num NUMBER;
BEGIN
FOR i IN 1..1
LOOP
INSERT INTO RATINGS (HOTELID, CATEGORYID, RATING)
SELECT a.IDHOTEL, b.CATEGORYID, INSTR(a.review, b.seed_words)
FROM review a, SeedWords b
WHERE a.IDHOTEL = i;
END LOOP;
END;
My second approach is:
DECLARE
i NUMBER := 1;
lv_hotel NUMBER := 1;
lv_rate NUMBER;
LV_RATE_NUM NUMBER;
lv_cat NUMBER;
hotel_match NUMBER;
cat_match NUMBER;
BEGIN
FOR rec_hotel IN (SELECT a.IDHotel AS lv_hotel,INSTR(a.review, b.seed_words) AS LV_RATE_NUM, b.CATEGORYID AS lv_cat
FROM review a, SEEDWORDS b)
LOOP
--DBMS_OUTPUT.put_line('start');
IF rec_hotel.LV_RATE_NUM > 0 THEN
lv_rate := rec_hotel.LV_RATE_NUM*.006;
ELSIF rec_hotel.LV_RATE_NUM < 1 THEN
lv_rate := rec_hotel.LV_RATE_NUM*10;
ELSIF rec_hotel.LV_RATE_NUM = 0 THEN
lv_rate := 8.6/3;
END IF;
INSERT INTO RATINGS
VALUES (rec_hotel.lv_hotel, rec_hotel.lv_cat, lv_rate);
--DBMS_OUTPUT.put_line(LV_RATE_NUM);
END LOOP;
END;
This is suppose to find words in the reviews. There are 4 categories that the words fall into so there are only supposed to be 4 ratings per hotel, but instead I am getting hundreds of ratings per hotel. What can I add into either block of code to make sure that for each hotelID there are only 4 records, one record for the rating of each categoryID?
My expected out put would look something like this with the rating value being the number of words found all the reviews for the the hotel:
HOTELID | CATEGORYID | RATING
1 | 1 | 5
1 | 2 | 10
1 | 3 | 2
1 | 4 | 6
2 | 1 | 6
2 | 2 | 10
2 | 3 | 4
2 | 4 | 2
My review table is the following:
Hotel Id | Review |
1 | "LARGE STRING"
1 | "LARGE STRING"
1 | "LARGE STRING"
1 | "LARGE STRING"
1 | "LARGE STRING"
1 | "LARGE STRING"
2 | "LARGE STRING"
2 | "LARGE STRING"
2 | "LARGE STRING"
2 | "LARGE STRING"
And My SeedWord Table is set up as follows
SEED ID| SEED_WORD|CATEGORYID
1 | "WORD" | 1
2 | "WORD" | 1
3 | "WORD" | 2
4 | "WORD" | 2
5 | "WORD" | 3
6 | "WORD" | 3
7 | "WORD" | 4
8 | "WORD" | 4
I hope this makes sense.
You are given an array of counters N initiated with zero.
You have a list of actions A to perform on N array.
each action is an int x
ie A = [1,5,3]
for each k in A as x actions
if x <= len(N) then increase N[i-1] by one
else set all N items with max of max value of N
you should return the counters array after the last action
Exercise Link
The first and simpler solution
wont pass 100% because of time complexity
create counters array with all 0 in size of len(A)
for each idx,action in A
if action <= len(N)
counters[idx-1]++
else
maxVal = max(counters)
now set max maxVal to all counters items
return counters
small improvement, store maxVal variable in the top and update it with each increase of the counter items. Then when you need to set all items, you dont need to find max value.
Better solution
that passes the tests 100%
We will still go through actions, and we store the max value and another new variable forcedVal. we will not update the whole counters array each time we need but only update the forcedVal with the max. Then when ever we need to ++ an item we will first check if it is smaller then forcedVal and if so we will give it forcedVal value before the ++.
create counters array with all 0 in size of len(A)
create max var with 0
create forcedVal var with 0
for each idx,action in A
if action <= len(N)
cur = counters[idx-1]
if cur < forceVal ? cur = forceVal
cur++
if cur > max
max = cur
counters[idx-1] = cur
else
forcedVal = max
here we do a single loop to set our counters
for each cnt in counters
if cnt < forcedVal ? cnt = forcedVal
return counters
this is how it will look with this example
Solution(5, [3, 4, 4, 6, 1, 4, 4])
| c[0] | c[1] | c[2] | c[3] | c[4] | action | max | forcedVal |
|------|------|------|------|------|--------|-----|-----------|
| 0 | 0 | 0 | 0 | 0 | null | 0 | 0 |
| 0 | 0 | 1 | 0 | 0 | 3 | 1 | 0 |
| 0 | 0 | 1 | 1 | 0 | 4 | 1 | 0 |
| 0 | 0 | 1 | 2 | 0 | 4 | 2 | 0 |
| 0 | 0 | 1 | 2 | 0 | 6 | 2 | 2 |
| 3 | 0 | 1 | 2 | 0 | 1 | 3 | 2 |
| 3 | 0 | 1 | 3 | 0 | 4 | 3 | 2 |
| 3 | 0 | 1 | 4 | 0 | 4 | 4 | 2 |
as you can see above after all we have left with:
3, 0, 1, 4, 0
we will our final cleanup loop to set all items to at least the value of forcedVal and we get
3, 2, 2, 4, 2
func Solution(N int, A []int) []int {
counters := make([]int, N)
var max int
var forcedVal int
for _, v := range A {
if v > N {
forcedVal = max
} else {
cur := counters[v-1]
if cur < forcedVal {
cur = forcedVal
}
cur++
if cur > max {
max = cur
}
counters[v-1] = cur
}
}
for i := range counters {
if counters[i] < forcedVal {
counters[i] = forcedVal
}
}
return counters
}
I have a cursor for loop include credit and debit amount of my daily transaction account, and I want to show them respectively.
DECLARE
v_debit := 0;
v_credit := 0;
CURSOR c_acct_rec is
select bk.acctname, bk.bsb, bk.accountnr, bk.value, bk.code
from mybank_tbl bk
r_acct_rec c_acct_rec%ROWTYPE;
FOR r_acct_rec in c_acct_rec LOOP
if r_acct_rec.code = 'dr' then
v_debit := show_debit;
else
v_credit := v_credit + r_acct_rec;
end if;
DBMS_OUTPUT.PUT_LINE(v_credit || v_debit);
END LOOP;
END;
This part of the SQL query from the above is showing all the debit and credit values.
+-----------------+
| value | code | |
+-----------------+
| | 12 | dr | |
| | 5 | dr | |
| | 7 | dr | |
| | 33 | dr | |
| | 16 | dr | |
| | 1000 | cr | |
+-----------------+
The problem I found because they are in the cursor loop, it will populate all value in one column like below
+-------+--------+
| Debit | Credit |
+-------+--------+
| 12 | |
| 5 | |
| 7 | |
| 33 | |
| 16 | |
| 1000 | |
+-------+--------+
The if/else statement can't control it to show all row records.
+-------+--------+
| Debit | Credit |
+-------+--------+
| 12 | 0 |
| 5 | 0 |
| 7 | 0 |
| 33 | 0 |
| 16 | 0 |
| 1000 | 0 |
| 1000 | 1000 |
+-------+--------+
Is that a way to separate it using a stored procedure or separate the row from the account value (r_acct_rec.value) to check to get this expected outcome?
+-------+--------+
| Debit | Credit |
+-------+--------+
| 12 | |
| 5 | |
| 7 | |
| 33 | |
| 16 | |
| | 1000 |
+-------+--------+
You have two variables. You need to populate one and blank the other, depending on the value of code. Use pad functions in the display to align the output nicely.
DECLARE
v_debit number := 0;
v_credit number := 0;
CURSOR c_acct_rec is
select bk.acctname, bk.bsb, bk.accountnr, bk.value, bk.code
from mybank_tbl bk
r_acct_rec c_acct_rec%ROWTYPE;
BEGIN
DBMS_OUTPUT.PUT_LINE(rpad('credit',10)|| ' | ' || rpad('debit',10));
FOR r_acct_rec in c_acct_rec LOOP
if r_acct_rec.code = 'dr' then
v_credit := null;
v_debit := r_acct_rec.value;
else
v_credit := r_acct_rec.value;
v_debit := null;
end if;
DBMS_OUTPUT.PUT_LINE(lpad(v_credit,10)|| ' | ' || lpad(v_debit,10));
END LOOP;
END;
Another option is to make the transformation from code-value into separate values in the query and eliminate the code for determining them.
declare
cursor c_acct_rec is
select bk.acctname
, bk.bsb
, bk.accountnr
, case when bk.code = 'dr' then bk.value else null end as debit
, case when bk.code = 'cr' then bk.value else null end as credit
from mybank_tbl bk;
r_acct_rec c_acct_rec%rowtype;
begin
dbms_output.put_line(rpad('credit',10)|| ' | ' || rpad('debit',10));
for r_acct_rec in c_acct_rec loop
dbms_output.put_line(lpad(r_acct_rec.credit,10)|| ' | ' || lpad(r_acct_rec.debit,10));
end loop;
end;
Your requirement can be met in SQL only, it does not require PL/SQL.
select
bk.acctname,
bk.bsb,
bk.accountnr,
bk.value,
bk.code,
case
when bk.code = 'dr' then
bk.value
end as debit,
case
when bk.code = 'cr' then
bk.value
end as credit
from mybank_tbl bk
I'm trying to increase the age of every customer by 1 and display them; using a cursor.
This is the Table structure.
Select * from customers;
+----+----------+-----+-----------+----------+
| ID | NAME | AGE | ADDRESS | SALARY |
+----+----------+-----+-----------+----------+
| 1 | Ramesh | 32 | Ahmedabad | 2000.00 |
| 2 | Khilan | 25 | Delhi | 1500.00 |
| 3 | kaushik | 23 | Kota | 2000.00 |
| 4 | Chaitali | 25 | Mumbai | 6500.00 |
| 5 | Hardik | 27 | Bhopal | 8500.00 |
| 6 | Komal | 22 | MP | 4500.00 |
Here is my code:
DECLARE
c_id customers.id%TYPE;
c_name customers.name%TYPE;
c_age customers.age%TYPE;
CURSOR c1 IS
SELECT id, name, age
FROM customers
FOR UPDATE OF salary;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO c_id, c_name, c_age;
UPDATE customers
SET age = age + 1
WHERE CURRENT OF c1;
EXIT WHEN c1%NOTFOUND;
dbms_output.put_line( c_id || ' ' || c_name || ' ' || c_age );
END LOOP;
CLOSE c1;
END;
/
However, I am getting the following error:
Error report:
ORA-01410: invalid ROWID
ORA-06512: at line 13
01410. 00000 - "invalid ROWID"
*Cause:
*Action:
1 Ramesh 32
2 Khilan 25
3 kaushik 23
4 Chaitali 25
5 Hardik 27
6 Komal 22
Why is this happening and how can I stop it?
You have your exit in the wrong place; it should be straight after the fetch. You are processing your six real rows properly, but then you have a seventh fetch - after which %notfound will be true - so there is no 'current' row to update. You need to exit before you try to do anything with that invalid row.
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO c_id, c_name, c_age;
EXIT WHEN c1%NOTFOUND;
UPDATE customers
SET age = age + 1
WHERE CURRENT OF c1;
dbms_output.put_line( c_id || ' ' || c_name || ' ' || c_age );
END LOOP;
CLOSE c1;
END;
Hopefully this is just an exercise, as it's not a very efficient way to do the updates.