insertion in a table of objects with nested table in oracle - oracle

I have a problem inserting in a nested table in oracle
These are the relevant types and tables;
create type movies_type as Table of ref movie_type;
create type actor_type under person_type
(
starring movies_type
) Final;
create table actor of actor_type
NESTED TABLE starring STORE AS starring_nt;
this is how i tried to insert
insert into actor values
(actor_type(29,'Carrie','Fisher',TO_DATE('21/10/1956', 'DD/MM/YY'),TO_DATE('27/12/2016', 'DD/MM/YY'),'USA', movies_type(select ref(m) from movie m where movie_id in (7, 8, 9))));
this doesn't work, it gives
SQL Error: ORA-00936: missing expression
which isn't very helpful.
i also tried nesting the select statement in parenthesis because i thought it might have been a syntax error
insert into actor values
(actor_type(29,'Carrie','Fisher',TO_DATE('21/10/1956', 'DD/MM/YY'),TO_DATE('27/12/2016', 'DD/MM/YY'),'USA', movies_type((select ref(m) from movie m where movie_id in (7, 8, 9)))));
but it said
SQL ERROR ORA-01427: single-row subquery returns more than one row
so i changed it to this
insert into actor values
(actor_type(29,'Carrie','Fisher',TO_DATE('21/10/1956', 'DD/MM/YY'),TO_DATE('27/12/2016', 'DD/MM/YY'),'USA', movies_type((select ref(m) from movie m where movie_id=7))));
which worked but it isn't what i want since it doesn't allow me to have multiple values in
movies_type
i don't understand what the problem is exactly and the errors messages aren't helpful
why does it say missing expression?
and why in the second case it gives single-row subquery returns more than one row?
thank you very much.

You want to use CAST and COLLECT to aggregate the references into a collection:
insert into actor values(
actor_type(
29,
'Carrie',
'Fisher',
DATE '1956-10-21',
DATE '2016-12-27',
'USA',
(
SELECT CAST( COLLECT(REF(m)) AS movies_type )
FROM movie m
WHERE movie_id IN (7, 8, 9)
)
)
);
db<>fiddle here
insert into actor values (actor_type(29,'Carrie','Fisher',TO_DATE('21/10/1956', 'DD/MM/YY'),TO_DATE('27/12/2016', 'DD/MM/YY'),'USA', movies_type(select ref(m) from movie m where movie_id in (7, 8, 9))));
why does it say missing expression?
Because you have movie_type() where the () brackets are for the object's constructor and then inside you have a SELECT statement that is nested inside another statement that is not surrounded in () brackets (as the surrounding brackets are for the constructor).
so i changed it to this
insert into actor values (actor_type(29,'Carrie','Fisher',TO_DATE('21/10/1956', 'DD/MM/YY'),TO_DATE('27/12/2016', 'DD/MM/YY'),'USA', movies_type((select ref(m) from movie m where movie_id=7))));
and why in the second case it gives single-row subquery returns more than one row?
Because, when you use WHERE movie_id IN (7,8,9) the query is returning 3 rows and it needs to return only a single row; for that you need to aggregate the multiple rows into a single row containing a collection.
what's wrong with multiple rows as parameters to the constructor?
The constructor expects a single item in each argument so you would need to use:
insert into actor values (
actor_type(
29,
'Carrie',
'Fisher',
DATE '1956-10-21',
DATE '2016-12-27',
'USA',
movies_type(
(select ref(m) from movie m where movie_id=7),
(select ref(m) from movie m where movie_id=8),
(select ref(m) from movie m where movie_id=9)
)
)
);
This would work (assuming movie_id is unique; if it isn't and things like the theatrical version and the director's cut of the same movie have the same movie_id then it still would raise exceptions) but it may use a table/index scans for each SELECT and would be less efficient than collating all the rows in a single SELECT using CAST/COLLECT.

Related

PL\SQL - Fetch values from table and Item - Oracle Apex

I need to insert data of one table to another table. All the values are from the table except one SO_ID. It is coming from the Item on the page. How do I do it?
insert into T_SORDER_ITEM_INWARD
(
select sd.ID, SO_ID
into :P25_SO_ID, sd.STOCK_ID,sd.ITEM_ID,sd.UOM_ID,
sd.ITEM_CONDITION_ID,sd.ORIGINAL,sd.ACTUAL,sd.WIDTH,sd.LENGTH,sd.STOCKQTY,
sd.KANTA,sd.RATE,sd.PACKET, sd.LABEL_METER, sd.EXCESS_SHORT,sd.LOCATION_ID,
sd.CLIENT_INITIAL, sd.FIN_YEAR, sd.SERIAL_NO
from T_STOCK_DETAIL sd join t_stock_master sm
on sd.stock_id = sm.stock_id
where sm.customer_id = p25_customer
)
A simplified example:
insert into another_table (id, name, location)
select :P25_SO_ID,
t.name,
t.location
from this_table t
Always name all columns you're inserting into (first line in my example).
Your query is impossible to understand. Not just because syntax is wrong, but because we have no idea which column is supposed to get which value.

Oracle CLOB column and LAG

I'm facing a problem when I try to use LAG function on CLOB column.
So let's assume we have a table
create table test (
id number primary key,
not_clob varchar2(255),
this_is_clob clob
);
insert into test values (1, 'test1', to_clob('clob1'));
insert into test values (2, 'test2', to_clob('clob2'));
DECLARE
x CLOB := 'C';
BEGIN
FOR i in 1..32767
LOOP
x := x||'C';
END LOOP;
INSERT INTO test(id,not_clob,this_is_clob) values(3,'test3',x);
END;
/
commit;
Now let's do a select using non-clob columns
select id, lag(not_clob) over (order by id) from test;
It works fine as expected, but when I try the same with clob column
select id, lag(this_is_clob) over (order by id) from test;
I get
ORA-00932: inconsistent datatypes: expected - got CLOB
00932. 00000 - "inconsistent datatypes: expected %s got %s"
*Cause:
*Action:
Error at Line: 1 Column: 16
Can you tell me what's the solution of this problem as I couldn't find anything on that.
The documentation says the argument for any analytic function can be any datatype but it seems unrestricted CLOB is not supported.
However, there is a workaround:
select id, lag(dbms_lob.substr(this_is_clob, 4000, 1)) over (order by id)
from test;
This is not the whole CLOB but 4k should be good enough in many cases.
I'm still wondering what is the proper way to overcome the problem
Is upgrading to 12c an option? The problem is nothing to do with CLOB as such, it's the fact that Oracle has a hard limit for strings in SQL of 4000 characters. In 12c we have the option to use extended data types (providing we can persuade our DBAs to turn it on!). Find out more.
Some of the features may not work properly in SQL when using CLOBs(like DISTINCT , ORDER BY GROUP BY etc. Looks like LAG is also one of them but, I couldn't find anywhere in docs.
If your values in the CLOB columns are always less than 4000 characters, you may use TO_CHAR
select id, lag( TO_CHAR(this_is_clob)) over (order by id) from test;
OR
convert it into an equivalent SELF JOIN ( may not be as efficient as LAG )
SELECT a.id,
b.this_is_clob AS lagging
FROM test a
LEFT JOIN test b ON b.id < a.id;
Demo
I know this is an old question, but I think I found an answer which eliminates the need to restrict the CLOB length and wanted to share it. Utilizing CTE and recursive subqueries, we can replicate the lag functionality with CLOB columns.
First, let's take a look at my "original" query:
WITH TEST_TABLE AS
(
SELECT LEVEL ORDER_BY_COL,
TO_CLOB(LEVEL) AS CLOB_COL
FROM DUAL
CONNECT BY LEVEL <= 10
)
SELECT tt.order_by_col,
tt.clob_col,
LAG(tt.clob_col) OVER (ORDER BY tt.order_by_col)
FROM test_table tt;
As expected, I get the following error:
ORA-00932: inconsistent datatypes: expected - got CLOB
Now, lets look at the modified query:
WITH TEST_TABLE AS
(
SELECT LEVEL ORDER_BY_COL,
TO_CLOB(LEVEL) AS CLOB_COL
FROM DUAL
CONNECT BY LEVEL <= 10
),
initial_pull AS
(
SELECT tt.order_by_col,
LAG(tt.order_by_col) OVER (ORDER BY tt.order_by_col) AS PREV_ROW,
tt.clob_col
FROM test_table tt
),
recursive_subquery (order_by_col, prev_row, clob_col, prev_clob_col) AS
(
SELECT ip.order_by_col, ip.prev_row, ip.clob_col, NULL
FROM initial_pull ip
WHERE ip.prev_row IS NULL
UNION ALL
SELECT ip.order_by_col, ip.prev_row, ip.clob_col, rs.clob_col
FROM initial_pull ip
INNER JOIN recursive_subquery rs ON ip.prev_row = rs.order_by_col
)
SELECT rs.order_by_col, rs.clob_col, rs.prev_clob_col
FROM recursive_subquery rs;
So here is how it works.
I create the TEST_TABLE, this really is only for the example as you should already have this table somewhere in your schema.
I create a CTE of the data I want to pull, plus a LAG function on the primary key (or a unique column) in the table partitioned and ordered in the same way I would have in my original query.
Create a recursive subquery using the initial row as the root and descending row by row joining on the lagged column. Returning both the CLOB column from the current row and the CLOB column from its parent row.

Fetch data from a table in oracle sql

I've two table in the database, the first one is Person and the second is Pilot. as following:
Person Table:
CREATE TABLE person(
person_id NUMBER PRIMARY KEY,
last_name VARCHAR2(30) NOT NULL,
first_name VARCHAR2(30) NOT NULL,
hire_date VARCHAR2(30) NOT NULL,
job_type CHAR NOT NULL,
job_status CHAR NOT NULL
);
/
INSERT INTO person VALUES (1000, 'Smith', 'Ryan', '04-MAY-90','F', 'I');
INSERT INTO person VALUES (1170, 'Brown', 'Dean', '01-DEC-92','P', 'A');
INSERT INTO person VALUES (2010, 'Fisher', 'Jane', '12-FEB-95','F', 'I');
INSERT INTO person VALUES (2080, 'Brewster', 'Andre', '28-JUL-98', 'F', 'A');
INSERT INTO person VALUES (3190, 'Clark', 'Dan', '04-APR-01','P', 'A');
INSERT INTO person VALUES (3500, 'Jackson', 'Tyler', '01-NOV-05', 'F', 'A');
INSERT INTO person VALUES (4000, 'Miller', 'Mary', '11-JAN-08', 'F', 'A');
INSERT INTO person VALUES (4100, 'Jackson', 'Peter', '08-AUG-11', 'P','I');
INSERT INTO person VALUES (4200, 'Smith', 'Ryan', '08-DEC-12', 'F','A');
COMMIT;
/
Pilot Table:
CREATE TABLE pilot(
person_id NUMBER PRIMARY KEY,
pilot_type VARCHAR2(100) NOT NULL,
CONSTRAINT fk_person_pilot FOREIGN KEY (person_id)
REFERENCES person(person_id)
);
/
INSERT INTO pilot VALUES (1170, 'Commercial pilot');
INSERT INTO pilot VALUES (2010, 'Airline transport pilot');
INSERT INTO pilot VALUES (3500, 'Airline transport pilot');
COMMIT;
/
I'm asked to write a pl/sql block of code that accepts the last name from the user and return the result as following:
1) if the last name is not in the table, it returns all the rows in the table.
2) if the last name is in the table, it shows all of the employee's information.
So far I'm doing well with the code, but I got stuck in the case that there are two employees with the last name. here is the cursor that I wrote:
cursor person_info is
select last_name, first_name, hire_date, job_type, job_status, nvl(pilot_type, '-----------')
from person
full outer join pilot
on person.person_id = pilot.person_id
where upper(last_name) = upper(v_last_name)
group by last_name, first_name, hire_date, job_type, job_status, pilot_type
order by last_name, first_name, hire_date asc;
Logically, there are three cases to be covered:
the first case, when the entered last name is in the table, I return all the rows in the table and that's done.
The second case when there is only one employee with the entered last name, and this case is done as well. The last case when there are more than one employee having the same last name like for example 'Jackson' or 'Smith' in this case, my program crashes and give me the error that my select into statement returns more than one row.
select person_id
into v_n
from person
where upper(last_name) = upper(v_last_name);
if v_n = 1 then
open person_info;
fetch person_info into v_last_name, v_first_name, v_hire_date, v_job_type, v_job_status, v_pilot_type;
Can someone help me in guiding me how to fetch the data correctly? I'm not allowed to create any temporary tables or views.
I'm so sorry for making the problem longer than it should, but I was trying to be as clear as possible in explaining the problem.
Thank you in advance.
if the error is
"ORA-01422 exact fetch returns more than requested number of rows" then I think your answer is here https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:981494932508
If you EXPECT the query to return more than one row, you would code:
for x in ( select * from t where ... )
loop
-- process the X record here
end loop;
Your immediate issue is that you're selecting the matching person_id into a variable, and then seeing if that specific ID is 1. You don't have an actual ID 1 anyway so that check would never match; but it is that querying matching multiple rows that gets the error, as you can't put two matching IDs into a single scalar variable.
The way you've structured it looks like you are trying to count how many matching rows there are, rather than looking for a specific ID:
select count(person_id)
into v_n
from person
where upper(last_name) = upper(v_last_name);
if v_n = 1 then
....
When you do have multiple matches then you will need to use the same mechanism to return all of those as you do when there are no matches and you return all employees. You may find the logic should be in the cursor query rather then in PL/SQL logic. It depends on the details of the assignment though, and how it expects you to return the data in both (or all three) scenarios.
It's also possible you just aren't expected to hit this problem - it isn't clear if the assignment is finding all employees, or only those that are pilots. The issue still exists in general, but with the data you show there aren't any duplicate pilot last names. If you haven't learned about this kind of error yet perhaps you're getting a bit ahead of what your tutor expects.

How to apply SUM to one record based on the value in a column - PL/SQL

I am trying to sum the values of one record based on the value in another column. I will try to explain using a dummy table and data.
table creation code:
create table test_prj_linking_t
(
prj_name varchar2(10),
prj_code varchar2(20),
prj_alt_code varchar2(50),
prj_bud1 number,
prj_bud2 number
)
insert statement:
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_A','A123','B123,C123',100,100);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_B','B123',null,200,200);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_C','C123',null,50,50);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_ALT_CODE,PRJ_BUD1,PRJ_BUD2) values ('prj_D','D123',null,70,70);
Intended Output: for a project whose prj_alt_code has prj_code of other projects, bud value should be added
For Prj_A, bud1 should come as 350, bud2 as 350 (sum of the bud values of prj_A,prj_B,prj_C)
For Prj_b, Prj_C, Prj_D there will be no summation of values
I was trying with something like this:
select prj_name,prj_code,
case
when prj_alt_code is not null
then
(select sum(t1.prj_bud1)
from test_prj_linking_t t1, test_prj_linking_t t2
where t1.prj_code=t2.prj_alt_code)
when prj_alt_code is null
then
(select t1.prj_bud1
from test_prj_linking_t t1)
end bud
from test_prj_linking_t
but this does not work when there are multiple records with NULL value for prj_alt_code, neither for a prj_alt_code having comma separated values (ex: B123,C123).
Please let me know how to do this in a select query.
Thanks & Regards,
Bishal Mandal
you should to change your data model to keep all attributes separatelly
create table test_prj_linking_t
(
prj_name varchar2(10),
prj_code varchar2(20),
prj_code_parent varchar2(20),
prj_bud1 number,
prj_bud2 number
)
after that, insert each prj_code with its parent separately ... it means:
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_A','A123',null,100,100);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_B','B123','A123',200,200);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_C','C123','A123',50,50);
Insert into TEST_PRJ_LINKING_T (PRJ_NAME,PRJ_CODE,PRJ_CODE_PARENT,PRJ_BUD1,PRJ_BUD2) values ('prj_D','D123',null,70,70);
this solution should solve your problem
Thank you for your inputs !! I agree, putting multiple values in a cell is not a good approach. However, this approach has been implemented for a long time in this case and I am not allowed to change it. So, I went ahead with regexp_substr to handle the multiple values :
(select regexp_substr(t2.prj_alt_code,'[^,]+', 1, level) from dual
connect by regexp_substr(t2.prj_alt_code, '[^,]+', 1, level) is not null)
and a combination of union all prj_alt_code=null and prj_alt_code is not null to achieve the intended result.

how to insert bulk records over 20000 into the db instead using the CURSOR?

Hi actually m trying to insert the bulk records into the DB.
Records are more than the 20000.
I have to insert the records into three tables since these records are corelated.
For example :
1. One sort of data into the Table 1.
1.1 another sort of data into the Table 2.
1.1.1 another sort of data into the Table 3.
we can consider the above statement as nested lenter code hereoop.
Currently m using the Cursor for above approach but it is taking v v v v long time.
waiting for ur valuable suggestion....
It sounds like you want an INSERT ALL statement. Something like
INSERT ALL
WHEN (<<some condition>>) THEN
INTO table1( <<list of columns>> )
VALUES( <<list of columns>> )
WHEN (<<another condition>>) THEN
INTO table2( <<list of columns>> )
VALUES( <<list of columns>> )
WHEN (<<third condition>>) THEN
INTO table3( <<list of columns>> )
VALUES( <<list of columns>> )
SELECT <<list of columns>>
FROM <<source tables>>
WHERE <<some predicates>>
The SELECT statement at the end would generally be whatever query you're using to populate the cursor. The conditions would implement whatever logic you implement inside the loop to determine which table to insert the data into.
If you know that a row from the cursor will always be inserted into a single table, you could use an INSERT FIRST rather than an INSERT ALL (the rest of the syntax remains the same) so that Oracle can stop evaluating conditions once the first condition evaluates to TRUE.

Resources