writing trigger containing multiple conditions - sql

So I have a table trans which has two columns tx_type and ref_nbr
and I want to create a trigger such that the trigger ensures the following condition
in the trans table.
The following two conditions should be ensured:
if tx_type = D or W then ref_nbr should match the branch_nbr in branch table
if tx_type= B , P or R then ref_nbr should match mer_nbr in mer table

Triggers are not intended for keeping consistency in database relations. Use foreign keys for that. So make a table trans not with one column ref_nbr but use 2 columns - one for each relation (foreign key). Additionaly you can create check constraint for making sure that correct column is filled for given tx_type.
If you try to use triggers, you will have problems with concurrent transactions changing related tables like deleting your ref_nbr.
Example definitions for mer, branch and trans tables with some sample inserts:
create table branch(
branch_nbr number generated by default on null as identity start with 3 primary key,
branch_name varchar2(100) not null
);
create table mer (
mer_nbr number generated by default on null as identity start with 2 primary key,
mer_name varchar2(100) not null
);
create table trans (
id number generated by default on null as identity primary key,
tx_type varchar2(1) not null,
ref_branch_nbr number,
ref_mer_nbr number,
constraint ck_tx_type check (tx_type in ('D', 'W', 'B', 'P', 'R')),
constraint ck_correct_ref_for_tx_type
check (
(tx_type in ('D', 'W') and ref_branch_nbr is not NULL and ref_mer_nbr is NULL)
or (tx_type in ('B', 'P', 'R') and ref_branch_nbr is NULL and ref_mer_nbr is not NULL)
),
constraint fk_trans_ref_branch_nbr
foreign key (ref_branch_nbr)
references branch(branch_nbr),
constraint fk_trans_ref_mer_nbr
foreign key (ref_mer_nbr)
references mer(mer_nbr)
);
insert into branch(branch_nbr, branch_name) values(1, 'Master');
insert into branch(branch_nbr, branch_name) values(2, 'Test');
insert into mer(mer_nbr, mer_name) values(1, 'Test to Master');
commit;
-- working:
insert into trans(tx_type, ref_mer_nbr) values('P', 1);
insert into trans(tx_type, ref_branch_nbr) values('D', 1);
-- not working - non existing parent:
insert into trans(tx_type, ref_mer_nbr) values('P', 999);
insert into trans(tx_type, ref_branch_nbr) values('D', 999);
-- not working - wrong tx_type or wrong ref column:
insert into trans(tx_type, ref_mer_nbr) values('D', 1);
insert into trans(tx_type, ref_branch_nbr) values('P', 1);
insert into trans(tx_type, ref_branch_nbr, ref_mer_nbr ) values('P', 1, 1);
-- not working - cant insert without tx_type
insert into trans(ref_mer_nbr, ref_branch_nbr) values(1, 1);

Related

Delete rows from 2 tables using a single query

I have the following database-schema:
I have the following example data:
CREATE TABLE computermapping (
ComputerMappingID int NOT NULL,
PrinterGUID char(36) NOT NULL,
ComputerGUID char(36) NOT NULL
);
INSERT INTO computermapping (ComputerMappingID, PrinterGUID, ComputerGUID) VALUES
(1, 'PRT01', 'Computer1'),
(2, 'PRT02', 'Computer1'),
(3, 'PRT01', 'Computer2'),
(4, 'PRT02', 'Computer2'),
(5, 'PRT03', 'Computer2'),
(6, 'PRT01', 'Computer3');
CREATE TABLE computerdefaultprinter (
ComputerGUID char(36) NOT NULL,
PrinterGUID char(36) NOT NULL
);
INSERT INTO computerdefaultprinter (ComputerGUID, PrinterGUID) VALUES
('Computer2', 'PRT01'),
('Computer1', 'PRT02');
Remark: Originally the tables are full of GUIDs, but I replaced them by names just for better readability.
I have also created an SQL-Fiddle with some example-data: Link
.
Taking the example data, I want to remove the printer "PRT01" from computer "Computer2".
I need to delete the appropriate row in the table computermapping and I need to delete the appropriate row in the table computerdefaultprinter. I want to delete the mentioned data in BOTH tables using ONE SINGLE statement.
According to my program-code I need to target the data by using NOT IN().
Till now I successfully used 2 statements joined/glued together by ";":
DELETE FROM computermapping WHERE PrinterGUID = 'PRT01' AND ComputerGUID NOT IN ('Computer1','Computer3');
DELETE FROM computerdefaultprinter WHERE PrinterGUID = 'PRT01' AND ComputerGUID NOT IN ('Computer1','Computer3')
This was working fine using MySQL, but it is not working with Microsoft SQL-Server. Yes, it does using the SQL Server Management Studio, but not programmatically. (count field incorrect or syntax error)
I am looking for a different approach for this task.
I did a research and it was mentioned, that it should be possible to delete the rows in both tables using "INNER JOIN", but I wasn't able to get it working and I am looking for help.
Thank you
You can add a foreign key with ON DELETE CASCADE.
For example:
CREATE TABLE computermapping (
ComputerMappingID int NOT NULL,
PrinterGUID char(36) NOT NULL,
ComputerGUID char(36) NOT NULL,
primary key (ComputerGUID, PrinterGUID)
);
INSERT INTO computermapping (ComputerMappingID, PrinterGUID, ComputerGUID) VALUES
(1, 'PRT01', 'Computer1'),
(2, 'PRT02', 'Computer1'),
(3, 'PRT01', 'Computer2'),
(4, 'PRT02', 'Computer2'),
(5, 'PRT03', 'Computer2'),
(6, 'PRT01', 'Computer3');
CREATE TABLE computerdefaultprinter (
ComputerGUID char(36) NOT NULL,
PrinterGUID char(36) NOT NULL,
foreign key (ComputerGUID, PrinterGUID)
references computermapping (ComputerGUID, PrinterGUID)
on delete cascade
);
INSERT INTO computerdefaultprinter (ComputerGUID, PrinterGUID) VALUES
('Computer2', 'PRT01'),
('Computer1', 'PRT02');
delete from computermapping
where PrinterGUID = 'PRT01' and ComputerGUID = 'Computer2';
The DELETE deletes a row in computermapping and all related rows from computerdefaultprinter as well.
See running example at SQL Fiddle.

Check if many-to-many relationship exists before insert or delete

I have 3 tables
For example:
Book
id
title
Tag
id
name
BookTag
book_id
tag_id
The goal to disallow having Book without Tag. i.e. when I try insert/delete data I need something to check on database level that Book has at least one Tag through many-to-many. If such validation fails it should throw constaint violation error or some sort of that. How should I implement that? Can it be reached by check constraint or should I create some trigger, if so then how?
please help me. thanks for your help in advance
You can enforce this at the pure database level by adding a foreign key in the book table that points back to a tag (any tag) in the book_tag table. As of now, your database model looks like:
create table book (
id int primary key not null,
title varchar(50)
);
create table tag (
id int primary key not null,
name varchar(50)
);
create table book_tag (
book_id int not null,
book_tag int not null,
primary key (book_id, book_tag)
);
Now, add the extra foreign key that points back to a tag:
alter table book add column a_tag int not null;
alter table book add constraint fk1 foreign key (id, a_tag)
references book_tag (book_id, tag_id) deferrable initially deferred;
Now when you insert a book, it can temporarily not have a tag, but only while the transaction hasn't finished yet. You need to insert a tag before committing. If you don't the constraint will fail, the transaction will rollback, and the insert won't happen.
Note: Please notice that this requires the use of deferrable constraints (look at deferrable initially deferred), something that is part of the SQL Standard but seldomly implemented. Fortunately, PostgreSQL does.
EDIT - Adding an example
Considering the previous modified tables you can try inserting a book without tags (will fail) and with tags (succeeding) as shown below:
insert into tag (id, name) values (10, 'classic');
insert into tag (id, name) values (12, 'action');
insert into tag (id, name) values (13, 'science fiction');
-- begin transaction
insert into book (id, title, a_tag) values (1, 'Moby Dick', 123);
commit; -- fails
-- begin transaction
insert into book (id, title, a_tag) values (2, 'Frankenstein', 456);
insert into book_tag (book_id, book_tag) values (2, 10);
insert into book_tag (book_id, book_tag) values (2, 13);
update book set a_tag = 10;
commit; -- succeeds

Double values in a table

Is it possible to define a ID column to be unique but every value have to be occur twice?
For example:
table TRANSLATION:
id | name_id | translation
____________|_________|____________
1 | 1 | apple
____________|_________|____________
2 | 1 | apfel
____________|_________|____________
3 | 2 | pear
____________|_________|____________
4 | 2 | birne
I want name_id values to always occur twice, not once and not three times. name_id is a FK from table with my objects that needs to be translated.
No, this is impossible to enforce, though you can attempt it using triggers this is normally a pretty messy solution.
I'd change your table structure to be something like the following:
ID
NAME_ID
LANGUAGE_ID
TRANSLATION
You could then create a unique index on NAME_ID and LANGUAGE_ID. Theoretically, you'd also have a table LANGUAGES, and the LANGUAGE_ID column would have a foreign key back into LANGUAGES.ID - you could then restrict the number of times each NAME_ID appears by not having the data in LANGUAGES.
Ultimately this means that your schema would look something like this:
create table languages (
id number
, description varchar2(4000)
, constraint pk_languages primary key (id)
);
insert into languages values (1, 'English');
insert into languages values (2, 'German');
create table names (
id number
, description varchar(4000)
, constraint pk_names primary key (id)
);
insert into names values (1, 'apple');
insert into names values (2, 'pear');
create table translations (
id number
, name_id number
, language_id number
, translation varchar2(4000)
, constraint pk_translations primary key (id)
, constraint fk_translations_names foreign key (name_id) references names (id)
, constraint fk_translations_langs foreign key (language_id) references languages (id)
, constraint uk_translations unique (name_id, language_id)
);
insert into translations values (1, 1, 1, 'apple');
insert into translations values (2, 1, 2, 'apfel');
insert into translations values (3, 2, 1, 'pear');
insert into translations values (4, 2, 2, 'birne');
and you should be unable to break the constraints:
SQL> insert into translations values (5, 1, 3, 'pomme');
insert into translations values (5, 1, 3, 'pomme')
*
ERROR at line 1:
ORA-02291: integrity constraint (FK_TRANSLATIONS_LANGS) violated - parent
key not found
SQL> insert into translations values (5, 1, 2, 'pomme');
insert into translations values (5, 1, 2, 'pomme')
*
ERROR at line 1:
ORA-00001: unique constraint (UK_TRANSLATIONS) violated
See this SQL Fiddle
Do you mean a maximum of twice? or do you mean they have to occur twice (i.e., once only is not ok)
If the former, Once only IS ok) then you could Add a bit field and make the Primary Key composite on the actual id and the bit field.
If the latter (They have to occur twice), then put two id fields in the same row and make then each a single field unique key.

postgresql partitioning master table duplicate entries

I ve created a simple partitioning structure as given below:
Master table
CREATE TABLE parent_table
(
id_n numeric(19,0) NOT NULL,
name_v character varying(255),
location_n numeric(19,0),
CONSTRAINT parent_table_pkey PRIMARY KEY (id_n )
)
Child tables
CREATE TABLE child_table_location_1
(
-- Inherited from table parent_table: id_n numeric(19,0) NOT NULL,
-- Inherited from table parent_table: name_v character varying(255),
-- Inherited from table parent_table: location_n numeric(19,0),
CONSTRAINT child_table_location_1_pkey PRIMARY KEY (id_n ),
CONSTRAINT child_table_location_1_location_n_check CHECK (location_n = 1::numeric)
)INHERITS (parent_table)
CREATE TABLE child_table_location_2
(
-- Inherited from table parent_table: id_n numeric(19,0) NOT NULL,
-- Inherited from table parent_table: name_v character varying(255),
-- Inherited from table parent_table: location_n numeric(19,0),
CONSTRAINT child_table_location_2_pkey PRIMARY KEY (id_n ),
CONSTRAINT child_table_location_2_location_n_check CHECK (location_n = 2::numeric)
)INHERITS (parent_table)
Trigger
CREATE OR REPLACE FUNCTION PARTITION_INSERTION_TRIGGER()
RETURNS TRIGGER AS
$$
BEGIN
EXECUTE 'INSERT INTO '|| QUOTE_IDENT('child_table_location_'||NEW.LOCATION_N)||' SELECT ($1).*' USING NEW;
RETURN NEW;
END;
$$
LANGUAGE PLPGSQL;
CREATE TRIGGER INSERT_INTO_PARTITION_TRIGGER BEFORE INSERT ON PARENT_TABLE FOR EACH ROW EXECUTE PROCEDURE PARTITION_INSERTION_TRIGGER();
I create the trigger such that based on the location field, the data gets populated in respective child tables.
Now I ve the below insert scripts:
INSERT INTO parent_table(id_n, name_v, location_n) VALUES (1, 'aaa', 1);
INSERT INTO parent_table(id_n, name_v, location_n) VALUES (2, 'bbb', 2);
INSERT INTO parent_table(id_n, name_v, location_n) VALUES (3, 'ccc', 1);
INSERT INTO parent_table(id_n, name_v, location_n) VALUES (4, 'ddd', 2);
INSERT INTO parent_table(id_n, name_v, location_n) VALUES (5, 'eee', 1);
INSERT INTO parent_table(id_n, name_v, location_n) VALUES (6, 'fff', 2);
When I select from individual tables:
Child Table 1
id_n| name_v| location_n
1|"aaa"|1
3|"ccc"|1
5|"eee"|1
Child Table 2
id_n| name_v| location_n
2|"bbb"|2
4|"ddd"|2
6|"fff"|2
But when I query on the master table:
Parent table
id_n|name_v|location_n
1|"aaa"|1
1|"aaa"|1
2|"bbb"|2
2|"bbb"|2
3|"ccc"|1
3|"ccc"|1
4|"ddd"|2
4|"ddd"|2
5|"eee"|1
5|"eee"|1
6|"fff"|2
6|"fff"|2
I get duplicate entries from the parent_table even after having the primary key constraint on the id field.
Why it is happening and what changes should I make on my design.
Awaiting quick response.
Thanks in advance.
Returning NULL is not the clean way.
The elegant solution is to have the trigger "INSTEAD OF"
See: http://www.postgresql.org/docs/9.3/static/sql-createtrigger.html

PostgreSQL Addition in Primary Key in Create Table

i'm facing a problem with a primary key in PostgreSQL, my plan was to make an addition of two values and set this to one primary key, how could this be realized (first try below):
/* Tabelle fuer die Test*/
create table Test(
var_a integer,
var_b integer,
var_key integer,
var_key = var_a + var_b,
primarykey(var_key),
);
if i call this with a foreign key then it should be one value:
foreign key (var_key_f) references Test(var_key),
EDIT: I know th option of two multiple primary keys entries but i want to have only one primary key, so that i not have to reference over two vars again. I need to have both variables generated to one primary key.
It appears this can be accomplished without triggers: (pg-9.3):
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE test
( var_key INTEGER NOT NULL PRIMARY KEY
, var_a INTEGER NOT NULL
, var_b INTEGER NOT NULL
, var_key_f INTEGER REFERENCES test(var_key)
, CONSTRAINT the_sum CHECK (var_a+var_b = var_key)
);
INSERT INTO test(var_key, var_a, var_b) VALUES(42, 21, 21); -- Ok
INSERT INTO test(var_key, var_a, var_b) VALUES(666, 660, 6); -- Ok
INSERT INTO test(var_key, var_a, var_b) VALUES(34, 21, 11); -- bad sum
INSERT INTO test(var_key, var_a, var_b) VALUES(666, 600, 66); -- duplicate sum
INSERT INTO test(var_key, var_a, var_b, var_key_f) VALUES(14, 6, 8, 42); -- Ok
INSERT INTO test(var_key, var_a, var_b, var_key_f) VALUES(13, 5, 8, 43); -- Bad FK
Result:
NOTICE: drop cascades to table tmp.test
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 1
INSERT 0 1
ERROR: new row for relation "test" violates check constraint "the_sum"
DETAIL: Failing row contains (34, 21, 11, null).
ERROR: duplicate key value violates unique constraint "test_pkey"
DETAIL: Key (var_key)=(666) already exists.
INSERT 0 1
ERROR: insert or update on table "test" violates foreign key constraint "test_var_key_f_fkey"
DETAIL: Key (var_key_f)=(43) is not present in table "test".