I'm stuck figuring out how to add some constraints to the attributes of my UDTs.
Here's my situation. I've got a UDT that represents the daily number of hours an employee should work (forgive me for using Italian names).
CREATE OR REPLACE TYPE TURNO_GIORNALIERO AS OBJECT(
GIORNO VARCHAR(15),
ORA_INIZIO DATE,
NUMERO_ORE NUMBER,
MEMBER FUNCTION getOreLavoro RETURN NUMBER
);
Then I defined new Type as a VARRAY(5) of TURNO_GIORNALIERO, named TURNI_SETTIMANALI (that is a Varray containing the number of hours an employee should work for each day of the week).
CREATE OR REPLACE TYPE TURNI_SETTIMANALI AS VARRAY(5) OF TURNO_GIORNALIERO;
In the end, I've created the table that contains TURNI_SETTIMANALI.
CREATE TABLE TURNO_LAVORO(
ID_TURNO CHAR(9) PRIMARY KEY,
TURNO TURNI_SETTIMANALI NOT NULL,
);
What I want to do is to add a constraint to the table TURNO_LAVORO in order to check if NUMERO_ORE (defined in TURNO GIORNALIERO) is greater than 5.
Could someone please help me? I've tried several solutions but nothing worked.
I doubt it is possible in check constraint. Documentation says:
Conditions of check constraints cannot contain the following
constructs:
Calls to user-defined functions
Nested table columns or attributes
You could use trigger however. Test types and table:
create or replace type daily_cycle
as object(day varchar(15), hour_start date, hour_count number);
create or replace type week_cycle
as varray(5) of daily_cycle;
create table test(id int, shift week_cycle);
Trigger:
create or replace trigger hour_check before insert on test for each row
begin
for i in 1..:new.shift.count() loop
if :new.shift(i).hour_count < 5 then
raise_application_error(-20001,'hours less than 5');
end if;
end loop;
end;
First insert works, second no:
insert into test values (1,
week_cycle(daily_cycle('a', date '2017-12-01', 5),
daily_cycle('b', date '2017-12-02', 8) ) );
insert into test values (2,
week_cycle(daily_cycle('a', date '2017-12-03', 3),
daily_cycle('b', date '2017-12-04', 12),
daily_cycle('b', date '2017-12-05', 7) ) );
Related
So I'm dealing with a situation can be described as the following:
Basically, I have two types car and supercar
CREATE OR REPLACE TYPE CAR_T AS OBJECT (
id VARCHAR (10),
fuel_type VARCHAR (10)
) NOT FINAL;
CREATE OR REPLACE TYPE SUPERCAR_T UNDER CAR_T (
number_cylinders number(10)
);
When inserting a supercar into car:
INSERT INTO CAR VALUES(SUPERCAR_T(99, 'petrol', 12));
I'd like to check if the the number of cylinders is greater than 6 before inserting into CAR table WITHOUT CREATING A TABLE FOR SUPERCARS:
CREATE OR REPLACE TRIGGER INSERT_SUPERCAR
BEFORE INSERT ON CAR
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
IF :NEW.number_cylinders < 6 THEN
RAISE_APPLICATION_ERROR(-20001,'Not a supercar');
END;
I always end up with the following error:
bad bind variable 'NEW.number_cylinders'
It appears that I cannot access the attribute of the subtype directly. My question is how to fix such an error, and is there a better way to do it using check constraint without creating a new table for supercars?
You can use the :NEW.OBJECT_VALUE pseudo-column and the TREAT function to cast the object to a SUPERCAR_T type and then check the number_cylinders attribute:
CREATE OR REPLACE TRIGGER INSERT_SUPERCAR
BEFORE INSERT ON CAR
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
IF :NEW.OBJECT_VALUE IS OF (SUPERCAR_T)
AND TREAT(:NEW.OBJECT_VALUE AS SUPERCAR_T).number_cylinders < 6
THEN
RAISE_APPLICATION_ERROR(-20001,'Not a supercar');
END IF;
END;
/
Then:
INSERT INTO CAR VALUES(SUPERCAR_T(99, 'petrol', 12));
Works but:
INSERT INTO CAR VALUES(SUPERCAR_T(99, 'petrol', 4));
Raises the exception:
ORA-20001: Not a supercar
You could also use a CHECK constraint:
ALTER TABLE car ADD CONSTRAINT car__supercar_cylinders__chk CHECK (
OBJECT_VALUE IS NOT OF (SUPERCAR_T)
OR TREAT(OBJECT_VALUE AS SUPERCAR_T).number_cylinders >= 6
);
or
ALTER TABLE car ADD CONSTRAINT car__supercar_cylinders2__chk CHECK (
TREAT(OBJECT_VALUE AS SUPERCAR_T).number_cylinders IS NULL
OR TREAT(OBJECT_VALUE AS SUPERCAR_T).number_cylinders >= 6
);
db<>fiddle here
I have started my journey in learning SQL and right I am having trouble creating and inserting data into tables. Here is the code that I have tried, I get an error message saying that there aren't enough values. I am using Oracle.
Create table project
(
proj_id number(10),
medic_name varchar2(10),
purpose varchar2(12),
start_date date,
end_date date,
pi_id null,
CONSTRAINT pkprojid primary key (proj_id),
CONSTRAINT fkproject foreign key (pi_id) references researcher
);
alter session set nls_date_format = 'mm/dd/yyyy';
Insert into project values (PR001, 'Medic1', 'heart', '09/01/2017', '07/31/2019');
Insert into project values (PR002, 'Medic1', 'diabetes', '10/01/2016', '07/31/2020);
Insert into project values (PR003, 'Medic3', 'lung', '11/1/2014', '12/31/2020');
Insert into project values (PR004, 'Medic3', 'blood', '01/10/2017', '07/31/2019');
Insert into project values (PR005, 'Medic5', 'blood', '07/10/2018', '01/31/2020');
alter session set nls_date_format = 'mm/dd/yyyy';
Insert into project values (PR001, 'Medic1', 'heart', '09/01/2017', '07/31/2019');
Issues:
Your table has 6 columns, you are only passing 5 for insert; it seems like you are missing last column (pi_id), hence the error message that you are getting. If you want to skip the last column (which is possible since it is declared as nullable), you can explictly list the column when inserting
first column (proj_id) is of number datatype; PR001 is not a number (neither a string, since it is not quoted: this is a syntax error); did you mean 1 instead? Or, if you want to insert string values, you need to change the datatype of column proj_id to varchar(N) (N being the maximum length of the string, in bytes).
Here is an insert statement that should work for your current table definition:
insert into project(proj_id, medic_name, purpose, start_date, end_date)
values (1, 'Medic1', 'heart', '09/01/2017', '07/31/2019');
Note: there is a missing quote at the end of the date on the second insert statement; I assume that this is a typo.
EDIT: I revised my original question for clarity. Hopefully this helps explain what I'm trying to accomplish more clearly.
I have a standard SQL table VEHICLES and I changed its name to OLTP_VEHICLES with a RENAME statement.
I created a new VEHICLES table as a dimension table that is the "beginning" of my star schema for this DB.
I now need to accomplish the following:
"For the vehicleCode primary key column, use an Oracle Sequence to populate the values. For the vehicleDescription column, use a concatenated combination of vehicleMake and vehicleModel from the OLTP_VEHICLES table."
I need to accomplish this by using a PL/SQL block to populate the description column by selecting the vehicleMake and vehicleModel from the OLTP_VEHICLES table and then inserting the concatenated combination into the VEHICLES Dimension table, via a cursor in a loop.
With this instruction, I am totally baffled. I think where I was confusing you fine folks before was the fact that I was leaving out the "second part" involving the insertion of the vehicleMake and vehicleModel concatenation.
Does this help explain better what I'm after? If not, I'm deeply sorry. I'm so confused on this that I'm even having trouble explaining it. Thanks again for your assistance.
CREATE TABLE VEHICLES
(vehicleVIN VARCHAR(25) PRIMARY KEY,
vehicleType VARCHAR(10) NOT NULL CHECK (lower(vehicleType) IN ('compact', 'midsize', 'fullsize', 'suv', 'truck')),
vehicleMake VARCHAR(15) NOT NULL,
vehicleModel VARCHAR(15) NOT NULL,
vehicleWhereFrom VARCHAR(20) NOT NULL CHECK (lower(vehicleWhereFrom) IN ('maryland','virginia','washington, d.c.')),
vehicleWholesaleCost DECIMAL(9,2) NOT NULL,
vehicleTradeID INT);
INSERT INTO VEHICLES
(vehicleVIN,vehicleType,vehicleMake,vehicleModel,vehicleWhereFrom,vehicleWholesaleCost,vehicleTradeID)
VALUES
('147258HHE91K3RT','compact','chevrolet','spark','Maryland',20583.00,NULL);
INSERT INTO VEHICLES
(vehicleVIN,vehicleType,vehicleMake,vehicleModel,vehicleWhereFrom,vehicleWholesaleCost,vehicleTradeID)
VALUES
('789456ERT0923RFB6','Midsize','ford','Taurus','washington, d.c.',25897.22,1);
INSERT INTO VEHICLES
(vehicleVIN,vehicleType,vehicleMake,vehicleModel,vehicleWhereFrom,vehicleWholesaleCost,vehicleTradeID)
VALUES
('1234567890QWERTYUIOP','fullsize','Lincoln','towncar','Virginia',44222.10,NULL);
INSERT INTO VEHICLES
(vehicleVIN,vehicleType,vehicleMake,vehicleModel,vehicleWhereFrom,vehicleWholesaleCost,vehicleTradeID)
VALUES
('WER234109TEO458GZ','SUV','Chevrolet','suburban','Maryland',52789.00,2);
ALTER TABLE VEHICLES RENAME TO OLTP_VEHICLES;
CREATE TABLE VEHICLES
(vehicleCode VARCHAR(25) PRIMARY KEY,
vehicleDescription VARCHAR(50) NOT NULL);
I also put this into SQL Fiddle if anyone wants to test something: http://sqlfiddle.com/#!4/2de3ae
Thanks!
Finally figured it out, after lots of research, trial and error, and of course, help from #MatBailie. Here's the solution I was after:
--Oracle sequence to populate primary key values in VEHICLES dimension table
DROP SEQUENCE SEQ_VEHICLES;
CREATE SEQUENCE SEQ_VEHICLES START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER VEHICLES_PK
BEFORE INSERT ON VEHICLES FOR EACH ROW
BEGIN
SELECT SEQ_VEHICLES.NEXTVAL INTO :new.vehicleCode FROM DUAL;
END;
/
--PL/SQL Block to populate the description column via a cursor in a loop w/concatenation
DECLARE
CURSOR vehDesCur IS
SELECT LOWER(vehicleMake) || ' ' || LOWER(vehicleModel) AS vehicleDescription
FROM OLTP_VEHICLES;
BEGIN
FOR oltp_data IN vehDesCur
LOOP
INSERT INTO VEHICLES (vehicleDescription)
SELECT oltp_data.vehicleDescription
FROM dual
WHERE NOT EXISTS
(SELECT *
FROM VEHICLES v
WHERE v.vehicleDescription = oltp_data.vehicleDescription);
END LOOP;
END;
/
I'm making a sql script so I have create tables, now I have a new table that have columns. One column has a FOREIGN KEY so I need this value to be SET DEFAULT at the value of the value of the original table. For example consider this two table
PERSON(Name,Surename,ID,Age);
EMPLOYER(Name,Surname,Sector,Age);
In Employer I need AGE to be setted on default on the AGE of Person, this only if PERSON have rows or just 1 row.
ID is Primary key for person and Surname,Sector for employer and AGE is FOREIGN KEY in Employer refferenced from Person
Example sql :
CREATE TABLE PERSON(
name VARCHAR(30) ,
surename VARCHAR(20),
ID VARCHAR(50) PRIMARY KEY,
Age INT NOT NULL,
);
CREATE TABLE EMPLOYER(
name VARCHAR(30) ,
Surename VARCHAR(20),
Sector VARCHAR(20),
Age INT NOT NULL,
PRIMARY KEY (Surename,Sector),
FOREIGN KEY (Age) REFERENCES Person(Age) //HERE SET DEFAULT Person(Age), how'??
);
Taking away the poor design choices of this exercise it is possible to assign the value of a column to that of another one using a trigger.
Rough working example below:
create table a (
cola int,
colb int) ;
create table b (
colc int,
cold int);
Create or replace function fn()
returns trigger
as $$ begin
if new.cold is null then
new.cold = (select colb from a where cola = new.colc);
end if;
return new;
end;
$$ language plpgsql;
CREATE TRIGGER
fn
BEFORE INSERT ON
b
FOR EACH ROW EXECUTE PROCEDURE
fn();
Use a trigger rather than a default. I have done things like this (useful occasionally for aggregated full text vectors among other things).
You cannot use a default here because you have no access to the current row data. Therefore there is nothing to look up if it is depending on your values currently being saved.
Instead you want to create a BEFORE trigger which sets the value if it is not set, and looks up data. Note that this has a different limitation because DEFAULT looks at the query (was a value specified) while a trigger looks at the value (i.e. what does your current row look like). Consequently a default can be avoided by explicitly passing in a NULL. But a trigger will populate that anyway.
I know this error issue was been addressed before, but I can't seem to find any relevant solution so I'm posting this question.
create table subscribers(
num_s number(6,0) ,
name varchar2(30) constraint nameM not null,
surname varchar2(20),
town varchar2(30),
age number(3,0) ,
rate number(3,0) ,
reduc number(3,0) ,
CONSTRAINT subscriber_pk primary key (num_s),
constraint age_c check (age between 0 and 120)
);
create or replace type copy_bookT as object(
num number(6),
loancode varchar2 (10),
book_ref ref bookT
);
create table copy_books of copy_bookT(
constraint pk_cb primary key (num),
constraint chk_st check (loancode in('Loan', 'Not')),
loancode default 'Loan' not null
);
create table Lending(
cb_num number(6),
sb_num number(6),
date_L date,
constraint fk_cb foreign key (cb_num) references copy_books(num),
constraint fk_sb foreign key (sb_num) references Subscribers(num_s)
);
create or replace trigger chk_DateL
for insert or update on lending
COMPOUND TRIGGER
--declare
L_Date int;
avail varchar2(10);
subtype copy_booksRec is lending%ROWTYPE;
type copied_bks is table of copy_booksRec;
cbks copied_bks := copied_bks();
before each row is
begin
cbks.extend;
cbks(cbks.last).cb_num := :new.cb_num;
cbks(cbks.last).sb_num := :new.sb_num;
end before each row;
before statement is
begin
for i in cbks.first .. cbks.last loop
select loancode into avail from copy_books where num = cbks(i).cb_num;
select count(date_L) into L_Date from lending where sb_num = cbks(i).sb_num and date_L = cbks(i).date_L;
if (L_Date = 0 and avail = 'Loan') then
update copy_books set loancode = 'Not' where num = cbks(i).cb_num;
cbks.delete;
-- cbks(i).date_L := cbks(i).date_L;
else
dbms_output.put_line('You can only make ONE LOAN at a time! You have already loaned a book on ' || L_Date);
cbks.delete;
end if;
end loop;
-- FORALL i IN cbks.first .. cbks.last
-- insert into lending values cbks(i);
cbks.delete;
end before statement;
end chk_DateL;
/
show errors
It all compiles successfully, but when I try to insert a sample record like:
insert into lending values (2, 700, '10-MAR-14');
it raises a numeric error which comes from the trigger line 18. I don't know what needs fixing despite my efforts.
You should not count on Oracle's default date format to translate your string literal to a date value , you should define the format you're using explicitly:
insert into lending values (2, 700, to_date('10-MAR-14', 'DD-MON-YY'));
While the date format issue is a valid point, that isn't causing your error. It's coming from line 18, which is the for ... loop line:
before statement is
begin
for i in cbks.first .. cbks.last loop
You've got cbks being extended and populated from the before row part of the trigger. When the before statement part fires, cbks is empty, as the row-level trigger hasn't fired yet. It's the first and last references that are throwing the ORA-06502: PL/SQL: numeric or value error error.
You can demonstrate the same thing with a simple anonymous block:
declare
type my_type is table of dual%rowtype;
my_tab my_type := my_type();
begin
for i in my_tab.first .. my_tab.last loop
null;
end loop;
end;
/
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 5
SQL Fiddle; you can see you can avoid it by adding an extend, but that doesn't really help you in your version, since you seem to want the row values. (You can eliminate the error in your code with an extend, but it's unlikely to do what you want still).
I'm really not sure what you're trying to achieve here, so I don't really have any advice on what you need to do differently.
as Mureinik already told, Oracle does not know about how to transform your varchar2 into date datatype and you should use date explicitly. But instead of making to_date use date literal - in my opinion it is more clear than using of to_date function
insert into lending values (2,700,date '2014-03-10');
by the way, you can simply change your NLS settings by altering the current session and installing the date format you need