Getting difference between two dates from 2 REF'd columns - sql

I'm new to SQL and I'm trying to get the date between 2 values between two REF'd column values.
I currently have the tables set up like below:
CREATE OR REPLACE TYPE Person_Type AS OBJECT
(PersonId NUMBER,
DateBorn DATE)
CREATE OR REPLACE TYPE Movie_Type AS OBJECT
(MovieId NUMBER,
ReleaseDate DATE)
CREATE OR REPLACE TYPE Role_Type AS OBJECT
(PersonId REF Person_Type,
MovieId REF Movie_Type,
Name VARCHAR2(40),
MAP MEMBER FUNCTION Actor_Age RETURN NUMBER)
The data is inserted like this:
INSERT INTO Person_Table
VALUES (10000, '11-NOV-1974')
INSERT INTO Movie_Table
VALUES(1000000, '19-DEC-1997')
INSERT INTO Role_Table
VALUES((SELECT REF(a) FROM Person_Table a WHERE a.PersonId = 10000),
(SELECT REF(b) FROM Movie_Table b WHERE b.MovieId = 1000000),
'Some Person')
I'm trying to get the method Actor_Age to return the difference between the day someone is born and the date the movie got released. I've tried the below but it won't compile..
CREATE OR REPLACE TYPE BODY Role_Type
AS
MAP MEMBER FUNCTION Actor_Age
RETURN NUMBER
IS
BEGIN
RETURN (MONTHS_BETWEEN((SELECT g.PersonId.DateBorn FROM Role_Table g),
(SELECT f.MovieId.Releasedate FROM Role_Table f)/12));
END;
END;

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE OR REPLACE TYPE Person_Type AS OBJECT
(PersonId NUMBER,
DateBorn DATE)
/
CREATE OR REPLACE TYPE Movie_Type AS OBJECT
(MovieId NUMBER,
ReleaseDate DATE)
/
CREATE OR REPLACE TYPE Role_Type AS OBJECT
(PersonId REF Person_Type,
MovieId REF Movie_Type,
Name VARCHAR2(40),
MAP MEMBER FUNCTION Actor_Age RETURN NUMBER)
/
CREATE OR REPLACE TYPE BODY Role_Type
AS
MAP MEMBER FUNCTION Actor_Age
RETURN NUMBER
IS
p_age NUMBER;
BEGIN
SELECT MONTHS_BETWEEN(
DEREF( MovieID ).ReleaseDate,
DEREF( PersonID ).DateBorn
) / 12
INTO p_age
FROM DUAL;
RETURN p_age;
END;
END;
/
CREATE TABLE Person_Table OF Person_Type
/
CREATE TABLE Movie_Table OF Movie_Type
/
CREATE TABLE Role_Table OF Role_Type
/
INSERT INTO Person_Table VALUES ( 10000, DATE '1970-01-01' )
/
INSERT INTO Movie_Table VALUES ( 1000000, DATE '2000-01-01' )
/
INSERT INTO Role_Table VALUES(
(SELECT REF(a) FROM Person_Table a WHERE a.PersonId = 10000),
(SELECT REF(b) FROM Movie_Table b WHERE b.MovieId = 1000000),
'Some Person'
)
/
Query 1:
SELECT Name,
r.Actor_Age()
FROM Role_table r
Results:
| NAME | R.ACTOR_AGE() |
|-------------|---------------|
| Some Person | 30 |

In pl sql you have to use construction select into
CREATE OR REPLACE TYPE BODY Role_Type
AS
MAP MEMBER FUNCTION Actor_Age
RETURN NUMBER
IS
v_date_born date ;
v_realse_date date ;
BEGIN
SELECT deref(self.PersonId).DateBorn into v_date_born FROM dual;
SELECT deref(self.MovieId).Releasedate into v_realse_date FROM dual;
RETURN (MONTHS_BETWEEN(v_date_born,v_realse_date)/12);
END;
END;

Related

Procedure to insert data if not exist pl sql

I have a table with these columns (id,first_name,birth). I want to create a procedure that insert a new customer only if the id inserted doesn't exist in the table. If it already exist, then don't insert it. This is my code so far, but I got an error 'line 3 sql statement ignored'. Any idea? I need to use procedure and pl sql in oracle. Thanks!
CREATE OR REPLACE PROCEDURE add_emp(v_id IN int,
v_name IN varchar2,
v_bday IN date) IS
BEGIN
INSERT INTO Employees
(Id, First_name, Birth)
SELECT *
FROM (SELECT v_id, v_name, v_bday) AS tmp
WHERE NOT EXISTS (SELECT Id FROM Employees WHERE Id = v_id);
END;
/
DECLARE
m_id int := 3;
m_name varchar2 := 'John';
m_bday date := '16-Dec-1990';
BEGIN
add_cust(m_id, m_name, m_bday);
END;
/
Your procedure has some syntax issue which is fixed in following code:
CREATE OR REPLACE PROCEDURE ADD_EMP (
V_ID IN INT,
V_NAME IN VARCHAR2,
V_BDAY IN DATE
) IS
BEGIN
INSERT INTO EMPLOYEES (
ID,
FIRST_NAME,
BIRTH
)
SELECT V_ID,
V_NAME,
V_BDAY
FROM DUAL -- FROM clause was missing
WHERE NOT EXISTS (
SELECT ID
FROM EMPLOYEES
WHERE ID = V_ID
);
END;
/
Also, Your calling PL/SQL block has some issues which are corrected in the following code:
DECLARE
M_ID INT := 3;
M_NAME VARCHAR2(10) := 'John'; -- varchar2 must be declared with size
M_BDAY DATE := DATE '1990-12-16'; -- added date literal to convert string to date
BEGIN
ADD_CUST(M_ID, M_NAME, M_BDAY);
END;
/
In Oracle SELECT does not work without a FROM clause (other DBMS products are different). So you need to provide a table; you can use DUAL, which is a dummy table provided by Oracle which is guaranteed to return one row.
INSERT INTO Employees(Id,First_name,Birth)
SELECT v_id, v_name, v_bday
from dual
WHERE NOT EXISTS (
SELECT Id FROM Employees WHERE Id = v_id
);
Your INSERT statement would work with a slight change
CREATE OR REPLACE PROCEDURE add_emp(v_id Employees.Id%type,
v_name Employees.First_name%type,
v_bday Employees.Birth%type) IS
BEGIN
INSERT INTO Employees
(Id, First_name, Birth)
SELECT v_id, v_name, v_bday
FROM dual
WHERE NOT EXISTS (SELECT * FROM Employees WHERE Id = v_id);
END;
/
You can replace INSERT statement with MERGE as an alternative DML such as
MERGE INTO Employees e1
USING
(SELECT v_id AS id, v_name AS First_name, v_bday AS birth
FROM dual) e2
ON ( e1.id = e2.id )
WHEN MATCHED THEN UPDATE SET e1.First_name = e2.First_name,
e1.Birth = e2.Birth -- If you need to change for the matching case
WHEN NOT MATCHED THEN INSERT( e1.id, e1.First_name, e1.birth )
VALUES( e2.id, e2.First_name, e2.birth );

Error in Initializing oracle collection

I have following self contained code which gives following error. I don't seem to find where do I have to initialize the collection.
create or replace type address_type is object
(
address_line varchar2(100),
city varchar2(100),
state varchar2(100)
)
/
create or replace type emp_rec_type is object
(
emp_name varchar2(100),
addr_rec address_type
)
/
create or replace type emp_arr is table of emp_rec_type
/
create table employees
(
emp_name varchar2(100),
addr_rec address_type
)
/
insert into employees values ( 'dave', address_type ( '30 br','br','nj'));
commit;
-- Create a function to return an array
create or replace function get_emp_rec ( p_name in varchar2 )
return
emp_arr
is
i integer;
l_arr emp_arr := emp_arr();
begin
i := 1;
l_arr.extend;
l_arr(l_arr.last).emp_name := 'a';
l_arr(l_arr.last).addr_rec := address_type ( 'a','b','c');
return l_arr;
end;
/
select emp_name from table ( get_emp_rec( 'dave' ))
* ERROR at line 1: ORA-06530: Reference to uninitialized composite ORA-06512: at "DBADMIN.GET_EMP_REC", line
22
You have to initialize emp_rec_type in the collection.
These two line
l_arr(l_arr.last).emp_name := 'a';
l_arr(l_arr.last).addr_rec := address_type ( 'a','b','c');
replace with this linie
l_arr(l_arr.last) := emp_rec_type('a', address_type ( 'a','b','c'));

how can i access a value that's being updated by a trigger?

here's the value of ACCOUNT_NUMBER that has been generated by a sequence and inserted in ACCOUNTS table by ACCOUNT_NUMBER_TRIG trigger that i need to insert it into the TRANSACTION TABLE by the trigger ACCOUNTS_TRANSCATION_TRIG_1
CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG
BEFORE INSERT
ON ACCOUNTS
FOR EACH ROW
WHEN (NEW.ACCOUNT_NUMBER is not null)
DECLARE
V_ACC_NO ACCOUNTS.ACCOUNT_NUMBER%TYPE;
BEGIN
SELECT ACCOUNT_NO_SEQ.nextvaL INTO V_ACC_NO FROM DUAL;
:NEW.ACCOUNT_NUMBER := V_ACC_NO;
END ACCOUNT_NUMBER_TRIG;
------------------------------------------------------------------------------
CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER
INSERT ON ACCOUNTS FOR EACH ROW DECLARE CURSOR ACCOUNTS_CUR IS
SELECT ACCOUNT_NUMBER FROM ACCOUNTS;
DECLARE
TEMP_1 NUMBER(5,0);
BEGIN
SELECT ACCOUNTS.ACCOUNT_NUMBER FROM INSERTED INTO TEMP_1
OPEN ACCOUNTS_CUR;
INSERT
INTO TRANSACTIONS VALUES
(
SYSDATE,
- :NEW.ACCOUNT_NUMBER,
'NEW ACCOUNT',
0
);
CLOSE ACCOUNTS_CUR;
END ACCOUNTS_TRANSCATION_TRIG_1;
CREATE TABLE accounts(
ACCOUNT_NUMBER number,
ACCOUNT_NAME varchar2(20)
);
CREATE SEQUENCE ACCOUNT_NO_SEQ;
CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG
BEFORE INSERT
ON ACCOUNTS
FOR EACH ROW
WHEN (NEW.ACCOUNT_NUMBER is not null)
BEGIN
:NEW.ACCOUNT_NUMBER :=ACCOUNT_NO_SEQ.nextvaL;
END ACCOUNT_NUMBER_TRIG;
/
CREATE TABLE transactions(
TR_DATE date,
TR_ACCOUNT_NUMBER number,
TR_TYPE varchar2(20),
TR_somenumber int
);
CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER
INSERT ON ACCOUNTS FOR EACH ROW
BEGIN
INSERT INTO TRANSACTIONS( TR_DATE, TR_ACCOUNT_NUMBER, TR_TYPE, TR_somenumber )
VALUES
(
SYSDATE,
:NEW.ACCOUNT_NUMBER,
'NEW ACCOUNT',
0
);
END ACCOUNTS_TRANSCATION_TRIG_1;
/
INSERT INTO accounts( ACCOUNT_NUMBER, ACCOUNT_NAME ) VALUES (1111,'My Name' );
select * from accounts;
ACCOUNT_NUMBER ACCOUNT_NAME
-------------- --------------------
2 My Name
select * from transactions;
TR_DATE TR_ACCOUNT_NUMBER TR_TYPE TR_SOMENUMBER
---------- ----------------- -------------------- -------------
2017/07/11 2 NEW ACCOUNT 0
You can use CURVAL to get the most recent value returned by NEXTVAL:
CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER
INSERT ON ACCOUNTS FOR EACH ROW DECLARE CURSOR ACCOUNTS_CUR IS
BEGIN
INSERT
INTO TRANSACTIONS VALUES
(
SYSDATE,
- ACCOUNT_NO_SEQ.curval,
'NEW ACCOUNT',
0
);
CLOSE ACCOUNTS_CUR;
END ACCOUNTS_TRANSCATION_TRIG_1;
However in this case there is no need, as it has been used to set the ACOUNT_NUMBER:
INSERT
INTO TRANSACTIONS VALUES
(
SYSDATE,
- :NEW.ACCOUNT_NUMBER,
'NEW ACCOUNT',
0
);
BTW unless you are on an old version of Oracle this should work for first trigger:
CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG
BEFORE INSERT
ON ACCOUNTS
FOR EACH ROW
WHEN (NEW.ACCOUNT_NUMBER is not null)
BEGIN
:NEW.ACCOUNT_NUMBER := ACCOUNT_NO_SEQ.nextvaL;
END ACCOUNT_NUMBER_TRIG;
(I suspect the WHEN clause is wrong - should be when is null?)

PL/SQL: ORA-12704: character set mismatch when insert varchar2 to nvarchar2

I try to populate a table TBL in a loop. And get ORA-12704: character set mismatch on a subquery.
Here is a query I use:
BEGIN
FOR i IN (SELECT t.Stuff FROM STUFF_TABLE t ORDER BY t.Name ASC)
LOOP
INSERT INTO TBL(StuffId, StuffName)
VALUES(
i.Stuff,
(SELECT TempStuffName FROM
(SELECT COALESCE(st.StuffName, i.Stuff) as TempStuffName FROM STUFFDEFINITION st WHERE st.Stuff = i.Stuff ORDER BY st.Version DESC)
WHERE ROWNUM = 1)
);
END LOOP;
END;
The columns types are the following:
STUFF_TABLE.Stuff nvarchar2(30)
TBL.StuffId nvarchar2(30)
TBL.StuffName nvarchar2(50)
STUFFDEFINITION.Stuff varchar2(255)
STUFFDEFINITION.StuffName varchar2(255)
The issue, as I understand it, is in type casting namely from varchar2(255) to nvarchar2(50).
I tried to use CAST, Translate functions, but it didn't help. The ORA-12704: character set mismatch still occurs.
Is there a way to populate TBL in a loop as I try it to do?
Below is the test data to reproduce issue I talk about:
CREATE TABLE STUFF_TABLE
(
Stuff nvarchar2(30),
Name nvarchar2(50)
);
CREATE TABLE TBL
(
StuffId nvarchar2(30),
StuffName nvarchar2(50)
);
CREATE TABLE STUFFDEFINITION
(
Stuff varchar2(255),
StuffName varchar2(255),
Version number(19)
)
INSERT INTO STUFF_TABLE(Stuff, Name) VALUES('First', 'Name1');
INSERT INTO STUFF_TABLE(Stuff, Name) VALUES('Second', 'Name2');
INSERT INTO STUFF_TABLE(Stuff, Name) VALUES('Third', 'Name3');
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('First', 'First Stuff', 1);
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('First', 'First Stuff', 2);
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('Second', 'Second Stuff', 1);
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('Third', 'Third Stuff', 1);
From your statement its clear that you are joining a varchar2 column with nvarchar2 and inserting a varchar2 column to a nvarchar2 column. You need to do a conversion first. Try this:
BEGIN
FOR i IN (SELECT t.Stuff FROM STUFF_TABLE t ORDER BY t.Name ASC)
LOOP
INSERT INTO TBL(StuffId, StuffName)
VALUES(
i.Stuff,
(SELECT to_nchar(TempStuffName)
FROM
(SELECT COALESCE(to_nchar(st.StuffName), i.Stuff) as TempStuffName
FROM STUFFDEFINITION st
WHERE to_nchar(st.Stuff) = i.Stuff
ORDER BY st.Version DESC)
WHERE ROWNUM = 1)
);
END LOOP;
END;
You shouldn't need to use PL/SQL:
INSERT INTO TBL ( StuffId, StuffName )
SELECT TO_NCHAR( t.stuff ),
TO_NCHAR(
COALESCE(
MAX( d.StuffName ) KEEP ( DENSE_RANK LAST ORDER BY d.version ),
t.stuff
)
)
FROM StuffTable t
LEFT OUTER JOIN StuffDefinition d
ON ( t.stuff = TO_NCHAR( d.stuff ) )
GROUP BY t.stuff;
Oracle says this about that error: 'A string expression in the VALUES clause of an INSERT statement does not have the same character set as the column into which the value would be inserted.'
Please select from nls_database_parameters and tell me what the values of these are:
NLS_CHARACTERSET
NLS_NCHAR_CHARACTERSET
NLS_RDBMS_VERSION
NLS_LENGTH_SEMANTICS

SQL: Stored procedure

I am implementing a library management system in SQL. I have the following table structure and some values inserted in them:
create table books
(
IdBook number(5),
NameBook varchar2(35),
primary key(IdBook)
);
create table users
(
IdUsers number(5),
NameUser varchar2(20),
primary key(IdUsers)
);
create table borrowed
(
IdBorrowed number(5),
IdUsers number(5),
IdBook number(5),
DueDate date,
DateReturned date,
constraint fk_borrowed foreign key(IdUsers) references users(IdUsers),
constraint fk_borrowed2 foreign key(IdBook) references books(IdBook)
);
insert into books values(0,'FairyTale');
insert into books values(1,'Crime and Punishment');
insert into books values(2,'Anna Karenina');
insert into books values(3,'Norwegian Wood');
insert into users values(01,'Robb Dora');
insert into users values(02,'Pop Alina');
insert into users values(03,'Grozavescu Teodor');
insert into users values(04,'Popa Alin');
insert into borrowed values(10,02,3,'22-Jan-2017',null);
insert into borrowed values(11,01,1,'25-Jan-2017','19-Dec-2016');
insert into borrowed values(12,01,3,'22-Jan-2017',null);
insert into borrowed values(13,04,2,'22-Jan-2017','13-Dec-2016');
What I want now is that my db to allow "borrowing" books for the users(i.e insert into the borrowed table) that have no unreturned books(i.e date returned is not null) and if they have unreturned books I want to abandon the whole process. I thought to implement this in the following way:
create or replace procedure borrowBook(IdBorrowed in number,IdUsers number,IdBook number,DueDate date,DateReturned date) as begin
if exists (SELECT u.IdUsers, u.NameUser, b.DateReturned
FROM users u, borrowed b
WHERE u.IDUSERS = b.IdUsers and DateReturned is not null),
insert into borrowed values(IdBorrowed,IdUsers,IdBook,DueDate,DateReturned);
end borrowBook;
The above procedure does not check if the parameter I pass to this function is the same as the one in my select and I do not know how to do this and correctly insert a value in my table.
Any help would be much appreciated. Thank in advance!
You should not name your parameters the same as columns also used inside the procedure.
You can also simplify your procedure to a single INSERT statement, no IF required:
create or replace procedure borrowBook(p_idborrowed in number, p_idusers number, p_idbook number, p_duedate date, p_datereturned date)
as
begin
insert into borrowed (idborrowed, idusers, idbook, duedate, datereturned)
select p_idborrowed, p_idusers, p_idbook, p_duedate, p_datereturned
from dual
where not exists (select *
from users u
join borrowed b on u.idusers = b.idusers
and b.datereturned is not null);
end borrowBook;
It's also good coding style to explicitly list the columns for an INSERT statement. And you should get used to the explicit JOIN operator instead of using implicit joins in the where clause.
What about this one:
create or replace procedure borrowBook( p_IdBorrowed in number ,
p_IdUsers number ,
p_IdBook number ,
p_DueDate date ,
p_DateReturned date )
as
begin
if (SELECT COUNT(*)
FROM borrowed
WHERE IDUSERS = p_IdUsers
AND DateReturned IS NULL) = 0 THEN
insert into borrowed values (p_IdBorrowed ,
p_IdUsers ,
p_IdBook ,
p_DueDate ,
p_DateReturned );
end if ;
end borrowBook;
You would seem to want something like this:
create or replace procedure borrowBook (
in_IdBorrowed in number,
in_IdUsers number,
in_IdBook number,
in_DueDate date,
in_DateReturned date
) as
v_flag number;
begin
select (case when exists (select 1
from borrowed b
where b.IdUsers = in_IdUsers and b.DateReturned is not null
)
then 1 else 0
end)
into v_flag
from dual;
if (flag = 0) then
insert into borrowed
values(in_IdBorrowed, in_IdUsers, in_IdBook, in_DueDate, v_DateReturned);
end if
end -- borrowBook;