Unique constraint violation in PostgreSQL Trigger - postgresql-9.1

I have below two tables. I wrote after insert trigger on employees table. If i insert the record in employees table it will insert the record in employee_audits table.
Both table have primary key column (id). suppose if you try to insert record ID value which does not exists in employees table and exists in employee_audits table, it shows the
error duplicate key value violates unique constraint "employee_audits_pkey" and also it is not inserting record in employees table. Both transaction failed.
But i want to insert the record in employees table.
CREATE TABLE employees(
id SERIAL PRIMARY KEY,
first_name VARCHAR(40) NOT NULL,
last_name VARCHAR(40) NOT NULL
);
CREATE TABLE employee_audits (
id SERIAL PRIMARY KEY,
last_name VARCHAR(40) NOT NULL,
changed_on TIMESTAMP(6) NOT NULL
)
The trigger function:
CREATE OR REPLACE FUNCTION log_last_name_changes()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO employee_audits(last_name,changed_on)
VALUES(NEW.last_name,now());
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
The trigger definition:
CREATE TRIGGER last_name_changes
AFTER INSERT
ON employees
FOR EACH ROW
EXECUTE PROCEDURE log_last_name_changes();
INSERT INTO employees (first_name, last_name)
VALUES ('John', 'Doe');
SELECT * FROM EMPLOYEES
id first_name last_name
1 "John" "Doe"
SELECT * FROM EMPLOYEE_AUDITS
ID last_name CHANGED_ON
1 "Doe" "2019-12-27 17:21:13.934"
Manual insert on second table
insert into employee_audits values(2,'banu','2019-12-27 17:21:13.934')
Manual insert on first table
INSERT INTO employees (first_name, last_name)
VALUES ('David', 'Raj');
Error duplicate key value violates unique constraint "employee_audits_pkey"
is it possible to insert record in employees table?

If you have a serial column you should never provide the value for it manually. Manually providing a value for a serial will not advance the sequence behind that column, so the next time you insert without specifying the id column, the next sequence value will be taken which is 2 as the sequence was only advanced once.
So instead of:
insert into employee_audits values(2,'banu','2019-12-27 17:21:13.934')
Just use:
insert into employee_audits (last_name, changed_at)
values ('banu','2019-12-27 17:21:13.934');
This behaviour of the serial columns is one of the reasons why it's highly recommended to use identity columns with modern Postgres versions.

Related

Oracle 11g Triggers

I have create a table person(id, name ,samenamecount).The samenamecount attribute can be null but for each row can store the row count for same names.I am achieving this by calling a stored procedure inside a after insert trigger.Below is my code.
create or replace procedure automatic(s in person.name%type)
AS
BEGIN
update person set samenamecount=(select count(*) from person where name=s) where name=s;
END;
create or replace trigger inserttrigger
after insert
on person
for each row
declare
begin
automatic(:new.name);
end;
On inserting a row it is giving error like
table ABCD.PERSON is mutating, trigger/function may not see it.
Can somebody help me to figure out this?
If you have the table:
CREATE TABLE person (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT person__id__pk PRIMARY KEY,
name VARCHAR2(20)
NOT NULL
);
Then rather than creating a trigger, instead, you could use a view:
CREATE VIEW person_view (
id,
name,
samenamecount
) AS
SELECT id,
name,
COUNT(*) OVER (PARTITION BY name)
FROM person;
You can use the trigger:
CREATE TRIGGER inserttrigger
AFTER INSERT ON person
BEGIN
MERGE INTO person dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY name) AS cnt
FROM person
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE SET samenamecount = src.cnt;
END;
/
fiddle
If you want to make it more efficient then you could use a compound trigger and collate the names that are being inserted and only update the matching rows.

Insert the id from a previous query inside a sqlite trigger

In a sqlite3 database I would like to create a trigger on a view so that I can insert data over the view. Inside the trigger I would like to insert something in the tblmedia table. The id of the inserted row should be now also inserted into the tblbook as id.
In sqlite there are no variables. Otherwise I would store the returning value in the variable and would use it in the second query.
Can this even be achieved in sqlite?
Following my sql schema:
CREATE TABLE tblmedia(
id INTEGER PRIMARY KEY NOT NULL,
title VARCHAR NOT NULL,
raiting INTEGER,
file_name VARCHAR NOT NULL,
media_type TEXT NOT NULL
);
CREATE TABLE tblbook(
id INTEGER PRIMARY KEY NOT NULL,
author VARCHAR,
FOREIGN KEY (id) REFERENCES tblmedia(id) ON DELETE CASCADE
);
CREATE VIEW book AS
SELECT
m.id as id,
m.title as title,
b.author as author,
m.raiting as raiting,
m.file_name as file_name
FROM tblbook b
LEFT JOIN tblmedia m ON m.id = b.id;
CREATE TRIGGER insert_book
INSTEAD OF INSERT ON book
BEGIN
INSERT INTO tblmedia(title, raiting, file_name)
VALUES(new.title, new.raiting, new.file_name);
INSERT INTO tblbook(id, author)
VALUES (xx, new.author); -- xx should be the id from the previous insert
END

How to write a delete trigger in Oracle PL/SQL - stuck on identifying "matching rows"

I have a trigger I'm writing whereby , once I delete a row, I want to delete the corresponding row in the another table (which is common_cis.security_function ).
and the source table is party.security_function
Here are the columns in common_cis.security_function :
URL
SCRTY_FUNC_NAME
SCRTY_FUNC_DESC
IDN
CREATE_TMSTMP
CNCRCY_USER_IDN
Here are the columns in party.security_function :
UPDATE_USER_SRC_SYS_CD
UPDATE_USER_ID
UPDATE_TS
SCRT_FUNC_NM
SCRT_FUNC_DESC
CREAT_USER_SRC_SYS_CD
CREAT_USER_ID
CREAT_TS
What I have so far is :
delete from common_cis.security_function CCSF
where CCSF.SCRTY_FUNC_NAME = :new.SCRT_FUNC_NM;
Is this the right idea? Or do I use some kind of row-ID ?
thanks
I think you should use integrity constraints for that, namely foreign key constraint with "ON DELETE CASCADE" condition.
Here is an example, but check first is there tables in your schema with names that I used:
-- create tables:
create table master_table(
URL varchar2(1000),
SCRTY_FUNC_NAME varchar2(100),
SCRTY_FUNC_DESC varchar2(1000));
create table detail_table(
SCRT_FUNC_NM varchar2(100),
SCRT_FUNC_DESC varchar2(1000),
UPDATE_USER_ID number,
UPDATE_TS varchar2(100));
-- add primary key and foreign key constraints:
alter table master_table add constraint function_pk primary key (SCRTY_FUNC_NAME);
alter table detail_table add constraint function_fk foreign key (SCRT_FUNC_NM) references master_table (SCRTY_FUNC_NAME) on delete cascade;
-- fill tables with data:
insert into master_table
values ('url number 1', 'sec function #1', 'description of function #1');
insert into detail_table
values('sec function #1', 'description', 1, '123abc');
insert into detail_table
values('sec function #1', 'description', 2, '456xyz');
-- check tables: first contains 1 row and second - 2 rows
select count(*) from master_table;
select count(*) from detail_table;
-- delete rows from first table only:
delete from master_table;
-- check tables once again - both are empty:
select count(*) from master_table;
select count(*) from detail_table;
-- clear test tables:
drop table detail_table;
drop table master_table;

Can multitable Insert statement combine columns from view and columns from another view?

I'm planning to do SQL expert examination.
I have doubts that answer D is correct:
Evaluate the following command:
CREATE TABLE employees
( employee_id NUMBER(2) PRIMARY KEY
, last_name VARCHAR2(25) NOT NULL
, department_id NUMBER(2)NOT NULL
, job_id VARCHAR2(8)
, salary NUMBER(10,2));
You issue
the following command to create a view that displays the IDs and last
names of the sales staff in the organization:
CREATE OR REPLACE VIEW sales_staff_vu AS
SELECT employee_id, last_name,job_id
FROM employees
WHERE job_id LIKE 'SA_%'
WITH CHECK OPTION;
Which two statements are true regarding the above view? (Choose two.)
A. It allows you to insert rows into the EMPLOYEES table .
B. It allows you to delete details of the existing sales staff from
the EMPLOYEES table.
C. It allows you to update job IDs of the existing sales staff to any
other job ID in the EMPLOYEES table.
D. It allows you to insert IDs, last names, and job IDs of the sales
staff from the view if it is used in multitable INSERT statements.
Source
A is FALSE as the view doesn't allow inserting into the department_id column which is mandatory.
B is TRUE, although it would be more accurate to say that the view only allows deletions of employees where the job_id matches the predicate LIKE 'SA_%'.
C is FALSE, as the WITH CHECK OPTION means that you can't change the job_id if the new job_id doesn't match the view's predicate.
D is FALSE: a multitable insert statement can't just insert some columns into the view and the remaining columns into the employees table. Even if you join the view to the table, the insert must still insert into the base table, not into the view:
insert into
(select e.employee_id, e.last_name, e.department_id, e.job_id
from sales_staff_vu v
join employees e
on v.employee_id = e.employee_id)
values
(1, 'KEMP', 2, 'SA_X');
I suspect this is a test of your ability to verify and ignore wrong information on the internet - i.e. 99% of sites say D is true!
Now, my answer can be easily disproved by crafting a multitable insert statement that successfully inserts via the view.
According to my knowledge answer D is wrong.Reasons are
1.If A is wrong, definitely D is wrong.because department_id column is mandatory field on the table, but it is not mentioned in view.so we can't insert a row using this view.
2.In Answer D,multitable INSERT statements are INSERT ALL, INSERT FIRST,etc.
to check this Answer,i have tried these steps
CREATE TABLE employees123
( employee_id NUMBER(2) PRIMARY KEY
, last_name VARCHAR2(25) NOT NULL
, department_id NUMBER(2)NOT NULL
, job_id VARCHAR2(8)
, salary NUMBER(10,2));
CREATE OR REPLACE VIEW sales_staff_vu123 AS
SELECT employee_id, last_name,job_id,department_id
FROM employees123
WHERE job_id LIKE 'SA_%'
WITH CHECK OPTION;
--department_id is added to view
--here i am trying to insert my employees table rows to employees123 table
INSERT ALL
INTO sales_staff_vu123 --using View
SELECT employee_id, last_name,job_id,department_id
FROM employees;
Error at Command Line:153 Column:15
Error report:
SQL Error: ORA-01702: a view is not appropriate here
01702. 00000 - "a view is not appropriate here"
*Cause:
*Action:
So my decision is we cant use views with multitable insert statements.

Refactor SQLite Table by splitting it in two and link with foreign keys

I'm working on a SQLite Database. The database is already filled, but I want to refactor it. Here is a sample of what I need to do:
I currently have one table:
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT,
EngineCap FLOAT);
I want to split this into two tables:
CREATE TABLE Vehicles (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT);
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
VehicleID INTEGER CONSTRAINT FK_Cars REFERENCES [Vehicles](ID),
EngineCap FLOAT);
I have figured out to create a temporary table with the Cars table contents, and I can fill up the Vehicles table with the contents of the Cars table:
CREATE TEMPORARY TABLE Cars_temp AS SELECT * FROM Cars;
INSERT INTO Vehicles (Name, TopSpeed)
SELECT Name, TopSpeed FROM Cars_temp;
But I am still looking for a way to go over that same selection, while putting the EngineCap field into the new Cars table and somehow extracting the corresponding ID value from the Vehicles table to put into the VehicleID foreign key field on the Cars table.
I'm open for workaround or alternative approaches.
Thanks.
Since #mateusza did not provide an example, I've made one:
Suppose you have this table:
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
Now you want to move street and city into a separate table Address, so you'll end up with two tables:
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(For this example, I'm doing the conversion in the same database. You'd probably use two DBs, converting one into the other, with an SQL ATTACH command.)
Now we create a view (which imitates our original table using the new tables) and the trigger:
CREATE VIEW Customer1 (name, street, city) AS
SELECT C.name, A.street, A.city FROM Customer2 AS C
JOIN Address as A ON (C.addr == A.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Customer1 FOR EACH ROW BEGIN
INSERT INTO Address (street, city) SELECT NEW.street, NEW.city;
INSERT INTO Customer2 (addr, name) SELECT last_insert_rowid(), NEW.name;
END;
Now you can copy the table rows:
INSERT INTO Customer1 (name, street, city) SELECT name, street, city FROM Customer;
The above is a simplified case where you'd only move some data into a single new table.
A more complex (and more general) case is where you want to...
Separate your original table's columns into several foreign tables, and
Have unique entries in the foreign tables (that's usually the reason why you'd refactor your table).
This adds some additional challenges:
You'll end up inserting into multiple tables before you can insert their rowids into the table with the referencing rowids. This requires storing the results of each INSERT's last_insert_rowid() into a temporary table.
If the value already exists in the foreign table, its rowid must be stored instead of the one from the (non-executed) insertion operation.
Here's a complete solution for this. It manages a database of music records, constisting of a song's name, album title and artist name.
-- Original table
CREATE TABLE [Song] (
[title] TEXT,
[album] TEXT,
[artist] TEXT
);
-- Refactored tables
CREATE TABLE [Song2] (
[title] TEXT,
[album_rowid] INTEGER,
[artist_rowid] INTEGER
);
CREATE TABLE [Album] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[title] TEXT UNIQUE
);
CREATE TABLE [Artist] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[name] TEXT UNIQUE
);
-- Fill with sample data
INSERT INTO Song VALUES ("Hunting Girl", "Songs From The Wood", "Jethro Tull");
INSERT INTO Song VALUES ("Acres Wild", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Broadford Bazar", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Statue of Liberty", "White Music", "XTC");
INSERT INTO Song VALUES ("Standing In For Joe", "Wasp Star", "XTC");
INSERT INTO Song VALUES ("Velvet Green", "Songs From The Wood", "Jethro Tull");
-- Conversion starts here
CREATE TEMP TABLE [TempRowIDs] (
[album_id] INTEGER,
[artist_id] INTEGER
);
CREATE VIEW Song1 (title, album, artist) AS
SELECT Song2.title, Album.title, Artist.name
FROM Song2
JOIN Album ON (Song2.album_rowid == Album.rowid)
JOIN Artist ON (Song2.artist_rowid == Artist.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Song1 FOR EACH ROW BEGIN
INSERT OR IGNORE INTO Album (title) SELECT NEW.album;
UPDATE TempRowIDs SET album_id = (SELECT COALESCE (
(SELECT rowid FROM Album WHERE changes()==0 AND title==NEW.album), last_insert_rowid()
) ) WHERE rowid==1;
INSERT OR IGNORE INTO Artist (name) SELECT NEW.artist;
UPDATE TempRowIDs SET artist_id = (SELECT COALESCE (
(SELECT rowid FROM Artist WHERE changes()==0 AND name==NEW.artist), last_insert_rowid()
) ) WHERE rowid==1;
INSERT INTO Song2 (title, album_rowid, artist_rowid) SELECT
NEW.title, (SELECT album_id FROM TempRowIDs), (SELECT artist_id FROM TempRowIDs);
END;
INSERT INTO TempRowIDs DEFAULT VALUES;
INSERT INTO Song1 (title, album, artist) SELECT title, album, artist FROM Song;
DROP TRIGGER TempTrig;
DROP TABLE TempRowIDs;
-- Conversion ends here
-- Print results
SELECT * FROM Song;
SELECT * FROM Song1;
-- Check if original and copy are identical (https://stackoverflow.com/a/13865679/43615)
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT * FROM Song UNION SELECT * FROM Song1)) == (SELECT COUNT() FROM Song) THEN 'Success' ELSE 'Failure' END;
Note that this example has one potential issue: If the constraints on the foreign table are more complex, the SELECT rowid FROM search for the existing entry needs to be updated accordingly. Ideally, SQLite should provide a way to determine the conflicting rowid somehow, but it doesn't, unfortunately (see this related question).
Simple solution without triggers:
create VEHICLES_TEMP table including the CAR_ID
create your new CARS table without the VEHICLES columns you don't want
update CARS with VEHICLE_ID taken from VEHICLES_TEMP (identified by the CAR_ID)
create final VEHICLES table without the CAR_ID
Create a table New_Cars and a INSTEAD OF INSERT trigger, which will insert data to both tables Vehicles and Cars. When inserting to Cars, you can use last_insert_rowid() function to refer to inserted row in Vehicles table.
This can be temporary solution, or you can leave it in your database for further modifications.

Resources