PostgreSQL- Function to update 3 shared columns from table B on table A - sql

The main goal is to create a function that updates 3 columns on another table (the update of the 2 columns from the first table trigger the update on the second one).
CREATE TRIGGER trigger_modif_amount
AFTER INSERT OR DELETE OR UPDATE OF net_amount, iva_amount
ON erp.tb_lines
FOR EACH ROW
EXECUTE PROCEDURE modif_amount();
The name of the 3 columns: net_amount , iva_amount , total_amount
CREATE OR REPLACE FUNCTION modif_amount()
RETURNS TRIGGER AS $$
BEGIN
UPDATE erp.tb_invoice
SET (net_amount,iva_amount,tot_amount) = (select COALESCE(sum(net_amount),0),COALESCE(sum(iva_amount),0),COALESCE(sum(net_amount+iva_amount),0) from tb_lines where invoice_id = coalesce(NEW.invoice_id, OLD.invoice_id))
WHERE invoice_id = coalesce(NEW.invoice_id, OLD.invoice_id);
END;
$$ LANGUAGE plpsql;
The tables:
CREATE TABLE erp.tb_invoice (
co_code CHARACTER(3) NOT NULL,
invoice_id INT NOT NULL,
invoice_no CHARACTER VARYING(15) NOT NULL,
cust_no CHARACTER(5) NOT NULL,
site_id INT NOT NULL,
payed CHARACTER(1) NOT NULL DEFAULT 'N',
net_amount REAL NOT NULL,
iva_amount REAL NOT NULL,
tot_amount REAL NOT NULL,
last_updated_by CHARACTER VARYING(20) DEFAULT 'SYSTEM',
last_update_date DATE NOT NULL,
CONSTRAINT pk_invoice PRIMARY KEY (invoice_id),
CONSTRAINT fk_invoice_company FOREIGN KEY (co_code) REFERENCES erp.tb_company (co_code),
CONSTRAINT fk_invoice_customer FOREIGN KEY (cust_no) REFERENCES erp.tb_customer (cust_no),
CONSTRAINT fk_invoice_site FOREIGN KEY (site_id) REFERENCES erp.tb_site (site_id)
);
CREATE TABLE erp.tb_lines (
invoice_id INT NOT NULL,
line_id INT NOT NULL,
line_num INT NOT NULL,
item CHARACTER(5),
description CHARACTER VARYING(120) NOT NULL,
net_amount REAL NOT NULL,
iva_amount REAL NOT NULL,
last_updated_by CHARACTER VARYING(20) DEFAULT 'SYSTEM',
last_update_date DATE NOT NULL,
CONSTRAINT pk_lines PRIMARY KEY (line_id),
CONSTRAINT fk_lines_invoice FOREIGN KEY (invoice_id) REFERENCES erp.tb_invoice (invoice_id)
);

This trigger definition to only execute the function (modif_amount) if column (net_amount) and (iva_amount) is specified as a target in the UPDATE command:
CREATE TRIGGER trigger_modif_amount
AFTER UPDATE OF net_amount,iva_amount ON tb_lines
FOR EACH ROW
EXECUTE PROCEDURE modif_amount();
This form only executes the function (modif_amount) if column (net_amount), (iva_amount) has in fact changed value:
CREATE TRIGGER trigger_modif_amount
AFTER UPDATE
ON tb_lines
FOR EACH ROW
WHEN ((OLD.net_amount IS DISTINCT FROM NEW.net_amount) AND
(OLD.iva_amount IS DISTINCT FROM NEW.iva_amount) )
EXECUTE PROCEDURE modif_amount();

You can create trigger which trigger after update of specific fields
https://www.postgresql.org/docs/14/sql-createtrigger.html
CREATE TRIGGER test
AFTER INSERT OR DELETE OR UPDATE OF net_amount, iva_amount
ON tb_lines
FOR EACH ROW
EXECUTE PROCEDURE modif_amount();
Updating overal sum in another table inside trigger is not a good idea (it moves business logic into triggers and is not save).
Version 1
update tb_invoice set
(net_amount,iva_amount,tot_amount) = (select COALESCE(sum(net_amount),0),COALESCE(sum(iva_amount),0),COALESCE(sum(net_amount+iva_amount),0) from tb_lines where invoice_id = coalesce(NEW.invoice_id, OLD.invoice_id))
where invoice_id = coalesce(NEW.invoice_id, OLD.invoice_id);
Version 2
update tb_invoice set
(net_amount,iva_amount,tot_amount) = (select COALESCE(sum(net_amount),0),COALESCE(sum(iva_amount),0),COALESCE(sum(net_amount+iva_amount),0) from tb_lines where tb_lines.invoice_id = tb_invoice.invoice_id)
where invoice_id = coalesce(NEW.invoice_id, OLD.invoice_id);
COALESCE in sum is needed to ensure, that even if invoice has no positions sums will be calculated as 0.
COALESCE in where is needed because during insert operation there is no OLD.invoice_id, and during delete operation there is no NEW.invoice_id.
My assumption is, that invoice_id cannot be changed. If not, that moving a position from one invoice to another should update ole and new one.

Related

How implement the function of `case` in DDL?

I have a table order. Its DDL is listed below
create table "order"
(
id serial primary key,
quantity integer not null,
estimated_delivery_date date not null,
lodgement_date date check ( lodgement_date > '2022-03-02' ),
product_model varchar(128) not null,
sales_id integer not null,
contract_number varchar(10) not null
);
I want to make the lodgement_date attribute to be null if it is later than 2022-03-02 when inserting a new row. How can I achieve this function in DDL.
To do that, you can't do it in the creation of the table. It is gonna be a constraint.
Example:
CONSTRAINT check_lodgement_date
CHECK (lodgement_date > '2022-03-02')

Creating a audit trigger which update a audit table automatically

My tables are
Book
CREATE TABLE Book
(
Bk_id CHAR(06)NOT NULL,
BK_Name VARCHAR(60)NOT NULL,
Author VARCHAR(30)NOT NULL,
Price NUMERIC(03)NOT NULL,
No_of_copies NUMERIC(02)NOT NULL,
CONSTRAINT Book_PK PRIMARY KEY (Bk_id)
);
Location
CREATE TABLE Location
(
Loc_id CHAR(06)NOT NULL,
Loc_Name VARCHAR(15)NOT NULL,
Stock NUMERIC(02)NOT NULL,
CONSTRAINT Location_PK PRIMARY KEY (Loc_id)
);
Customer
CREATE TABLE Customer
(
Cus_id CHAR(06)NOT NULL,
Cus_Name VARCHAR(25)NOT NULL,
Gender VARCHAR(06)NOT NULL,
TP CHAR(12)NOT NULL,
Address VARCHAR(40)NOT NULL,
CONSTRAINT Customer_PK PRIMARY KEY (Cus_id)
);
Copy
CREATE TABLE Copy
(
Copy_id CHAR(06)NOT NULL,
Bk_id CHAR(06)NOT NULL,
Loc_id CHAR(06)NOT NULL,
Opinion CHAR(02)NOT NULL,
CONSTRAINT pk_Copy PRIMARY KEY (Copy_id),
CONSTRAINT fk_Copy_Bk_id_FK FOREIGN KEY (Bk_id) REFERENCES Book(Bk_id),
CONSTRAINT fk_Copy_Loc_id_FK FOREIGN KEY (Loc_id) REFERENCES Location(Loc_id)
);
Borrow
CREATE TABLE Borrow
(
Cus_evo NUMERIC(02)NOT NULL,
B_Date DATE NOT NULL,
R_Date DATE NOT NULL,
Fee NUMERIC(03)NOT NULL,
Copy_id CHAR(06)NOT NULL,
Cus_id CHAR(06)NOT NULL,
CONSTRAINT pk_Borrow PRIMARY KEY (Cus_id,Copy_id),
CONSTRAINT fk_Borrow_Copy_id_FK FOREIGN KEY (Copy_id) REFERENCES Copy(Copy_id),
CONSTRAINT fk_Borrow_Cus_id_FK FOREIGN KEY (Cus_id) REFERENCES Customer(Cus_id)
);
Audit_Table
Create table Audit_Table
(
Cus_Name VARCHAR(25)NOT NULL,
BK_Name VARCHAR(60)NOT NULL,
B_Date DATE NOT NULL,
Loc_Name VARCHAR(15)NOT NULL,
Cus_evo NUMERIC(02)NOT NULL
);
If a customer gives a zero evaluationCus_evo=0, the details of their Borrowing (Cus_Name from CUSTOMER, the BK_Name from Book, B_Date from Borrow, Loc_Name of the copy from Location and Cus_evo from Borrow) must be placed in an audit table.
The trigger that I created:
CREATE OR REPLACE TRIGGER "AUDIT_TRIGGER"
BEFORE
INSERT OR UPDATE ON Borrow
FOR EACH ROW
WHEN (new.Cus_evo=0)
BEGIN
INSERT INTO Audit_Table
VALUES (:OLD.Cus_Name, :OLD.BK_Name, :OLD.B_Date, :OLD.Loc_Name, :OLD.Cus_evo);
END;
/
I get this error:
Errors: TRIGGER AUDIT_TRIGGER
Line/Col: 3/9 PLS-00049: bad bind variable 'OLD.CUS_NAME'
Line/Col: 3/24 PLS-00049: bad bind variable 'OLD.BK_NAME'
Line/Col: 3/51 PLS-00049: bad bind variable 'OLD.LOC_NAME'
You can not use old and new values from other tables only from table Borrwo when you are creating trigger
You need to specify if you want to check new or old value in WHEN clause
CREATE OR REPLACE TRIGGER "AUDIT_TRIGGER"
BEFORE INSERT OR UPDATE ON Borrow --I have removed double quotes
FOR EACH ROW
WHEN (new.Cus_evo = 0) -- I have added new. before the name of the column
BEGIN
INSERT INTO Audit_Table
VALUES (:OLD.Fee, :OLD.Copy_id, :OLD.Cus_id, :OLD.B_Date, :OLD.Cus_evo);
-- I have replaced column names from other tables with column names
-- from table "Borrow"
END;
/
Here is a demo:
DEMO
In this demo I have removed
CONSTRAINT fk_Borrow_Copy_id_FK FOREIGN KEY (Copy_id) REFERENCES Copy(Copy_id),
from table Borrow spec because it references on the table Copy that we do not have here in your question.
After I have read the comment from #Belayer I have concluded you will need two triggers like this:
CREATE OR REPLACE TRIGGER "AUDIT_TRIGGER"
BEFORE INSERT ON Borrow
FOR EACH ROW
when (new.Cus_evo = 0)
BEGIN
INSERT INTO Audit_Table
VALUES (:new.Copy_id, :new.Copy_id, :new.B_Date, :new.Cus_id, :new.Cus_evo);
END;
/
CREATE OR REPLACE TRIGGER "AUDIT_TRIGGER2"
BEFORE UPDATE ON Borrow
FOR EACH ROW
when (OLD.Cus_evo = 0)
BEGIN
INSERT INTO Audit_Table
VALUES (:OLD.Copy_id, :OLD.Copy_id, :OLD.B_Date, :OLD.Cus_id, :OLD.Cus_evo);
END;
/
Here is a demo with demonstration:
DEMO

Trying to run script through Oracle 11g

I am trying to run SQL statements in the Oracle 11g Express edition, where I am to create tables. Here is my SQL code:
CREATE TABLE STORE
(
StoreID INT PRIMARY KEY,
StoreName VARCHAR2(30) NOT NULL,
City VARCHAR2(30) NOT NULL,
Country VARCHAR2(30) NOT NULL CHECK Country in ('China','Egypt','United States','Spain','New Zealand','Mexico','Africa'),
Phone VARCHAR2(30) NOT NULL,
Fax VARCHAR2(30),
Email VARCHAR2(50) UNIQUE,
Contact VARCHAR2(30) NOT NULL,
UNIQUE (StoreName, City)
);
CREATE TABLE PURCHASE_ITEM
(
PurchaseItemID INT PRIMARY KEY,
StoreID INT NOT NULL REFERENCES STORE(StoreID) ON DELETE CASCADE ON UPDATE CASCADE,
"Date" DATE NOT NULL,
Description VARCHAR2(30) NOT NULL,
Category VARCHAR2(30),
PriceUsed NUMBER(15, 2)
);
CREATE SEQUENCE pur_seq
START WITH 500
INCREMENT BY 5;
CREATE OR REPLACE TRIGGER Purchase
BEFORE INSERT ON PURCHASE_ITEM
FOR EACH ROW
BEGIN
SELECT pur_seq.NEXTVAL
INTO :new.PurchaseItemID
FROM dual;
END;
CREATE TABLE SHIPPER
(
ShipperID INT PRIMARY KEY,
ShipperName VARCHAR2(30) NOT NULL,
Phone VARCHAR2(30) NOT NULL,
Fax VARCHAR2(30),
Email VARCHAR2(50) UNIQUE,
Contact VARCHAR2(30) NOT NULL
);
CREATE TABLE SHIPMENT
(
ShipmentID INT PRIMARY KEY Auto Increment,
ShipperID INT NOT NULL REFERENCES SHIPPER(ShipperID) ON DELETE CASCADE ON UPDATE CASCADE,
ShipperInvoiceNumber INT NOT NULL UNIQUE,
Origin VARCHAR2(30) NOT NULL,
Destination VARCHAR2(30) NOT NULL,
DepartureDate DATE,
ArrivalDate DATE
);
ALTER TABLE SHIPMENT AUTO_INCREMENT = 100;
CREATE TABLE SHIPMENT_ITEM
(
ShipmentID INT NOT NULL REFERENCES SHIPMENT(ShipmentID) ON DELETE CASCADE ON UPDATE CASCADE,
ShipmentItemID INT NOT NULL,
PurchaseItemID INT NOT NULL REFERENCES PURCHASE_ITEM(PurchaseItemID) ON DELETE CASCADE ON UPDATE CASCADE,
InsuredValue NUMBER(15, 2) NOT NULL defaut 100,
PRIMARY KEY (ShipmentID, ShipmentItemID)
);
It only ends up processing 4 statements and I keep getting these error messages:
CREATE TABLE STORE ( StoreID INT PRIMARY K - ORA-00906: missing left parenthesis
CREATE TABLE PURCHASE_ITEM ( PurchaseItemID INT - ORA-00907: missing right parenthesis
CREATE SEQUENCE pur_seq START WITH 500 INCREMENT BY 5 - ORA-00955: name is already used by an existing object
CREATE OR REPLACE TRIGGER Purchase BEFORE INSERT ON PURCHASE - ORA-00942: table or view does not exist
I am wholly unfamiliar with Oracle 11g. I am unsure if I am using the correct application in it for my assignment. I am only going by these instructions:
"For this assignment you are to write scripts to create tables and insert records. In oracle 11g I don’t want you to use the tools to generate the tables, you are required to write scripts to create the tables and records and will need to include the scripts for your submission."
Please, what am I doing wrong?
Let me summarize the problems with your scripts
First you have to enclose the check constraint with braces like below
CREATE TABLE STORE
(
StoreID INT PRIMARY KEY,
StoreName VARCHAR2(30) NOT NULL,
City VARCHAR2(30) NOT NULL,
Country VARCHAR2(30) NOT NULL CHECK (Country in ('China','Egypt','United States','Spain','New Zealand','Mexico','Africa')),
Phone VARCHAR2(30) NOT NULL,
Fax VARCHAR2(30),
Email VARCHAR2(50) UNIQUE,
Contact VARCHAR2(30) NOT NULL,
UNIQUE (StoreName, City)
);
Second, there is no ON UPDATE CASCADE in Oracle 11g so you need to remove it from the CREATE TABLE statement
Third, there is no Auto increment in Oracle 11G for columns So refer this SO for a workaround
Please let me know with these corrections whether your issue is resolved

SQL constraint: two attributes, at least one foreign key match on same table

I have a table of phone numbers owned by a company, and a table of phone call records. Every call record includes (non-null) source and destination numbers. I am given the integrity constraint that either the source number or the destination number, but not both, are allowed to be numbers that are not in the phone number table (because they are numbers not owned by this company). In other words, I need to ensure that at least one of them is a foreign key to the phone number table.
create table phonenumber (
phonenum numeric(10,0) not null,
primary key (phonenum)
);
create table call_record (
URID varchar(20) not null,
c_src numeric(10,0) not null,
c_dst numeric(10,0) not null,
primary key (URID)
);
The following sounds like what I want but isn't valid SQL:
constraint call_constraint check (
foreign key (c_src) references phonenumber (phonenum) or
foreign key (c_dst) references phonenumber (phonenum)
)
Is there a way to specify this in DDL? If not, how would I write a trigger to enforce this?
Edited:
Here is another idea using DDL and not using trigger:
create table phonenumber (
phonenum numeric(10,0) not null,
primary key (phonenum)
);
Create a function to validate foreign key "by hand".
CREATE OR REPLACE FUNCTION call_check(p_src NUMBER, p_dst NUMBER) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
FOR x IN (SELECT COUNT(*) c
FROM (SELECT 1
FROM phonenumber
WHERE phonenum = p_src
UNION ALL
SELECT 1
FROM phonenumber
WHERE phonenum = p_dst)) LOOP
IF x.c>=1 AND x.c <= 2 THEN
RETURN 'OK';
END IF;
END LOOP;
RETURN 'NOK';
END;
If you're on 11g and up, then add virtual column and add check on that column
--drop table call_record
create table call_record (
URID varchar(20) not null,
c_src numeric(10,0) not null,
c_dst numeric(10,0) not null,
call_check_col GENERATED ALWAYS AS (call_check(c_src, c_dst)),
primary key (URID)
);
ALTER TABLE call_record ADD CONSTRAINT call_check_con CHECK (call_check_col='OK');
Let's test
SQL> INSERT INTO phonenumber VALUES ('123');
1 row inserted
SQL> INSERT INTO call_record (urid, c_src, c_dst) VALUES ('C1', '123', '321');
1 row inserted
SQL> INSERT INTO call_record (urid, c_src, c_dst) VALUES ('C3', '123', '123');
1 row inserted
SQL> INSERT INTO call_record (urid, c_src, c_dst) VALUES ('C2', '321', '321');
INSERT INTO call_record (urid, c_src, c_dst) VALUES ('C2', '321', '321')
ORA-02290: check constraint (TST.CALL_CHECK_CON) violated

Oracle - How do I create a table that has an autoincrementing unique key for the ID

This is the first time that I've use oracle SQL and I'm having a problem creating tables with a unique key.
I don't understand why this auto-incrementing id is not working:
ID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
The next question I have is why I am getting an error in each of my statements:
ORA-00922: missing or invalid option
Here is my code:
--
-- Sequence for aout incrment
--
CREATE SEQUENCE IF NOT EXISTS AUTO_INC_SEQ
START WITH 1
INCREMENT BY 1;
--
-- Table Person
--
CREATE TABLE IF NOT EXISTS FITNESS_PERSON
(
ID NUMBER NOT NULL PRIMARY KEY,
FIRST_NAME VARCHAR NOT NULL,
LAST_NAME VARCHAR NOT NULL,
NICK_NAME VARCHAR NOT NULL,
DATE_BIRTH DATE NOT NULL,
PASSWORD VARCHAR NOT NULL,
CONSTRAINT UNIQUE(NICK_NAME)
);
--
-- Table BMR
--
CREATE TABLE IF NOT EXISTS FITNESS_BMR
(
ID NUMBER NOT NULL PRIMARY KEY,
VALUE FLOAT NOT NULL,
VALUE_DATE DATE NOT NULL
);
--
-- M:N for BMR and Person
--
CREATE TABLE IF NOT EXISTS FITNESS_BMR_PERSON
(
BMR_ID NUMBER NOT NULL,
PERSON_ID NUMBER NOT NULL,
FOREIGN KEY(BMR_ID) REFERENCES FITNESS_BMR(ID),
FOREIGN KEY(PERSON_ID) REFERENCES FITNESS_PERSON(ID),
CONSTRAINT BMR_PER PRIMARY KEY(BMR_ID, PERSON_ID)
);
What's the right way to do this (create a table and with an auto-incrementing key that is unique).
You can use a table, a sequence to generate unique ID values and a trigger.
For example:
Table:
CREATE Table FITNESS_BMR
(
ID NUMBER NOT NULL PRIMARY KEY,
VALUE FLOAT NOT NULL,
VALUE_DATE DATE NOT NULL
);
Sequence: create sequence t1_seq start with 1 increment by 1 nomaxvalue;
Trigger:
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT
ON FITNESS_BMR
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT t1_seq.nextval INTO :NEW.ID FROM dual;
END;
/