Create a nested table and insert data only in the inner table - oracle

I have this table: employees(id, firstname, lastname, salary) and I added another column that will store all the past salaries of every employee.
CREATE OR REPLACE TYPE salary_list AS TABLE OF NUMBER;
/
ALTER TABLE employees
ADD (salary_history salary_list)
NESTED TABLE salary_history STORE AS salary_history_tab;
Now, the table is employees(id, firstname, lastname, salary, salary_history) and salary_history is null. When I modify the salaray value, how do I insert the current salary value into the inner table salary_history? I have tried:
INSERT INTO
TABLE(SELECT salary_history FROM employees WHERE id=1)
VALUES(1500);
And I get the following error:
ORA-22908: reference to NULL table value
Yes, the nested table salary_history is null because I just created it. What am I doing wrong? What is the correct way to insert values(append to the existing data) into salary_history identifying the employee by id?
UPDATE:
added nvl(,()) like so:
INSERT INTO
TABLE(SELECT nvl(salary_history,salary_list()) FROM employees WHERE id=1)
VALUES(1000);
The error that I get now is:
ORA-25015: cannot perform DML on this nested table view column

You can use COALESCE( salary_history, salary_list() ) MULTISET UNION ALL salary_list( :your_new_value ) to append the new value to the old list (or create a new list if it does not exist).
Oracle 11g R2 Schema Setup:
CREATE OR REPLACE TYPE salary_list AS TABLE OF NUMBER;
/
CREATE TABLE employees(
id NUMBER,
salary_history salary_list
) NESTED TABLE salary_history STORE AS salary_history_tab
/
INSERT INTO employees VALUES ( 1, NULL )
/
Query 1:
UPDATE employees
SET salary_history = COALESCE( salary_history, salary_list() )
MULTISET UNION ALL salary_list( 500 )
WHERE id = 1
SELECT * FROM employees
Results:
| ID | SALARY_HISTORY |
|----|----------------|
| 1 | 500 |
Query 2:
UPDATE employees
SET salary_history = COALESCE( salary_history, salary_list() )
MULTISET UNION ALL salary_list( 700 )
WHERE id = 1
SELECT * FROM employees
Results:
| ID | SALARY_HISTORY |
|----|----------------|
| 1 | 500,700 |
Query 3:
UPDATE employees
SET salary_history = COALESCE( salary_history, salary_list() )
MULTISET UNION ALL salary_list( 500 )
WHERE id = 1
SELECT * FROM employees
Results:
| ID | SALARY_HISTORY |
|----|----------------|
| 1 | 500,700,500 |

The column is currently null, rather than an empty table; you can create an empty nested table to start with:
UPDATE employees
SET salary_history = NEW salary_list()
WHERE id = 1;
1 row updated.
And your first statement will then work:
INSERT INTO
TABLE(SELECT salary_history FROM employees WHERE id=1)
VALUES(1500);
1 row inserted.
You can see the new value:
SELECT * FROM employees;
ID FIRSTNAME LASTNAME SALARY SALARY_HISTORY
---------- ---------- ---------- ---------- ------------------------------
1 Joe BLoggs 1234 SALARY_LIST(1500)

Related

Oracle insert and replace value with it's primary key value from another table

I have two tables.
TABLE2 has fields CODE_NM and DESCRIPTION. CODE_NM is the primary key in this table and a foreign key in TABLE1 to this table.
TABLE2:
|---------------------|------------------|
| CODE_NM | DESCRIPTION |
|---------------------|------------------|
| 001 |description 1 text|
|---------------------|------------------|
| 002 |description 2 text|
|---------------------|------------------|
TABLE1:
|---------------------|----------------------|------------------|
| CODE_NM | DESCRIPTION_DETAIL | USER |
|---------------------|----------------------|------------------|
| 001 | some text in here | USERID |
|---------------------|----------------------|------------------|
Every time a row is inserted to TABLE1, a user will input the DESCRIPTION, DESCRIPTION_DETAIL, and USER. With each insert, I want to replace the DESCRIPTION with CODE_NM instead.
It is certain that for any DESCRIPTION inserted, there will be a value for it with its associated primary key in TABLE2.
So I should be able to insert:
INSERT INTO TABLE1 (CODE_NM, DESCRIPTION_DETAIL, USER)
VALUES ('description 1 text','this it the situation','USERID');
and instead of 'description 1 text', I want to display the primary key, which is '001':
|---------------------|----------------------|------------------|
| CODE_NM | DESCRIPTION_DETAIL | USER |
|---------------------|----------------------|------------------|
| 001 | some text in here | USERID |
|---------------------|----------------------|------------------|
Is this possible to do using a trigger?
You can create such a before insert trigger for table1 :
SQL> create or replace trigger trg_tbl1_bi
before insert on table1
for each row
declare
begin
select code_nm
into :new.code_nm
from table2
where trim(description) = trim(:new.code_nm);
exception when no_data_found then
raise_application_error(-20001,'No matching record found!');
end;
end;
/
but you need an exact match among those strings(the values of table2.description column and :new.code_nm of table1 )
You can create trigger
create or replace trigger tbi_table2
before insert on table2
on each row
declare
begin
:new.code_nm := select code_nm from table1 where description=:new.code_nm;
end;
Here's what ended up working:
create or replace trigger trigger_name
before insert on TABLE1
FOR EACH ROW
DECLARE
v_code_nm table2.code_nm%type;
begin
SELECT code_nm INTO v_code_nm FROM TABLE2 WHERE DESCRIPTION=:new.code_nm;
:new.code_nm := v_code_nm;
end;

Getting rows that are having distinct id with bulk collect - Oracle

I want to get the records that are having distinct member id using bulk collect.
My query is like below:
...
type members_nt is table of table2%rowtype;
members_bc members_nt;
...
select t2.* bulk collect
into members_bc
from table1 t1
,table2 t2
where t1.isn= t2.isn
and t1.customer= customer
and t1.insert_date between ... and ... );
t2 table has a column named member_id and what I want to get into members_bc is the rows having distinct member_id.
For example if my t2 table has something like this:
name | surname | account | member_id
john alby abc 123
john alby def 123
mary hale lkj 234
I want to bulk collect only
name | surname | account | member_id
john alby abc 123
mary hale lkj 234
or
name | surname | account | member_id
john alby def 123
mary hale lkj 234
It does not matter which one. But memberid must be unique in the members_bc.
How can I achieve this? Thank you.
Use the ROW_NUMBER() analytic function to give each row a number per member_id and then filter to only get the first row.
DECLARE
TYPE members_nt IS TABLE OF table2%ROWTYPE;
members_bc members_nt;
BEGIN
SELECT t2.name, t2.surname, t2.account, t2.member_id
BULK COLLECT INTO members_bc
FROM (
SELECT t2.*,
ROW_NUMBER() OVER ( PARTITION BY member_id ORDER BY ROWNUM ) AS rn
FROM table2 t2
) t2
WHERE rn = 1;
FOR i IN 1 .. members_bc.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( members_bc(i).member_id || ' ' || members_bc(i).name );
END LOOP;
END;
/
outputs:
123 john
234 mary
db<>fiddle here
You can avoid "select *" and just enter the desired columns by creating a cursor for your select and then creating a %rowtype of the cursor.
Using #MT0 answer as baseline template.)
declare
cursor members_cur is
select t2.name, t2.surname, t2.account, t2.member_id
from ( select t2.name, t2.surname, t2.account, t2.member_id
, row_number() over ( partition by member_id order by rownum ) as rn
from table2 t2
) t2
where rn = 1;
type members_tt is table of members_cur%rowtype;
members_bc members_tt;
begin
open members_cur;
fetch members_cur
bulk collect into members_bc;
close members_cur;
for i in 1 .. members_bc.count
loop
dbms_output.put_line(members_bc(i).member_id || ' ' ||members_bc(i).name);
end loop;
end;
A simple option is to use aggregation, e.g.
select name,
surname,
min(account) as account, --> this
member_id
from ...
group by name, surname, member_id --> and this

insert all and inner join in oracle

I would like to insert data in to two tables. Will be one-to-many connection. For this, I have to use Foreign Key, of course.
I think, table1 - ID column is an ideal for this a Primary Key. But I generate it always with a trigger, automatically, every line. SO,
How can I put Table1.ID (auto generated, Primary Key) column in to table2.Fkey column in the same insert query?
INSERT ALL INTO table1 ( --here (before this) generated the table1.id column automatically with a trigger.
table1.food,
table1.drink,
table1.shoe
) VALUES (
'apple',
'water',
'slippers'
)
INTO table2 (
fkey,
color
) VALUES (
table1.id, -- I would like table2.fkey == table1.id this gave me error
'blue'
) SELECT
*
FROM
table1
INNER JOIN table2 ON table1.id = table2.fkey;
The error message:
"00904. 00000 - "%s: invalid identifier""
As suggested by #OldProgrammer, use sequence
INSERT ALL INTO table1 ( --here (before this) generated the table1.id column automatically with a trigger.
table1_id,
table1.food,
table1.drink,
table1.shoe
) VALUES (
<sequecename_table1>.nextval,
'apple',
'water',
'slippers'
)
INTO table2 (
fkey,
color
) VALUES (
<sequecename_table2>.nextval,
<sequecename_table1>.currval, -- returns the current value of a sequence.
'blue'
) SELECT
*
FROM
table1
INNER JOIN table2 ON table1.id = table2.fkey;
Since you're using Oracle DB's 12c version, then might use Identity Column Property. Then easily return the value of first table's (table1) to a local variable by charging of returning clause just after an insert statement for table1, and use inside the next insert statement which is for table2 as stated below :
SQL> create table table1(
2 ID integer generated always as identity primary key,
3 food varchar2(50), drink varchar2(50), shoe varchar2(50)
4 );
SQL> create table table2(
2 fkey integer references table1(ID),
3 color varchar2(50)
4 );
SQL> declare
2 cl_tab table1.id%type;
3 begin
4 insert into table1(food,drink,shoe) values('apple','water','slippers' )
5 returning id into cl_tab;
6 insert into table2 values(cl_tab,'blue');
7 end;
8 /
SQL> select * from table1;
ID FOOD DRINK SHOE
-- ------- ------- -------
1 apple water slippers
SQL> select * from table2;
FKEY COLOR
---- --------------------------------------------------
1 blue
Anytime you issue the above statement for insertions between begin and end, both table1.ID and table2.fkey columns will be populated by the same integer values. By the way do not forget to commit the changes by insertions, if you need these values throughout the DB(i.e.from other sessions also).

Oracle query to find records in a table that don't exist?

This is probably an easy query but I can't seem to write it.
How can I make a query that looks in a table for all records that don't have the following values in the table
So for the following account, I need to insert into this table
the values
Account = Test003
Role = Owner
Grp = AO1
because this table doesn't have any columns with Role or Grp = to the above
--------------------------------------------------
| ACCOUNT | ROLE | GRP |
--------------------------------------------------
| Test003 | Overlay | WAD |
To make a MERGE work use the DUAL table to generate a row for the values you're searching for.
merge into your_table yt
using ( select 'Test003' as acct
, 'Owner' as role
, 'AO1' as grp
from dual ) q
on (yt.account = q.acct
and yt.role = q.role
and yt.grp = q.grp)
when not matched then
insert (account, role, grp)
values (q.acct, q.role, q.grp)
If you want to MERGE multiple records you can use UNION ALL in the USING to create a set of several queries on DUAL.
This is how I understood the question:
SQL> create table test
2 (account varchar2(10),
3 role varchar2(10),
4 grp varchar2(10));
Table created.
SQL> -- insert sample data
SQL> insert into test
2 (select 'test001', 'actor' , 'a01' from dual union --> contains GRP
3 select 'test002', 'director', 'd02' from dual union
4 select 'test003', 'owner' , 'a01' from dual union --> contains ROLE and GRP
5 select 'test004', 'overlay' , 'wad' from dual
6 );
4 rows created.
SQL>
The above sample data mean that I should insert "owner" and "a01" for accounts test002 and test004 as they don't have neither role = owner and grp = a01
SQL> insert into test (account, role, grp)
2 (select x.account, 'owner', 'a01'
3 from (select account
4 from test
5 where role <> 'owner'
6 and grp <> 'a01'
7 ) x
8 );
2 rows created.
SQL> select * from test
2 order by account, role, grp;
ACCOUNT ROLE GRP
---------- ---------- ----------
test001 actor a01
test002 director d02
test002 owner a01
test003 owner a01
test004 overlay wad
test004 owner a01
6 rows selected.
SQL>

Oracle Order by array index

I have a collection which is a table of numbers containing primary keys and I want to sort a select statement - containing those keys - by the index of the number collection.
For example:
TYPE "NUMBERCOLLECTION" AS TABLE OF NUMBER;
...
myNumberCollection numberCollection := numberCollection(45, 7799, 2187);
...
select nr
, columnA
, columnB
from myTable
where myTable.nr member of myNumberCollection
order by index of myNumberCollection
will result in
NR COLUMNA COLUMNB
-----------------------
45 xyz abc
7799 xyz abc
2187 xyz abc
SELECT nr,
columnA,
columnB
FROM myTable m
INNER JOIN
( SELECT ROWNUM AS id, COLUMN_VALUE FROM TABLE( myNumberCollection ) ) c
ON ( m.nr = c.COLUMN_VALUE )
ORDER BY c.id;

Resources