I am trying to create a BEFORE UPDATE trigger on the two tables purchaseorderdetail and purchaseorderheader as shown below. I would like to implement this trigger on PurchaseOrderHeader table that prohibits updates of the PurchaseOrderHeader.SubTotal column if the corresponding data in the PurchaseOrderDetail table is not consistent with the new value of the PurchaseOrderHeader.SubTotal column.
CREATE TABLE purchaseorderheader(
purchaseorderid NUMBER(4),
revisionnumber NUMBER(2),
status NUMBER(1),
employeeid NUMBER(3),
vendorid NUMBER(4),
shipmethodid NUMBER(1),
orderdate TIMESTAMP,
shipdate TIMESTAMP,
subtotal FLOAT(10),
taxamt FLOAT(10),
freight FLOAT(10),
modifieddate TIMESTAMP,
PRIMARY KEY(purchaseorderid)
);
CREATE TABLE purchaseorderdetail(
purchaseorderid NUMBER(4),
purchaseorderdetailid NUMBER(4),
duedate TIMESTAMP,
orderqty NUMBER(6),
productid NUMBER(6),
unitprice FLOAT(10),
receivedqty FLOAT(10),
rejectedqty FLOAT(10),
modifieddate TIMESTAMP,
PRIMARY KEY(purchaseorderdetailid),
CONSTRAINT fk_orderid FOREIGN KEY (purchaseorderid) REFERENCES purchaseorderheader(purchaseorderid)
);
What I have come up with so far, any help?
CREATE OR REPLACE TRIGGER Header_Before_Subtotal BEFORE UPDATE
ON purchaseorderheader
FOR EACH ROW
BEGIN
IF(:NEW.subtotal <> (SELECT unitprice*orderqty FROM purchaseorderdetail GROUP BY purchaseorderid)) THEN
RAISE_APPLICATION_ERROR(-20001, 'Subtotal is not equal to unitprice * order quantity, check again.');
END IF;
END;
Compute total separately (and reference the master key), and then compare that value to new subtotal.
CREATE OR REPLACE TRIGGER Header_Before_Subtotal
BEFORE UPDATE
ON purchaseorderheader
FOR EACH ROW
DECLARE
l_total number;
BEGIN
SELECT d.unitprice * d.orderqty
INTO l_total
FROM purchaseorderdetail d
WHERE d.purchaseorderid = :new.purchaseorderid;
IF :NEW.subtotal <> l_total THEN
RAISE_APPLICATION_ERROR(-20001, 'Subtotal is not equal to unitprice * order quantity, check again.');
END IF;
END;
/
if i have “INSERT INTO” i need added automatic calculation for “AGE” attribute that need to be calculated
I've tried
create or replace TRIGGER AGE_CALC03 BEFORE INSERT OR UPDATE ON EMPLOYEES
FOR EACH ROW BEGIN
:new.AGE := SYSDATE - :new.BIRTH_DATE;
END AGE_CALC03;”
Having an AGE column is a bad choice because it will become incorrect if the data in the row is not refreshed regularly.
However, I suspect this is a school assignment and you don't care about the design problems. OK - the reason your trigger isn't giving you a good value for AGE is that the calculation is simply wrong. You're subtracting one date from another, thinking that this will give you the difference in years. It does not - it gives you the difference in DAYS.
The correct calculation to use for AGE is FLOOR(MONTHS_BETWEEN(SYSDATE, BIRTH_DATE) / 12). So your trigger should read:
CREATE OR REPLACE TRIGGER AGE_CALC03
BEFORE INSERT OR UPDATE ON EMPLOYEES
FOR EACH ROW
BEGIN
:new.AGE := FLOOR(MONTHS_BETWEEN(SYSDATE, :new.BIRTH_DATE) / 12);
END AGE_CALC03;
But - back to the problems with an AGE column. A better way to obtain an employees age is to have a function to call which returns you a persons age based on their birth date. Something like the following:
FUNCTION COMPUTE_AGE(pinBirth_date IN DATE)
RETURN NUMBER
AS
BEGIN
RETURN FLOOR(MONTHS_BETWEEN(SYSDATE, pinBirth_date ) / 12)
END COMPUTE_AGE;
Then get rid of the AGE column on the EMPLOYEES table and call this function any time you need someone's age, which will then be correct at any given moment in time.
In Oracle 12 and later, use a virtual column and a deterministic function:
CREATE FUNCTION calculate_age(
birth_date DATE,
now_date DATE DEFAULT SYSDATE
) RETURN INTEGER DETERMINISTIC
IS
BEGIN
RETURN FLOOR( MONTHS_BETWEEN( now_date, birth_date ) / 12 );
END;
/
and:
CREATE TABLE EMPLOYEES (
EMPLOYEE_ID VARCHAR2(5 BYTE)
CONSTRAINT EMPLOYEES__EMPLOYEE_ID__NN NOT NULL
CONSTRAINT EMPLOYEES__EMPLOYEE_ID__PK PRIMARY KEY,
FIRST_NAME VARCHAR2(100 BYTE),
LAST_NAME VARCHAR2(100 BYTE),
IDENTIFICATION_NUMBER NUMBER(13,0),
MANAGER_ID VARCHAR2(5 BYTE)
CONSTRAINT EMPLOYEES__MANAGER_ID__FK REFERENCES EMPLOYEES ( EMPLOYEE_ID ),
DEPARTMENT_ID VARCHAR2(1 BYTE),
WORKING_STATUS VARCHAR2(1 BYTE),
BIRTH_DATE DATE,
AGE INTEGER
GENERATED ALWAYS AS ( calculate_age( BIRTH_DATE ) ) VIRTUAL
);
Then if you insert some sample data:
INSERT INTO EMPLOYEES ( EMPLOYEE_ID, BIRTH_DATE )
SELECT 'Alice', DATE '2000-01-01' FROM DUAL UNION ALL
SELECT 'Bob', DATE '1990-01-01' FROM DUAL UNION ALL
SELECT 'Carol', DATE '1980-01-01' FROM DUAL;
and query the ages:
SELECT Employee_id, birth_date, age FROM employees;
You get the output:
EMPLOYEE_ID | BIRTH_DATE | AGE
:---------- | :------------------ | --:
Alice | 2000-01-01 00:00:00 | 20
Bob | 1990-01-01 00:00:00 | 30
Carol | 1980-01-01 00:00:00 | 40
db<>fiddle here
If you already have the birth_date column in your system, Why do you have Age column too. This is redundancy of data. Whenever you need to show age you can simply use your logic as SYSDATE - BIRTH_DATE.
If you strictly need to use AGE column in your table, You have to use TRIGGER as you are currently using. Are you having any issues in doing so?
Name Null Type
------------ -------- ------------
ID NOT NULL NUMBER
NAME VARCHAR2(20)
ADDRESS VARCHAR2(20)
CLASS VARCHAR2(10)
DATE_CREATED TIMESTAMP(6)
1 SATISH PUNE SOFTWARE
2 ABHISHEK INDORE BUSINESS
3 ARUN BARWANI GOVERNMENT
4 RITESH SHIVNI SOFTWARE
5 NAYAN BANGLORE SOFTWARE 27-12-19 07:23:45.000000000 PM
If you just want to select from your table including systimestamp for null values then use following select:
Select id, name, address, class,
Nvl(date_created, systimestamp)
From your_table;
If you want to update the date_created in table then use following update:
Update your_table
Set date_created = systimestamp
Where date_created is null;
Cheers!!
I have found an exercise in the SQL book I study, which is not solved and I can not solve it.
The goal is to implement a trigger that avoids overlapping contracts. If a customer with a current contract signs a new one, the end date of the previous one will be one day before the new start date.
Tables given are:
CREATE TABLE CLIENTS (
clientId VARCHAR2(15),
DNI VARCHAR2(9),
name VARCHAR2(100) NOT NULL,
surname VARCHAR2(100) NOT NULL,
sec_surname VARCHAR2(100),
eMail VARCHAR2(100) NOT NULL,
phoneN NUMBER(12),
birthdate DATE,
CONSTRAINT PK_CLIENTS PRIMARY KEY (clientId),
CONSTRAINT UK1_CLIENTS UNIQUE (DNI),
CONSTRAINT UK2_CLIENTS UNIQUE (eMail),
CONSTRAINT UK3_CLIENTS UNIQUE (phoneN),
);
CREATE TABLE contracts(
contractId VARCHAR2(10),
clientId VARCHAR2(15),
startdate DATE NOT NULL,
enddate DATE,
contract_type VARCHAR2(50),
address VARCHAR2(100) NOT NULL,
town VARCHAR2(100) NOT NULL,
ZIPcode VARCHAR2(8) NOT NULL,
country VARCHAR2(100) NOT NULL,
CONSTRAINT PK_contracts PRIMARY KEY (contractId),
CONSTRAINT FK_contracts1 FOREIGN KEY (clientId) REFERENCES CLIENTS
);
Any suggestions?
I agree with the posted comments that it helps to have some specifics about what is failing in the prior attempts, and I would also recommend not using a TRIGGER at all for this kind of thing.
But as this is for a study exercise, here are some examples that might be a starting place.
I've modified your tables to disallow NULL PRIMARY KEYs in these examples.
To get started, create the tables:
CREATE TABLE CLIENTS (
CLIENTID VARCHAR2(15) NOT NULL,
DNI VARCHAR2(9),
NAME VARCHAR2(100) NOT NULL,
SURNAME VARCHAR2(100) NOT NULL,
SEC_SURNAME VARCHAR2(100),
EMAIL VARCHAR2(100) NOT NULL,
PHONEN NUMBER(12),
BIRTHDATE DATE,
CONSTRAINT PK_CLIENTS PRIMARY KEY (CLIENTID),
CONSTRAINT UK1_CLIENTS UNIQUE (DNI),
CONSTRAINT UK2_CLIENTS UNIQUE (EMAIL),
CONSTRAINT UK3_CLIENTS UNIQUE (PHONEN)
);
CREATE TABLE CONTRACTS (
CONTRACTID VARCHAR2(10) NOT NULL,
CLIENTID VARCHAR2(15) NOT NULL,
STARTDATE DATE NOT NULL,
ENDDATE DATE,
CONTRACT_TYPE VARCHAR2(50),
ADDRESS VARCHAR2(100) NOT NULL,
TOWN VARCHAR2(100) NOT NULL,
ZIPCODE VARCHAR2(8) NOT NULL,
COUNTRY VARCHAR2(100) NOT NULL,
CONSTRAINT PK_CONTRACTS PRIMARY KEY (CONTRACTID),
CONSTRAINT FK_CONTRACTS1 FOREIGN KEY (CLIENTID) REFERENCES CLIENTS
);
Then, create the first CLIENTs:
INSERT INTO CLIENTS VALUES (1,NULL,'Frodo','Baggins',NULL,'the.real.frodo#adventure.com',NULL,NULL);
INSERT INTO CLIENTS VALUES (2,NULL,'Chewbacca','UNKNOWN',NULL,'chewio.#kashyyyk.org',NULL,NULL);
COMMIT;
Then create a TRIGGER. In this first example, the TRIGGER is an AFTER STATEMENT type.
It is simple but inefficient since it evaluates every CLIENT after each INSERT statement.
Against a large data set, or in the face of multiple TRIGGERs, this could be a problem.
This TRIGGER will check for the prior contract and will set its ENDDATE to one day before the new contract, if it is null or after the start of the new contract.
CREATE OR REPLACE TRIGGER CONTRACT_ENDDATE_ADJUSTER
AFTER INSERT ON CONTRACTS
BEGIN
MERGE INTO CONTRACTS
USING (
SELECT CONTRACTID,
CANDIDATE_ENDDATE AS ENDDATE
FROM
(SELECT CONTRACTS.CONTRACTID,
(TRUNC(LEAD(STARTDATE) OVER (PARTITION BY CLIENTID ORDER BY STARTDATE ASC) - 1)) AS CANDIDATE_ENDDATE,
DENSE_RANK() OVER (PARTITION BY CLIENTID ORDER BY STARTDATE DESC) AS CONTRACT_ORDER
FROM CONTRACTS)
WHERE CONTRACT_ORDER = 2) CANDIDATE_CONTRACT
ON (CONTRACTS.CONTRACTID = CANDIDATE_CONTRACT.CONTRACTID)
WHEN MATCHED THEN UPDATE SET CONTRACTS.ENDDATE = CANDIDATE_CONTRACT.ENDDATE
WHERE CONTRACTS.ENDDATE IS NULL OR CONTRACTS.ENDDATE > CANDIDATE_CONTRACT.ENDDATE;
END;
/
Then, test it out.
Add the initial contracts. No enddate changes expected, as these are the first. Frodo's contract here has an end-date already set.
INSERT INTO CONTRACTS VALUES('Break-Ring',1,TO_DATE('19560511','YYYYMMDD'), TO_DATE('19851014','YYYYMMDD'), NULL, 'No 1', 'Doom Mountain', 'MORD', 'Middle-Earth');
INSERT INTO CONTRACTS VALUES('SaveGalaxy',2,TO_DATE('19770615','YYYYMMDD'), NULL, NULL, 'No 75', 'Rwookrrorro', 'RWKR', 'Kashyyyk');
SELECT CONTRACTID, CLIENTID, STARTDATE, ENDDATE FROM CONTRACTS ORDER BY CLIENTID ASC, STARTDATE ASC;
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 14-OCT-85
SaveGalaxy 2 15-JUN-77
Then add new contracts.
Frodo's new contract starts before the end of his existing contract, so the enddate will be adjusted.
Chewie's initial contract had no ENDDATE, so it will be adjusted as well.
INSERT INTO CONTRACTS VALUES('GoBackHome',1,TO_DATE('19570219','YYYYMMDD'), NULL, NULL, 'No 13', 'Hobbiton', 'HBTN', 'Middle-Earth');
INSERT INTO CONTRACTS VALUES('DefendHoth',2,TO_DATE('19801115','YYYYMMDD'), NULL, NULL, 'Meteor Crater', 'Ice Ridge', 'METEO', 'Hoth');
SELECT CONTRACTID, CLIENTID, STARTDATE, ENDDATE FROM CONTRACTS ORDER BY CLIENTID ASC, STARTDATE ASC;
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 18-FEB-57
GoBackHome 1 19-FEB-57
SaveGalaxy 2 15-JUN-77 14-NOV-80
DefendHoth 2 15-NOV-80
And as other contracts are signed, the pattern continues:
INSERT INTO CONTRACTS VALUES('GoWedding',2,TO_DATE('19830309','YYYYMMDD'), NULL, NULL, 'Main Hall', 'Grand Palace', 'ALLNC', 'Coruscant');
INSERT INTO CONTRACTS VALUES('Gardening',1,TO_DATE('19570503','YYYYMMDD'), NULL, NULL, 'No 13', 'Hobbiton', 'HBTN', 'Middle-Earth');
SELECT CONTRACTID, CLIENTID, STARTDATE, ENDDATE FROM CONTRACTS ORDER BY CLIENTID ASC, STARTDATE ASC;
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 18-FEB-57
GoBackHome 1 19-FEB-57 02-MAY-57
Gardening 1 03-MAY-57
SaveGalaxy 2 15-JUN-77 14-NOV-80
DefendHoth 2 15-NOV-80 08-MAR-83
GoWedding 2 09-MAR-83
To stabilize the workload on this query, a COMPOUND TRIGGER may be used instead. This second example achieves the same result as the first, but only interrogates the CONTRACTs of CLIENTs that have changed:
First, ROLLBACK;
Then, create a type to be used by the TRIGGER:
CREATE OR REPLACE TYPE NUMBER_LIST IS TABLE OF NUMBER;
/
Then create the COMPOUND TRIGGER:
CREATE OR REPLACE TRIGGER CONTRACT_ENDDATE_ADJUSTER
FOR INSERT ON CONTRACTS
COMPOUND TRIGGER
V_CLIENTS NUMBER_LIST;
BEFORE STATEMENT
IS
BEGIN
V_CLIENTS:= NUMBER_LIST();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
V_CLIENTS.EXTEND();
V_CLIENTS(V_CLIENTS.COUNT) := :NEW.CLIENTID;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
MERGE INTO CONTRACTS
USING (
SELECT CONTRACTID,
CANDIDATE_ENDDATE AS ENDDATE
FROM
(SELECT CONTRACTS.CONTRACTID,
(TRUNC(LEAD(STARTDATE) OVER (PARTITION BY CLIENTID ORDER BY STARTDATE ASC) - 1)) AS CANDIDATE_ENDDATE,
DENSE_RANK() OVER (PARTITION BY CLIENTID ORDER BY STARTDATE DESC) AS CONTRACT_ORDER
FROM CONTRACTS
WHERE CONTRACTS.CLIENTID IN (SELECT * FROM TABLE(V_CLIENTS)))
WHERE CONTRACT_ORDER = 2) CANDIDATE_CONTRACT
ON (CONTRACTS.CONTRACTID = CANDIDATE_CONTRACT.CONTRACTID)
WHEN MATCHED THEN UPDATE SET CONTRACTS.ENDDATE = CANDIDATE_CONTRACT.ENDDATE
WHERE CONTRACTS.ENDDATE IS NULL OR CONTRACTS.ENDDATE > CANDIDATE_CONTRACT.ENDDATE;
END AFTER STATEMENT;
END CONTRACT_ENDDATE_ADJUSTER;
/
After repeating the above inserts, the result is the same:
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 18-FEB-57
GoBackHome 1 19-FEB-57 02-MAY-57
Gardening 1 03-MAY-57
SaveGalaxy 2 15-JUN-77 14-NOV-80
DefendHoth 2 15-NOV-80 08-MAR-83
GoWedding 2 09-MAR-83
This is my table
CREATE TABLE Book (
hotelNo VARCHAR2(2) NOT NULL,
guestNo VARCHAR2(1) DEFAULT NULL,
dateFrom DATE NOT NULL,
dateTo DATE NOT NULL,
roomNo VARCHAR2(2) NOT NULL,
CONSTRAINT pk_Book PRIMARY KEY(hotelNo)
);
This is one of my data (hotelNo, roomNo, guestNo, dateFrom, dateTo) VALUES (13,20,16,to_date('1/21/2008', 'MM/DD/YYYY'),to_date('4/0/2008', 'MM/DD/YYYY'));
My solution is
SELECT dateFrom, COUNT(*) TOTAL from Booking
GROUP BY dateFrom
ORDER BY dateFrom DESC
This could be a simple approach, aggregating by year and month, ordering for the number of occurrences and keeping the row with the maximum number of occurrences.
If you have more than a month with the maximum number of occurrences, this will only give one of them.
Say you have a table like this:
insert into book values (1, null, date '2017-01-01', date '2017-01-01', 99);
insert into book values (2, null, date '2017-01-02', date '2017-01-01', 99);
insert into book values (3, null, date '2017-01-03', date '2017-01-01', 99);
insert into book values (4, null, date '2017-02-01', date '2017-01-01', 99);
insert into book values (5, null, date '2017-02-02', date '2017-01-01', 99);
This query
select *
from (
select count(1),
extract(month from dateFrom) month,
extract(year from dateFrom) year
from Book
group by extract(month from dateFrom),
extract(year from dateFrom)
order by 1 desc
)
where rownum = 1
will give
COUNT(1) MONTH YEAR
---------- ---------- ----------
3 1 2017