Need Help Writing SQL Trigger - sql

Been trying to write this trigger but I can't really work it out..
What I need to do:
Delete an item from the item table but at the same time delete any questions which are associated with the item as well as any questionupdates associated with that question. These deleted records then need to be stored in archived tables with a time of deletion as well as the ID of the operator that deleted them.
A question may have several updates associated with it as may an item have many questions relating to it.
I've put all the schema in the SQL fiddle as it's a lot easier to work on in there but I'll put it in here if needed.
The link to the SQL fiddle:
http://sqlfiddle.com/#!1/1bb25
EDIT: Thought I might as well put it here..
Tables:
CREATE TABLE Operator
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL
);
CREATE TABLE Item
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL
);
CREATE TABLE Faq
(
ID INTEGER NOT NULL PRIMARY KEY,
Question VARCHAR(150) NOT NULL,
Answer VARCHAR(2500) NOT NULL,
ItemID INTEGER,
FOREIGN KEY (ItemID) REFERENCES Item(ID)
);
CREATE TABLE Customer
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(20) NOT NULL,
Email VARCHAR(20) NOT NULL
);
CREATE TABLE Question
(
ID INTEGER NOT NULL PRIMARY KEY,
Problem VARCHAR(1000),
AskedTime TIMESTAMP NOT NULL,
CustomerID INTEGER NOT NULL,
ItemID INTEGER NOT NULL,
FOREIGN KEY (ItemID) REFERENCES Item(ID),
FOREIGN KEY (CustomerID) REFERENCES Customer(ID)
);
CREATE TABLE qUpdate
(
ID INTEGER NOT NULL PRIMARY KEY,
Message VARCHAR(1000) NOT NULL,
UpdateTime TIMESTAMP NOT NULL,
QuestionID INTEGER NOT NULL,
OperatorID INTEGER,
FOREIGN KEY (OperatorID) REFERENCES Operator(ID),
FOREIGN KEY (QuestionID) REFERENCES Question(ID)
);
-- Archive Tables
CREATE TABLE DeletedQuestion
(
ID INTEGER NOT NULL PRIMARY KEY,
Problem VARCHAR(1000),
AskedTime TIMESTAMP NOT NULL,
CustomerID INTEGER NOT NULL,
ItemID INTEGER NOT NULL
);
CREATE TABLE DeletedqUpdate
(
ID INTEGER NOT NULL PRIMARY KEY,
Message VARCHAR(1000) NOT NULL,
UpdateTime TIMESTAMP NOT NULL,
Question INTEGER NOT NULL
);
CREATE TABLE DeletedItem
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL,
OperatorDeleteID INTEGER NOT NULL,
FOREIGN KEY (OperatorDeleteID) REFERENCES Operator(ID)
);
Some samples inserts for testing
--Product Inserts
INSERT INTO Item (ID, Name) VALUES (1, 'testitem1');
INSERT INTO Item (ID, Name) VALUES (2, 'testitem2');
--Operator Inserts
INSERT INTO Operator (ID, Name) VALUES (1, 'testname1');
INSERT INTO Operator (ID, Name) VALUES (2, 'testname2');
--Faq Inserts
INSERT INTO Faq (ID, Question, Answer, ItemID) VALUES (1, 'testq1', 'testa1', 1);
INSERT INTO Faq (ID, Question, Answer, ItemID) VALUES (2, 'testq2', 'testa2', 2);
-- Customer Inserts
INSERT INTO Customer (ID, Name, Email) VALUES (1, 'testcust1', 'testemail1');
INSERT INTO Customer (ID, Name, Email) VALUES (2, 'testcust2', 'testemail2');
-- Question Inserts
INSERT INTO Question (ID, Problem, AskedTime, CustomerID, ItemID) VALUES (1,'testproblem1','2012-03-14 09:30',1,1);
INSERT INTO Question (ID, Problem, AskedTime, CustomerID, ItemID) VALUES (2,'testproblem1','2012-07-14 09:30',2,1);
INSERT INTO qUpdate (ID, Message, UpdateTime, OperatorID, QuestionID) VALUES (1, 'test1','2012-05-14 09:30', 1, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, OperatorID, QuestionID) VALUES (2, 'test2','2012-08-14 09:30', 2, 1);

The first thing to do is to understand that in PostgreSQL, a CREATE TRIGGER statement binds a trigger function to one or more operations on a table, so let's start with the syntax of the function. You can write trigger functions in various scripting languages, but the most common is plpgsql. A simple function might look like this:
CREATE OR REPLACE FUNCTION Question_delete_trig_func()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO DeletedQuestion
SELECT OLD.*;
RETURN OLD;
END;
$$;
To run this after deletes:
CREATE TRIGGER Question_delete_trig
AFTER DELETE ON Question
FOR EACH ROW EXECUTE PROCEDURE Question_delete_trig_func();
That should be enough to get you started.
You should have a trigger like this for each table from which deleted rows should be saved. Then you need to determine how you will make the deletes happen. You could just define the appropriate foreign keys as ON DELETE CASCADE and let PostgreSQL do it for you.

Related

Many-to-Many Link Table Foreign Key Modeling in SQLite

I have the following two tables in SQLite:
CREATE TABLE `Link` (
`link_id` integer NOT NULL,
`part_id` integer NOT NULL,
CONSTRAINT `link_pk` PRIMARY KEY(`link_id`,`part_id`)
);
CREATE TABLE `Main` (
`main_id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,
`link_id` integer NOT NULL REFERENCES `Link`(`link_id`)
);
INSERT INTO `Link` (link_id, part_id) VALUES (1,10);
INSERT INTO `Link` (link_id, part_id) VALUES (1,11);
INSERT INTO `Link` (link_id, part_id) VALUES (1,12);
INSERT INTO `Link` (link_id, part_id) VALUES (2,15);
INSERT INTO `Main` (main_id, link_id) VALUES (1,1);
INSERT INTO `Main` (main_id, link_id) VALUES (2,1);
INSERT INTO `Main` (main_id, link_id) VALUES (3,2);
Many Main rows may reference the same link id, and many Link rows may have the same link id, such that select * from Main natural join Link where main_id=1 will return N rows, and select * from Main where link_id=1 will return K rows. The link id is important, and the original data each main has 1 link id, and each link has N part ids.
Using the schemas above, I am unable to insert any rows in Main due to the foreign key constraint (foreign key mismatch - "Main" referencing "Link": INSERT INTO Main (main_id, link_id) VALUES (1,1);), presumably because of the composite key requirement. I can get this to work by removing the foreign key constraint, but then I am obviously missing a constraint. Reversing the direction of the key wouldn't work either since, as stated above, it's a Many-to-Many relationship. Is there a way to properly model this in SQLite with a constraint that at least one row exists in Link for each link_id in Main?
I would propose a different design.
Each of the 2 entities link_id and part_id should be the primary key in 2 tables, something like:
CREATE TABLE Links (
link_id INTEGER PRIMARY KEY,
link_description TEXT
);
CREATE TABLE Parts (
part_id INTEGER PRIMARY KEY,
part_description TEXT
);
Then, create the junction table of the above tables (like your current Link table):
CREATE TABLE Links_Parts (
link_id INTEGER NOT NULL REFERENCES Links(link_id),
part_id INTEGER NOT NULL REFERENCES Parts(part_id),
PRIMARY KEY(link_id, part_id)
);
and the table Main:
CREATE TABLE Main (
main_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
link_id INTEGER NOT NULL REFERENCES Links(link_id)
);
All the relations are there and you have referential integrity guaranteed if you set foreign key support:
PRAGMA foreign_keys = ON;
See a simplified demo.

PostgreSQL - Insert data into multiple tables simultaneously

I am having a data insertion problem in tables linked by foreign key. I have read in some places that there is a "with" command that helps in these situations, but I do not quite understand how it is used.
I would like to put together four tables that will be used to make a record, however, that all the data were inserted at once, in a single query, and that they were associated with the last table, to facilitate future consultations. Here is the code for creating the tables:
CREATE TABLE participante
(
id serial NOT NULL,
nome character varying(56) NOT NULL,
CONSTRAINT participante_pkey PRIMARY KEY (id),
);
CREATE TABLE venda
(
id serial NOT NULL,
inicio date NOT NULL,
CONSTRAINT venda_pkey PRIMARY KEY (id)
);
CREATE TABLE item
(
id serial NOT NULL,
nome character varying(256) NOT NULL,
CONSTRAINT item_pkey PRIMARY KEY (id)
);
CREATE TABLE lances_vendas
(
id serial NOT NULL,
venda_id integer NOT NULL,
item_id integer NOT NULL,
participante_id integer NOT NULL,
valor numeric NOT NULL,
CONSTRAINT lance_vendas_pkey PRIMARY KEY (id),
CONSTRAINT lances_vendas_venda_id_fkey FOREIGN KEY (venda_id)
REFERENCES venda (id),
CONSTRAINT lances_vendas_item_id_fkey FOREIGN KEY (item_id)
REFERENCES item (id),
CONSTRAINT lances_vendas_participante_id_fkey FOREIGN KEY (participante_id)
REFERENCES participante (id)
);
The idea is to write WITH clauses that contain INSERT ... RETRUNING to return the generated keys. Then these “views for a single query” can be used to insert those keys into the referencing tables.
WITH par_key AS
(INSERT INTO participante (nome) VALUES ('Laurenz') RETURNING id),
ven_key AS
(INSERT INTO venda (inicio) VALUES (current_date) RETURNING id),
item_key AS
(INSERT INTO item (nome) VALUES ('thing') RETURNING id)
INSERT INTO lances_vendas (venda_id, item_id, participante_id, valor)
SELECT ven_key.id, item_key.id, par_key.id, numeric '3.1415'
FROM par_key, ven_key, item_key;
I know that you requested a single query, but you may still want to consider using a transaction:
BEGIN;
INSERT INTO participante (nome) VALUES ('Laurenz');
INSERT INTO venda (inicio) VALUES (current_date);
INSERT INTO item (nome) VALUES ('thing');
INSERT INTO lances_vendas (venda_id, item_id, participante_id, valer)
VALUES (currval('venda_id_seq'), currval('item_id_seq'), currval('participante_id_seq'), 3.1415);
COMMIT;
The transaction ensures that any new row in participante, venda and item leave the value of currval('X') unchanged.
You could create a function to do that job. Take a look at this example:
CREATE OR REPLACE FUNCTION import_test(p_title character varying, p_last_name character varying, p_first_name character varying, p_house_num integer, p_street character varying, p_zip_code character varying, p_city character varying, p_country character varying)
RETURNS integer
LANGUAGE plpgsql
AS
$body$
DECLARE
address_id uuid;
parent_id uuid;
ts timestamp;
BEGIN
address_id := uuid_generate_v4();
parent_id := uuid_generate_v4();
ts := current_timestamp;
insert into address (id, number, street, zip_code, city, country, date_created) values (address_id, p_house_num, p_street, p_zip_code, p_city, p_country, ts);
insert into person (id, title, last_name, first_name, home_address, date_created) values (parent_id, p_title, p_last_name, p_first_name, address_id, ts);
RETURN 0;
END;
$body$
VOLATILE
COST 100;
COMMIT;
Note how the generated UUID for the address (first insert) is used in the person-record (second insert)
Usage:
SELECT import_test('MR', 'MUSTERMANN', 'Peter', 'john2#doe.com', 54, 'rue du Soleil', '1234', 'Arlon', 'be');
SELECT import_test('MS', 'MUSTERMANN', 'Peter 2', 'peter2#yahoo.com', 55, 'rue de la Lune', '56789', 'Amnéville', 'fr');

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

Model a Table that depends on a set of values in referenced table that can or not be present at the same time

I have a table 'medical_observations' that in one field references other table 'sypstoms_at_arriving' that describes a list of possible symptoms.
CREATE TABLE `patients`(
id_patient INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(25) ,
address VARCHAR(50) ,
CONSTRAINT `uc_Info_Patient` UNIQUE (`id_patient`)
);
INSERT INTO `patients` values (1,'joe','joe´s address');
INSERT INTO `patients` values (2,'moe','moe´s address');
INSERT INTO `patients` values (3,'karl','karle´s address');
INSERT INTO `patients` values (4,'lenny','lenny´s address');
CREATE TABLE `symptoms_at_arrival` (
symptom_at_arrival varchar(30) primary key
);
INSERT INTO `symptoms_at_arrival` values ('vomit');
INSERT INTO `symptoms_at_arrival` values ('urine');
INSERT INTO `symptoms_at_arrival` values ('dizziness');
INSERT INTO `symptoms_at_arrival` values ('convulsion');
CREATE TABLE `medical_observations`(
id_medical_observation INTEGER NOT NULL PRIMARY KEY,
id_patient INTEGER NOT NULL,
symptom_at_arrival VARCHAR(30),
FOREIGN KEY (id_patient) references `patients` (id_patient),
FOREIGN KEY (symptom_at_arrival) references `symptoms_at_arrival` (symptom_at_arrival ),
CONSTRAINT `uc_Info_medical_Observation` UNIQUE (`id_medical_observation`,`id_patient`)
);
My doubt is how to model or store th case when patient has several symptoms... and not just one.
If that would be the case the name of symptom would be enough...
But if patient show several symptoms at the same time?
Update
I have done a sqlfiddle, I was thinking to add a kind of table with 1's and 0's representing if patient shows certain symptom... Would that be correct?
You'll have to make connection in the foreign keys
|patient| |medical_observations| |symptoms_at_arriving|
--------- ---------------------- ----------------------
**id** 1 ----| **id_medical_observation** |-----1 **id**
name |-M **id_patient** | symptom_at_arrival
**symptom_at_arrival** M---|
Try this, don't have mysql here to test, making table multi primary key to support multiple symptoms at same time
CREATE TABLE `symptoms_at_arriving` (
id integer not null primary key autoincrement,
symptom_at_arrival varchar(30)
);
INSERT INTO `symptom_at_arrival' values ('vomit');
INSERT INTO `symptom_at_arrival` values ('urine');
INSERT INTO `symptom_at_arrival` values ('dizziness');
INSERT INTO `symptom_at_arrival` values ('convulsion');
CREATE TABLE `medical_observations`(
id_medical_observation INTEGER NOT NULL,
id_patient INTEGER NOT NULL,
symptom_at_arrival integer not null,
FOREIGN KEY (id_patient) references `patients` (id_patient),
FOREIGN KEY (symptom_at_arrival) references `symptoms_at_arriving` (symptom_at_arrival,
PRIMARY KEY (id_medical_observation, id_patient, symptom_at_arrival)
);

Multilingual database design

I'm trying to design a database schema for a multilingual application. I have so far found a sample from this address. http://fczaja.blogspot.com/2010/08/multilanguage-database-design.html
But I haven't understood this sample. Should I insert Id value on app_product first? How can I know that these values are true for ProductId on app_product_translation?
CREATE TABLE ref_language (
Code Char(2)NOT NULL,
Name Varchar(20) NOT NULL,
PRIMARY KEY (Code)
);
CREATE TABLE app_product (
Id Int IDENTITY NOT NULL,
PRIMARY KEY (Id)
);
CREATE TABLE app_product_translation (
ProductId Int NOT NULL,
LanguageCode Char(2) NOT NULL,
Description Text NOT NULL,
FOREIGN KEY (ProductId) REFERENCES app_product(Id),
FOREIGN KEY (LanguageCode) REFERENCES ref_language(Code)
);
It looks like SQLServer code, proceeding on that assumption.
Yes you must insert the app_product first. But you cannot insert the id column's value. It is assigned automatically, because it is an identity column.
Two things you can check out...to find the identity column's value after inserting.
The OUTPUT clause of the INSERT statement. It can return any values that are inserted, not just the identity column.
The ##Identity variable. (by far more traditional and popular)
declare #lastid int
insert into x values (1,2,3)
set #lastid = ##identity
insert into y values (#lastid, a, b, c)