This question already has an answer here:
Checking if date in table B is between date in Table A before inserting SQLite
(1 answer)
Closed 1 year ago.
I have two tables that both have separate start/end date values in them. One Project can contain many Plans, and a specific Plan start/end date should be between its Project start/end date. I dont know how to validate this, and I have tried to use triggers but I just cant figure it out. Can someone give me some pointers on what I'm doing wrong?
This is my trigger:
%%sql
CREATE TRIGGER beforeInsertInPlan BEFORE INSERT ON Plan FOR EACH ROW
BEGIN
SELECT proID.projectID FROM Project
INNER JOIN Project ON pID = Project.projectID
WHERE
And here are my two tables:
%%sql
DROP TABLE IF EXISTS Project;
CREATE TABLE Project (
projectID varchar(255) NOT NULL UNIQUE,
name varchar(255) NOT NULL DEFAULT ' ',
leader varchar(255) NOT NULL DEFAULT ' ',
budget varchar(255) NOT NULL DEFAULT '0',
startDate DATE NOT NULL DEFAULT '2000-12-31',
endDate DATE NOT NULL DEFAULT '2000-12-31'
CHECK (JulianDay(startDate) <= JulianDay(endDate)),
PRIMARY KEY (projectID)
);
and:
%%sql
DROP TABLE IF EXISTS Plan;
CREATE TABLE Plan (
pID varchar(255) NOT NULL UNIQUE,
projectID varchar(255) DEFAULT NULL,
name varchar(255) NOT NULL DEFAULT ' ',
startDate DATE NOT NULL DEFAULT ' ',
endDate DARE NOT NULL DEFAULT ' '
CHECK (JulianDay(startDate) <= JulianDay(endDate) AND (startDate >= Project.startDate) AND
(endDate <= Project.endDate)),
PRIMARY KEY (pID, projectID),
FOREIGN KEY (projectID) REFERENCES Project(projectID)
);
First a BEFORE INSERT trigger will probably result in nothing but issues. see https://sqlite.org/lang_createtrigger.html#cautions_on_the_use_of_before_triggers
So here's a trigger that I believe will work as intended albeit it deleting the inserted row:-
CREATE TRIGGER IF NOT EXISTS afterInsertInPlan
AFTER INSERT ON plan
WHEN (
(NOT (new.startdate) BETWEEN
(SELECT startdate FROM project WHERE projectID = new.projectID)
AND
(SELECT enddate FROM project WHERE projectID = new.projectID)
)
OR
(NOT (new.enddate) BETWEEN
(SELECT startdate FROM project WHERE projectID = new.projectID)
AND
(SELECT enddate FROM project WHERE projectID = new.projectID)
)
)
BEGIN
DELETE FROM plan WHERE pID = new.pID ;
END
;
Testing/Demo
The above was tested using :-
DROP TABLE IF EXISTS Project;
CREATE TABLE Project (
projectID varchar(255) NOT NULL UNIQUE,
name varchar(255) NOT NULL DEFAULT ' ',
leader varchar(255) NOT NULL DEFAULT ' ',
budget varchar(255) NOT NULL DEFAULT '0',
startDate DATE NOT NULL DEFAULT '2000-12-31',
endDate DATE NOT NULL DEFAULT '2000-12-31'
CHECK (JulianDay(startDate) <= JulianDay(endDate)),
PRIMARY KEY (projectID)
);
DROP TABLE IF EXISTS Plan;
CREATE TABLE Plan (
pID varchar(255) NOT NULL UNIQUE,
projectID varchar(255) DEFAULT NULL,
name varchar(255) NOT NULL DEFAULT ' ',
startDate DATE NOT NULL DEFAULT ' ' ,
endDate DATE NOT NULL DEFAULT ' ',
CHECK (endDate >= startDate),
PRIMARY KEY (pID, projectID),
FOREIGN KEY (projectID) REFERENCES Project(projectID)
);
DROP TABLE IF EXISTS trigger_log;
CREATE TABLE IF NOT EXISTS trigger_log (id INTEGER PRIMARY KEY, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, trigger_text TEXT);
DROP TRIGGER IF EXISTS beforeInsertInPlan;
CREATE TRIGGER IF NOT EXISTS afterInsertInPlan
AFTER INSERT ON plan
WHEN (
(NOT (new.startdate) BETWEEN
(SELECT startdate FROM project WHERE projectID = new.projectID)
AND
(SELECT enddate FROM project WHERE projectID = new.projectID)
)
OR
(NOT (new.enddate) BETWEEN
(SELECT startdate FROM project WHERE projectID = new.projectID)
AND
(SELECT enddate FROM project WHERE projectID = new.projectID)
)
)
BEGIN
DELETE FROM plan WHERE pID = new.pID ;
INSERT INTO trigger_log (trigger_text) VALUES('DELETED FROM Plan Table due to date(s) not within project. pID was '||new.pID);
END
;
INSERT INTO project VALUES ('P1','P1','Mary',100,'2021-09-01','2022-09-30');
INSERT INTO plan VALUES ('P1P1','P1','Plan1','2021-09-01','2022-09-30');
INSERT INTO plan VALUES ('P1P2','P1','Plan2','2021-08-01','2022-09-30'); /* X */
INSERT INTO plan VALUES ('P1P3','P1','Plan3','2021-09-01','2022-10-30'); /* X */
INSERT INTO plan VALUES ('P1P4','P1','Plan4','2020-09-01','2022-10-30'); /* X */
INSERT INTO plan VALUES ('P1P5','P1','Plan5','2021-09-01','2021-10-01');
INSERT INTO plan VALUES ('P1P6','P1','Plan6','2021-10-01','2021-11-01');
INSERT INTO plan VALUES ('P1P7','P1','Plan7','2021-11-01','2021-12-01');
SELECT * FROM plan;
/* Cleanup Environment */
SELECT * FROM trigger_log;
DROP TABLE IF EXISTS trigger_log;
DROP TRIGGER IF EXISTS beforeInsertInPlan;
DROP TABLE IF EXISTS Plan;
DROP TABLE IF EXISTS Project;
When run then the results are :-
The Plan's in the plan table:-
i.e. those commented with an X (3) were not inserted
The trigger_log (used to confirm triggering when testing) :-
i.e. the 3 commented with an X that were not inserted have been logged accordingly.
Example of why not to use a BEFORE INSERT trigger
Swapping the trigger to use BEFORE INSERT and :-
All are inserted :-
None are deleted even though logged:-
i.e. nothing to delete as nothing has been inserted.
Related
It give me error example image at below:
Trigger code:
CREATE OR REPLACE TRIGGER InsertNewStaffs
BEFORE INSERT ON Staffs
FOR EACH ROW
ENABLE
DECLARE
v_user varchar(255);
v_date varchar(255);
v_Staffs_ID Staffs.Staffs_ID%TYPE;
v_Staffs_Name Staffs.Staffs_Name%TYPE;
v_Staffs_Contact_Number Staffs.Staffs_Contact_Number%TYPE;
v_Staffs_Email Staffs.Staffs_Email%TYPE;
v_Orders_ID Staffs.Orders_ID%TYPE;
v_count INTEGER;
BEGIN
SELECT count(*) INTO v_count FROM Staffs
WHERE Staffs_ID = v_Staffs_ID OR
Staffs_Name = v_Staffs_Name OR
Staffs_Contact_Number = v_Staffs_Contact_Number OR
Staffs_Email = v_Staffs_Email;
IF v_count > 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Oops, some data is already exists. Please try again...');
DBMS_OUTPUT.PUT_LINE('Oops, some data is already exists. Please try again...');
SELECT user, TO_CHAR(sysdate, 'DD/MON/YYYY HH24:MI:SS') INTO v_user, v_date FROM dual;
ELSE
INSERT INTO Staffs(Staffs_ID, Staffs_Name, Staffs_Contact_Number, Staffs_Email, Orders_ID)
VALUES(v_Staffs_ID, v_Staffs_Name, v_Staffs_Contact_Number, v_Staffs_Email, v_Orders_ID);
DBMS_OUTPUT.PUT_LINE('One Row Inserted By ' || v_user || CHR(10));
DBMS_OUTPUT.PUT_LINE('Inserted data at ' || v_date);
INSERT INTO monitorInsertStaffs(user_name, entry_date, operation)
VALUES(v_user, v_date, 'Insert');
END IF;
END;
/
My Table:
CREATE TABLE Staffs(
Staffs_ID char(20) NOT NULL,
Staffs_Name varchar(255) NOT NULL,
Staffs_Contact_Number varchar(50) NOT NULL,
Staffs_Email varchar(255) NOT NULL,
Orders_ID char(20),
PRIMARY KEY (Staffs_ID),
FOREIGN KEY (Orders_ID) REFERENCES Orders(Orders_ID)
);
CREATE TABLE Orders(
Orders_ID char(20) NOT NULL,
Order_Date DATE NOT NULL,
Order_Status varchar(255) NOT NULL,
Order_Quantity int NOT NULL,
Order_TotalAmount NUMERIC(10,2) NOT NULL,
Order_TotalPrice NUMERIC(10,2) NOT NULL,
PRIMARY KEY (Orders_ID),
Pets_Products_ID char(20),
CustomerID char(20),
FOREIGN KEY (Pets_Products_ID) REFERENCES Pets_Products(Pets_Products_ID),
FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);
I try to insert data and if the data has existed it will show RAISE_APPLICATION_ERROR(-20000, 'Oops, some data is already exists. Please try again...'); but it didn't show the message and also cannot insert data when no exists the data.
I don't know where is error code that I find.
The whole concept is just wrong.
you've based trigger on a table into which you're just inserting a row (staffs)
then you're selecting from the same table (it'll raise the mutating table error if you try to insert more than a single row)
the where clause uses local variables that have no values
insert into staffs cause the same trigger to fire over and over again, until Oracle concludes that that's enough and raises the error
Don't use a trigger. Use UNIQUE constraints:
CREATE TABLE Staffs(
Staffs_ID char(20) NOT NULL,
Staffs_Name varchar(255) NOT NULL,
Staffs_Contact_Number varchar(50) NOT NULL,
Staffs_Email varchar(255) NOT NULL,
Orders_ID char(20),
PRIMARY KEY (Staffs_ID),
UNIQUE (Staffs_Name),
UNIQUE (Staffs_Contact_Number),
UNIQUE (Staffs_Email),
FOREIGN KEY (Orders_ID) REFERENCES Orders(Orders_ID)
);
(However, you should also consider whether your business requirements make sense or if you can have multiple staff members called Jane Smith or if you can have two staff members who share an office with the same telephone number?)
If you want to use a logging table then use an autonomous transaction to just insert into that table:
CREATE OR REPLACE TRIGGER InsertNewStaffs
BEFORE INSERT ON Staffs
FOR EACH ROW
ENABLE
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO monitorInsertStaffs(
user_name, entry_date, operation
) VALUES(
:NEW.Staffs_ID, SYSDATE, 'Insert'
);
COMMIT;
END;
/
db<>fiddle here
I want to assure at inserting a manager that department manager start date [DEPARTMENT.mgr_start_date] is coming after his birthdate [EMPLOYEE.bdate],
how can I do that?
CREATE TABLE IF NOT EXISTS EMPLOYEE
(
ssn INT(16) unsigned NOT NULL,
fname VARCHAR(16),
lname VARCHAR(16),
bdate DATE,
address VARCHAR(32),
gender enum('m','f'),
salary decimal(16,2),
Dno VARCHAR(8),
PRIMARY KEY (ssn)
);
CREATE TABLE IF NOT EXISTS DEPARTMENT
(
mgr_ssn INT(16) unsigned,
Dname VARCHAR(32),
mgr_start_date DATE,
Dnumber VARCHAR(8),
PRIMARY KEY (Dnumber),
FOREIGN KEY (mgr_ssn) REFERENCES EMPLOYEE(ssn)
);
You would have to do this with a trigger.
CHECK constraints can reference only columns in the table where the constraint is defined.
The full SQL standard includes a type of constraint called an ASSERTION, which allows multi-table constraints, but MariaDB does not implement this feature of SQL (very few brands of SQL databases do implement it).
CREATE TRIGGER t BEFORE INSERT ON DEPARTMENT
FOR EACH ROW BEGIN
IF NEW.mgr_start_date < (SELECT bdate FROM EMPLOYEE WHERE ssn = NEW.mgr_ssn) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'manager is way too young';
END IF;
END
Test:
insert into EMPLOYEE set ssn=123, bdate='2021-01-01';
insert into DEPARTMENT set mgr_ssn=123, dnumber='1', mgr_start_date='2010-01-01';
ERROR 1644 (45000): manager is way too young
I have a table with multiple columns and one (unique key) should be a value composed from the values of other two columns.
CREATE TABLE batches (
id TEXT PRIMARY KEY UNIQUE,
name TEXT NOT NULL,
project_id INTEGER);
On each insert, I want to generate the id based on the value of 'name' and 'project_id' (this one can be null):
INSERT INTO batches (name,project_id) VALUES
('21.01',NULL),
('21.01',1),
('21.02',2);
So, I have created a table TRIGGER but doesn't execute.
CREATE TRIGGER create_batches_id
AFTER INSERT ON batches FOR EACH ROW
BEGIN
UPDATE batches
SET id = SELECT quote(name ||"_"|| (CASE project_id
WHEN NULL THEN '' ELSE project_id END )
FROM batches WHERE rowid = (SELECT MAX(rowid) FROM batches))
WHERE rowid = (SELECT MAX(rowid) FROM batches);
END;
Error:
SQL Error [1]: [SQLITE_ERROR] SQL error or missing database (near "SELECT": syntax error)
I expect:
id = 21.01_
id = 21.01_1
id = 21.01_2
What am I doing wrong? If I run only the SELECT/CASE statment it returns ok: '21.01_2'
I have also tried without the quote() function, no success.
UPDATE I:
I have managed to execute the whole create trigger statement (parenthesis were missing):
CREATE TRIGGER create_batch_id
AFTER INSERT ON batches FOR EACH ROW
BEGIN
UPDATE batches
SET id = (SELECT name ||"_"|| (CASE project_id WHEN NULL THEN 0 ELSE project_id END ) FROM batches WHERE rowid = (SELECT MAX(rowid) FROM batches) )
WHERE rowid = (SELECT MAX(rowid) FROM batches);
END;
It seems my editor (DBeaver) has a glitch with the following new line character. If it is inside the selection it runs into this exception (or I am missing something):
SQL Error [1]: [SQLITE_ERROR] SQL error or missing database (incomplete input)
If I manually select only the above lines (from CREATE to ;), the trigger is created, however, not the expected result. If value in project_id is NULL, no id value is created.
Don't add the column id in the table.
Instead define the combination of name and project_id as the PRIMARY KEY of the table, so that it is also UNIQUE:
CREATE TABLE batches (
name TEXT NOT NULL,
project_id INTEGER,
PRIMARY KEY(name, project_id)
)
Then, whenever you need that id you can run a query:
SELECT name || '_' || COALESCE(project_id, '') AS id,
name,
project_id
FROM batches
Or create a view:
CREATE VIEW v_batches AS
SELECT name || '_' || COALESCE(project_id, '') AS id,
name,
project_id
FROM batches
and query the view:
SELECT * FROM v_batches
See the demo.
Or if your version of SQLite is 3.31.0+ you can have the column id as a generated column:
CREATE TABLE batches (
name TEXT NOT NULL,
project_id INTEGER,
id TEXT GENERATED ALWAYS AS (name || '_' || COALESCE(project_id, '')),
PRIMARY KEY(name, project_id)
);
Given:
CREATE TABLE worktags (
worktag_id integer not null primary key,
worktag character(32) not null default '' unique,
...
last_updated character(32) not null default '[Error]'
);
CREATE TABLE truefacts (
about character(32) not null primary key,
fact character(32) not null
);
The following:
CREATE TRIGGER zz_worktags_last_updated AFTER UPDATE ON worktags BEGIN
UPDATE worktags SET
last_updated = truefacts.fact WHERE truefacts.about = 'Last Worktag Update';
END;
gives the error:
Error: near line 52: no such column: truefacts.fact
But the column exists, and the syntax diagram seems to indicate that
[[schema-name . ] table-name . ] column-name
is a legal expr for the right-side of a SET column-name = expr.
You would need to use a subquery to access the other (truefacts) table (as there is no FROM truefacts anywhere) e.g. :-
CREATE TRIGGER zz_worktags_last_updated AFTER UPDATE ON worktags BEGIN
UPDATE worktags SET
last_updated = (SELECT fact FROM truefacts WHERE about = 'Last Worktag Update');
END;
Saying that, there is then no need for the TRIGGER as the subquery could be embedded into the UPDATE.
e.g. consider the following example :-
DROP TRIGGER IF EXISTS zz_worktags_last_updated;
DROP TABLE IF EXISTS worktags;
DROP TABLE IF EXISTS truefacts;
CREATE TABLE worktags (
worktag_id integer not null primary key,
worktag character(32) not null default '' unique,
last_updated character(32) not null default '[Error]'
);
CREATE TABLE truefacts (
about character(32) not null primary key,
fact character(32) not null
);
INSERT INTO truefacts VALUES('Last Worktag Update','xxx');
INSERT INTO worktags (worktag,last_updated) VALUES('mytag',(datetime('now')));
SELECT * FROM worktags;
UPDATE worktags SET last_updated = (SELECT fact FROM truefacts WHERE about = 'Last Worktag Update'), worktag = 'aaaa' WHERE worktag_id = 1;
SELECT * FROM worktags;
UPDATE truefacts SET fact = 'zzzz' WHERE rowid = 1;
CREATE TRIGGER zz_worktags_last_updated AFTER UPDATE ON worktags BEGIN
UPDATE worktags SET
last_updated = (SELECT truefacts.fact FROM truefacts WHERE truefacts.about = 'Last Worktag Update');
END;
UPDATE worktags SET worktag = 'bbbb' WHERE worktag_id = 1;
SELECT * FROM worktags;
This :-
Drops the tables and triggers if they exist (so it can be rerun)
Crate the 2 tables and populates them.
Selects everything from the worktags table (just the 1 row)
Updates the row in the worktags table using a subquery (this is the no trigger required example)
Selects everything from the updated (without a trigger) worktags table.
Updates the fact column of the truefacts (to show that the trigger works)
6.Creates the trigger.
Updates the row in the worktags table, changing the worktag column, leaving the change to the last_updated column to be done by the trigger.
Selects everything from the updated by the trigger worktags table.
Running the above results in :-
and lastly
I have created a table with an Id column as varchar(20).
I need a stored procedure which can increment id by 1.
I have tried this:
ALTER PROCEDURE dbo.spInsertCatQuery
(#Users_Id varchar(20),
#Cat_Id varchar(20),
#Query varchar(100),
#Query_Title varchar(50)
)
AS
BEGIN
Declare #Query_Id bigint
SELECT #Query_Id = coalesce((select max(Query_Id) + 1 from tblCatQuery), 1);
INSERT INTO tblCatQuery
VALUES(#Query_Id, #Users_Id, #Cat_Id, #Query_Title, #Query)
END
But it is not working after 10th record.
Change the selection of Query_id from your table to below
SELECT #Query_Id=
coalesce((select max(cast(Query_Id as int)) + 1 from tblCatQuery), 1);
Based on Gordon's comment; my understanding is that since ID is varchar max(id) is not fetching the correct max value but casting it will do so.
For example try this
create table testtab (id varchar(10));
insert into testtab values(2),(200),(53)
If you say below it will return 53
select MAX(id) from testtab
but this one will return 200
select MAX(cast(id as int)) from testtab
Tested in SQL SERVER 2008 R2
You do know your stored procedure has an implicit race condition, don't you?
Between your calculating the new query id and your table insert getting committed, another session can come in, get exactly the same query id, insert it and get committed. Guess what happens when your insert tries to commit? First in wins; the second gets a duplicate key error. Don't ask me how I know this :)
If you really need a text query id, you might try using a computed field, something like this:
create table dbo.tblCatQuery
(
query_id int not null identity(1,1) primary key clustered ,
query_id_text as right('0000000000'+convert(varchar,id),10) ,
user_id varchar(20) not null ,
cat_id varchar(20) not null ,
query varchar(100) not null ,
query_title varchar(50) not null ,
)
Then your stored procedure looks like this:
create procedure dbo.spInsertCatQuery
#Users_Id varchar(20) ,
#Cat_Id varchar(20) ,
#Query varchar(100) ,
#Query_Title varchar(50) ,
#Query_ID varchar(10) output
AS
insert dbo.tblCatQuery ( user_id , cat_id , query_title , query )
VALUES ( #Users_Id , #Cat_Id , #Query_Title , #Query )
-- give the caller back the id of the row just inserted
set #Query_ID = ##SCOPE_IDENTITY
-- for redundancy, hand it back as the SP's return code, too
return #Query_ID
GO
It sounds like your application needs a string for the ID field, yet in the database you want it ID to behave as an auto-incrementing integer field.
Consider using an integer in the database, and when you retrieve the value and need to use it as as string, at that point convert the value to a string, either in your query or in your application.
This will solve your problem.
You must seriously review your design. I shall suggest something like this.
CREATE TABLE tblCatQuery(QueryId int NOT NULL PRIMARY KEY IDENTITY(1, 1),
UserId int NOT NULL REFERENCES tblUsers(UserId),
CatId int NOT NULL REFERENCES tblCat(CatId),
Query varchar(100), Query_Title varchar(50))
CREATE TABLE tblUsers(UserId int NOT NULL PRIMARY KEY IDENTITY(1, 1), ....
CREATE TABLE tblCat(CatId int NOT NULL PRIMARY KEY IDENTITY(1, 1), ....
CREATEPROCEDURE dbo.spInsertCatQuery
(
#Users_Id int,
#Cat_Id int,
#Query varchar(100),
#Query_Title varchar(50)
)
AS
BEGIN
INSERT INTO tblCatQuery(Users_Id, Cat_Id, Query_Title, Query)
VALUES( Users_Id, Cat_Id, Query_Title, Query)
END