Why is my codes having error 00049 Bad Bind Variable - sql

create or replace trigger addDate
before insert or update on Employee
for each row
begin
for EmployeeRec in
(
select * from employee
)
loop
if (EmployeeRec.DateLimit > sysdate) then
raise_application_error (-20001, 'You are only allowed to insert once a day, please try again
tomorrow');
end if;
end loop EmployeeRec;
:NEW.DateLimit := sysdate + 1;
end;
/
show errors;
Need to be able to insert a record for a day only. Any other records must wait till the next day
There is PLS 00049 ERROR at New.DateLimit

Maybe column name is not DateLimit, but Date_Limit.

If I am following you correctly, you want to allow just one record per day, as defined by column date_limit.
You don't need a trigger for this. One option uses a computed column and a unique constraint.
create table employee (
employee_id int primary key,
name varchar2(100),
date_limit date default sysdate,
day_limit date generated always as (trunc(date_limit)) unique
);
day_limit is a computed column, that contains the date portion of date_limit (with the time portion removed). A unique constraint is set on this column, so two rows cannot have date_limits that belong to the same day.
Demo on DB Fiddle

Related

Creating a trigger to automatically calculate the value of total tour time

I am facing some issue while creating a trigger that can automatically calculate the total time it takes for a tour to finish, below is the text question
The event holder has decided to record the shortest tour time for each event type for
all future exhibitions. Add two new attributes in the EVENTTYPE table named
eventype_record and eventtype_recordholder to store the shortest tour time for
each event type and the participant (participant number) who holds the record. You
may assume that only a single participant will hold each record and that the record
holder will only be replaced by a new single participant if their tour time is less
than the current eventtype_record. Calculate the tour time attribute added in previous task when the tour finish time is updated (ie. the participant finished the tour)
What I currently have is
CREATE OR REPLACE TRIGGER tour_time_updater
AFTER UPDATE OF tour_finishtime ON entry
FOR EACH ROW
BEGIN
UPDATE entry
SET tour_time = to_char(((:new.tour_finishtime - :old.tour_starttime) * 24 * 60), '9999.99');
END;
/
But after I try to insert a fake participant in my entry table with no finish time with this code
INSERT INTO entry (
event_id,
entry_no,
tour_starttime,
tour_finishtime,
part_no,
group_id,
tour_time
) VALUES (
9,
6,
to_date('09:05:43', 'HH:MI:SS'),
NULL,
5,
NULL,
NULL
);
and only update its tour_finishtime later
UPDATE entry SET tour_finishtime = to_date('10:05:43', 'HH:MI:SS')
where part_no = 5 and event_id = 9;
it is giving me errors like
**UPDATE entry SET tour_finishtime = to_date('10:05:43', 'HH:MI:SS')
where part_no = 5 and event_id = 9
Error report -
ORA-04091: table XXXXXXXX.ENTRY is mutating, trigger/function may not see it
ORA-06512: at "XXXXXXXX.TOUR_TIME_UPDATER", line 5
ORA-04088: error during execution of trigger 'XXXXXXXX.TOUR_TIME_UPDATER'**
Can anyone help me with that? Thank you in advance!
Don't update the table (literally), but
CREATE OR REPLACE TRIGGER tour_time_updater
BEFORE UPDATE OF tour_finishtime ON entry
FOR EACH ROW
BEGIN
:new.tour_time := to_char(((:new.tour_finishtime - :new.tour_starttime) * 24 * 60), '9999.99');
END;
/
Code you wrote is trying to update the same table whose modification fired the trigger; for the trigger, that table is "mutating" and that's an invalid condition.
You could "fix" it by writing a compound trigger (if your database version supports it) or using a package, but - why bother, if correct way to do it is as suggested in code above?
Don't use a TRIGGER, use a virtual column:
CREATE TABLE entry (
event_id NUMBER PRIMARY KEY,
entry_no NUMBER,
tour_starttime DATE,
tour_finishtime DATE,
part_no NUMBER,
group_id NUMBER
);
ALTER TABLE entry ADD tour_time INTERVAL DAY(0) TO SECOND (0)
GENERATED ALWAYS AS ((tour_finishtime - tour_starttime) DAY(0) TO SECOND(0));
or
ALTER TABLE entry ADD tour_time NUMBER(7,2)
GENERATED ALWAYS AS ((tour_finishtime - tour_starttime) *24 * 60);
If you must use a trigger (don't) then:
CREATE TABLE entry (
event_id NUMBER PRIMARY KEY,
entry_no NUMBER,
tour_starttime DATE,
tour_finishtime DATE,
part_no NUMBER,
group_id NUMBER,
tour_time INTERVAL DAY(0) TO SECOND(0)
);
CREATE OR REPLACE TRIGGER tour_time_updater
BEFORE INSERT OR UPDATE OF tour_finishtime ON entry
FOR EACH ROW
BEGIN
:NEW.tour_time := (:new.tour_finishtime - :new.tour_starttime) DAY(0) TO SECOND(0);
END;
/
db<>fiddle here

Check for overlapped dates on any row of a table Oracle SQL

I have the following schema:
CREATE TABLE EPOCA
(
ID INT
CONSTRAINT PK_EPOCA PRIMARY KEY,
NOME VARCHAR(250),
DATA_INI DATE
CONSTRAINT NN_EPOCA_DATA_INI NOT NULL,
DATA_FIM DATE,
CONSTRAINT CK_EPOCA_DATAS CHECK (DATA_INI < DATA_FIM)
);
And the following trigger, that is supposed to raise an error whenever an EPOCA is inserted into the database and the period between DATA_FIM and DATA_INI is overlapped with other periods of other EPOCAS.
CREATE OR REPLACE TRIGGER TRGEPOCASNAOSOBREPOSTAS
AFTER INSERT OR UPDATE
ON EPOCA
FOR EACH ROW
BEGIN
IF INSERTING THEN
IF :OLD.DATA_INI <= :NEW.DATA_INI AND :OLD.DATA_FIM >= :NEW.DATA_FIM THEN
RAISE_APPLICATION_ERROR(-20021, 'INSERT FAILED BECAUSE SELECTED DATES OVERLAP EXISTENT ONES');
END IF;
ELSIF UPDATING THEN
IF :OLD.DATA_INI <= :NEW.DATA_INI AND :OLD.DATA_FIM >= :NEW.DATA_FIM THEN
RAISE_APPLICATION_ERROR(-20022, 'UPDATE FAILED BECAUSE SELECTED DATES OVERLAP EXISTENT ONES');
END IF;
END IF;
END;
Think of it as: if I define the summer between june 1st and 30th of august I cannot define anyother period of the year with that period of time, nor can I update an existing period with those dates or nothing between june 1st and 30th august.
Right now I can insert any EPOCA with the same date as any other present in the table and I can update the date of any EPOCA with the dates of other EPOCAS and it allows me. What can I change?
I guess you would need a trigger like this one:
CREATE OR REPLACE TRIGGER TRGEPOCASNAOSOBREPOSTAS
AFTER INSERT OR UPDATE
ON EPOCA
c INTEGER;
BEGIN
SELECT COUNT(*)
INTO c
FROM EPOCA e
WHERE EXISTS (
SELECT 1
FROM EPOCA ee
WHERE (e.DATA_INI BETWEEN ee.DATA_INI AND ee.DATA_FIM
OR e.DATA_FIM BETWEEN ee.DATA_INI AND ee.DATA_FIM)
AND ee.ROWID <> e.ROWID);
IF c > 0 THEN
RAISE_APPLICATION_ERROR(-20021, 'INSERT FAILED BECAUSE SELECTED DATES OVERLAP EXISTENT ONES');
END IF;
END;
Note, the FOR EACH ROW clause is not given!
Otherwise the trigger performs only the currently inserted/updated row but does not compare to any existing data.
Consider also cases like this:
In the table you have a period from 1st to 30th of August, then you try to add period for 1st of May to 31th of December. Of course, such situations should be also blocked by the trigger. Thus you need only a statement-level trigger, i.e. a row level trigger which checks only the inserted/updated row is not sufficient.

How to create a SQL Tables that updates when the date changes

Currently I'm creating an app that can essentially create post-it notes. I'm working on making my SQL tables for it. What I want to do is make it so the tables data is searchable by date. Multiple posts may be made on a day obviously. So I'm putting the date into a separate table. What I'm wondering is if it's possible to make it so the date column on the date table is not the current date that it will auto increment the Id and create a new column with the current date
CREATE TABLE IF NOT EXISTS ideas (
id SERIAL PRIMARY KEY,
ideas text,
date_id int );
CREATE TABLE IF NOT EXISTS date (
id SERIAL PRIMARY KEY,
table_date CONVERT(VARCHAR(15), GETDATE(),10));
Is the code I have so far any and all suggestions are welcome!
I would recommend using a TRIGGER procedure. You can trigger a function every time an insert is made on the ideas table. This function can check the dates table and make sure the current date exists in there. It can even set the new id of that date in the date_id column in the ideas table.
For example:
DROP TABLE IF EXISTS ideas;
CREATE TABLE ideas (
id SERIAL PRIMARY KEY,
ideas text,
date_id int
);
-- "date" is a reserved word. try to avoid naming a table "date".
DROP TABLE IF EXISTS dates;
CREATE TABLE dates (
id SERIAL PRIMARY KEY,
table_date DATE DEFAULT NOW() -- i would recommend the DATE type here
);
DROP TRIGGER IF EXISTS insert_date_if_absent ON ideas;
DROP FUNCTION IF EXISTS insert_date_if_absent();
CREATE FUNCTION insert_date_if_absent()
RETURNS TRIGGER
AS $$
DECLARE
today date := now();
new_date_id integer;
BEGIN
IF NOT EXISTS (SELECT * FROM dates WHERE table_date = today) THEN
INSERT INTO dates (table_date) VALUES (today) RETURNING id INTO new_date_id;
ELSE
SELECT id FROM dates WHERE table_date = today INTO new_date_id;
END IF;
IF NEW.date_id IS NULL THEN
NEW.date_id := new_date_id;
END IF;
RETURN NEW;
END
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER insert_date_if_absent
BEFORE INSERT ON ideas
FOR EACH ROW
EXECUTE PROCEDURE insert_date_if_absent();
This will allow you to omit date_id when inserting into ideas. If omitted, it will get automatically set by the trigger to the id of today's date.
INSERT INTO ideas (ideas) VALUES ('sup dudeee');
Some other feedback which I incorporated in my answer:
Do not store dates as a VARCHAR, it's less efficient and more hassle. Use a DATE instead.
Do not name tables after reserved words in Postgres. Rather than date, name it dates.

Limit data input based on count of instances in table

Oracle 12c.
I currently have a table to hold patient visits containing a physician id, patient id, and data/time of visit.
I would like to create a constraint that, upon data entry, checks whether a specific physician has 5 appointments in that given day. If the physician does have 5 appointments, no additional appointment can be added.
Is there any way to do this other than using a stored procedure for entry?
If I were to use a stored procedure (as opposed to a trigger due issues declaring a variable) I receive the following error: Error(4,8): PLS-00103: Encountered the symbol "UPDATE" when expecting one of the following: := . ( # % ; not null range default character
I am unsure if this is because I can't use a BEFORE UPDATE on a procedure. Any thoughts?
CREATE OR REPLACE PROCEDURE doc_apt_limit_5
IS
v_visit_count
BEFORE UPDATE OR INSERT ON aa_patient_visit
FOR EACH ROW
BEGIN
SELECT (COUNT(*)) INTO v_visit_count
FROM aa_patient_visit
WHERE physid = :NEW.physid
GROUP BY physid, visittime;
IF v_visit_count > 4 THEN
RAISE_APPLICATION_ERROR(-20001, 'physician is fully booked on this date');
END IF;
END;
Go with trigger. Probably the best solution in this scenario.
CREATE OR REPLACE TRIGGER doc_apt_limit_5 BEFORE
UPDATE OR
INSERT ON aa_patient_visit FOR EACH ROW
DECLARE v_visit_count PLS_INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_visit_count
FROM aa_patient_visit
WHERE physid = :NEW.physid
GROUP BY physid,
visittime;
IF v_visit_count > 4 THEN
RAISE_APPLICATION_ERROR(-20001, 'physician is fully booked on this date');
END IF;
END;

Oracle - Insert New Row with Auto Incremental ID

I have a workqueue table that has a workid column. The workID column has values that increment automatically. Is there a way I can run a query in the backend to insert a new row and have the workID column increment automatically?
When I try to insert a null, it throws error ORA01400 - Cannot insert null into workid.
insert into WORKQUEUE (facilitycode,workaction,description) values ('J', 'II', 'TESTVALUES')
What I have tried so far - I tried to look at the table details and didn't see any auto-increment. The table script is as follow
"WORKID" NUMBER NOT NULL ENABLE,
Database: Oracle 10g
Screenshot of some existing data.
ANSWER:
I have to thank each and everyone for the help. Today was a great learning experience and without your support, I couldn't have done. Bottom line is, I was trying to insert a row into a table that already has sequences and triggers. All I had to do was find the right sequence, for my question, and call that sequence into my query.
The links you all provided me helped me look these sequences up and find the one that is for this workid column. Thanks to you all, I gave everyone a thumbs up, I am able to tackle another dragon today and help patient care take a step forward!"
This is a simple way to do it without any triggers or sequences:
insert into WORKQUEUE (ID, facilitycode, workaction, description)
values ((select max(ID)+1 from WORKQUEUE), 'J', 'II', 'TESTVALUES')
It worked for me but would not work with an empty table, I guess.
To get an auto increment number you need to use a sequence in Oracle.
(See here and here).
CREATE SEQUENCE my_seq;
SELECT my_seq.NEXTVAL FROM DUAL; -- to get the next value
-- use in a trigger for your table demo
CREATE OR REPLACE TRIGGER demo_increment
BEFORE INSERT ON demo
FOR EACH ROW
BEGIN
SELECT my_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
There is no built-in auto_increment in Oracle.
You need to use sequences and triggers.
Read here how to do it right. (Step-by-step how-to for "Creating auto-increment columns in Oracle")
ELXAN#DB1> create table cedvel(id integer,ad varchar2(15));
Table created.
ELXAN#DB1> alter table cedvel add constraint pk_ad primary key(id);
Table altered.
ELXAN#DB1> create sequence test_seq start with 1 increment by 1;
Sequence created.
ELXAN#DB1> create or replace trigger ad_insert
before insert on cedvel
REFERENCING NEW AS NEW OLD AS OLD
for each row
begin
select test_seq.nextval into :new.id from dual;
end;
/ 2 3 4 5 6 7 8
Trigger created.
ELXAN#DB1> insert into cedvel (ad) values ('nese');
1 row created.
You can use either SEQUENCE or TRIGGER to increment automatically the value of a given column in your database table however the use of TRIGGERS would be more appropriate. See the following documentation of Oracle that contains major clauses used with triggers with suitable examples.
Use the CREATE TRIGGER statement to create and enable a database trigger, which is:
A stored PL/SQL block associated with a table, a schema, or the
database or
An anonymous PL/SQL block or a call to a procedure implemented in
PL/SQL or Java
Oracle Database automatically executes a trigger when specified conditions occur. See.
Following is a simple TRIGGER just as an example for you that inserts the primary key value in a specified table based on the maximum value of that column. You can modify the schema name, table name etc and use it. Just give it a try.
/*Create a database trigger that generates automatically primary key values on the CITY table using the max function.*/
CREATE OR REPLACE TRIGGER PROJECT.PK_MAX_TRIGGER_CITY
BEFORE INSERT ON PROJECT.CITY
FOR EACH ROW
DECLARE
CNT NUMBER;
PKV CITY.CITY_ID%TYPE;
NO NUMBER;
BEGIN
SELECT COUNT(*)INTO CNT FROM CITY;
IF CNT=0 THEN
PKV:='CT0001';
ELSE
SELECT 'CT'||LPAD(MAX(TO_NUMBER(SUBSTR(CITY_ID,3,LENGTH(CITY_ID)))+1),4,'0') INTO PKV
FROM CITY;
END IF;
:NEW.CITY_ID:=PKV;
END;
Would automatically generates values such as CT0001, CT0002, CT0002 and so on and inserts into the given column of the specified table.
SQL trigger for automatic date generation in oracle table:
CREATE OR REPLACE TRIGGER name_of_trigger
BEFORE INSERT
ON table_name
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT sysdate INTO :NEW.column_name FROM dual;
END;
/
the complete know how, i have included a example of the triggers and sequence
create table temasforo(
idtemasforo NUMBER(5) PRIMARY KEY,
autor VARCHAR2(50) NOT NULL,
fecha DATE DEFAULT (sysdate),
asunto LONG );
create sequence temasforo_seq
start with 1
increment by 1
nomaxvalue;
create or replace
trigger temasforo_trigger
before insert on temasforo
referencing OLD as old NEW as new
for each row
begin
:new.idtemasforo:=temasforo_seq.nextval;
end;
reference:
http://thenullpointerexceptionx.blogspot.mx/2013/06/llaves-primarias-auto-incrementales-en.html
For completeness, I'll mention that Oracle 12c does support this feature. Also it's supposedly faster than the triggers approach. For example:
CREATE TABLE foo
(
id NUMBER GENERATED BY DEFAULT AS IDENTITY (
START WITH 1 NOCACHE ORDER ) NOT NULL ,
name VARCHAR2 (50)
)
LOGGING ;
ALTER TABLE foo ADD CONSTRAINT foo_PK PRIMARY KEY ( id ) ;
Best approach: Get the next value from sequence
The nicest approach is getting the NEXTVAL from the SEQUENCE "associated" with the table. Since the sequence is not directly associated to any specific table,
we will need to manually refer the corresponding table from the sequence name convention.
The sequence name used on a table, if follow the sequence naming convention, will mention the table name inside its name. Something likes <table_name>_SEQ. You will immediately recognize it the moment you see it.
First, check within Oracle system if there is any sequence "associated" to the table
SELECT * FROM all_sequences
WHERE SEQUENCE_OWNER = '<schema_name>';
will present something like this
Grab that SEQUENCE_NAME and evaluate the NEXTVAL of it in your INSERT query
INSERT INTO workqueue(id, value) VALUES (workqueue_seq.NEXTVAL, 'A new value...')
Additional tip
In case you're unsure if this sequence is actually associated with the table, just quickly compare the LAST_NUMBER of the sequence (meaning the current value) with the maximum id of
that table. It's expected that the LAST_NUMBER is greater than or equals to the current maximum id value in the table, as long as the gap is not too suspiciously large.
SELECT LAST_NUMBER
FROM all_sequences
WHERE SEQUENCE_OWNER = '<schema_name>' AND SEQUENCE_NAME = 'workqueue_seq';
SELECT MAX(ID)
FROM workqueue;
Reference: Oracle CURRVAL and NEXTVAL
Alternative approach: Get the current max id from the table
The alternative approach is getting the max value from the table, please refer to Zsolt Sky answer in this same question
This is a simple way to do it without any triggers or sequences:
insert into WORKQUEUE (ID, facilitycode, workaction, description)
values ((select count(1)+1 from WORKQUEUE), 'J', 'II', 'TESTVALUES');
Note : here need to use count(1) in place of max(id) column
It perfectly works for an empty table also.