Making columns optional in Oracle - sql

I'm having a bit of an Oracle dilemma.
I have a table consisting of 5 columns: one PK, two FKs, one INT value and one Date.
I also have a sequence set up for my PK.
I set up a trigger that replaces the PK by an auto-incremented value and the Date by the current date so that you can enter the values (null, FK, FK, INT, null) but I was wondering if there was a way to modify my trigger so that I can enter simply (FK, FK, INT)? As it stands (obviously) if I enter only 3 values I get the ORA-00947: not enough values error.
CREATE or REPLACE trigger TRIG_new_product
before insert on product
for each row
BEGIN
SELECT sq_product.nextval, sysdate
into :new.productID, :new.productDate
FROM dual;
END TRIG_new_product;

If you don't want to supply values for a column, don't list it in the insert statement:
insert into product
(fk_column, fk_column, int_column)
values
(42, 24, 4224);
The error message "not enough" values has nothing to do with your trigger and probably stems from the fact that you didn't specify the columns in your insert statement. In that case you have to supply a value for each column. You probably did something like this:
insert into product -- no columns specified therefore all are required
values
(42, 24, 4224);
Of course leaving out columns during insert will only work if they are defined as nullable.
Btw: your trigger could be written a bit simpler:
CREATE or REPLACE trigger TRIG_new_product
before insert on product
for each row
BEGIN
:new.productID := sq_product.nextval;
:new.productDate := sysdate;
END TRIG_new_product;

Related

Cant figure out this SQL Trigger

so I'm suppose to be making a trigger for my database that will limit how many classes a faculty member can be assigned. If QUALIFIED = 'Y', then they can teach up to three classes. the trouble i'm running into is that I dont know what is wrong with my SQL statement that wont let it be run.
CREATE OR REPLACE trigger "ASSIGN_T1"
BEFORE
INSERT ON "ASSIGN"
FOR EACH ROW
BEGIN
DECLARE
A_COUNT NUMBER;
A_QUALIFY CHAR(2);
SET(SELECT QUALIFY FROM QUALIFY WHERE (FID = :NEW.FID)) AS A_QUALIFY
SET(SELECT COUNT(FID) FROM QUALIFY WHERE (FID = :NEW.FID)) AS A_COUNT
IF (A_QUALIFY = 'Y' AND A_COUNT < 3) THEN
INSERT INTO ASSIGN (FID, CID) VALUES (:NEW.FID, :NEW.CID);
END IF;
END;
The two errors i'm getting are
line 8, position 8 PLS-00103: Encountered the symbol "(" when expecting one of the following: constant exception table long double ref char time timestamp
line 8, position 61 PLS-00103: Encountered the symbol "AS" when expecting one of the following: set
The 1st problem here is that the BEGIN needs to move down below the DECLARE and the variable declarations.
The 2nd problem is the way you're attempting to set those variables. Get rid of those SETs and AS's. In PL/SQL, one valid way to set a variable with the result of a SQL statement is with a SELECT INTO. Like so....
SELECT QUALIFY
INTO A_QUALIFY
FROM QUALIFY
WHERE FID = :NEW.FID;
...and you can do the same for A_COUNT
I won't guarantee everything will work right after you do that, but that's the bare minimum to fix here.
Also, even if doing the above works, watch out for SELECT INTO because you'll get a "no data found" error if there's ever a scenario where you don't already have a FID = :NEW.FID being passed in OR an "exact fetch returns more than requested number of rows" error if you have more than 1 existing record with that FID in your table. You then either have to handle those exceptions, or use a different method of assigning values to your variable (such as declaring the SQL in a cursor and then OPEN cursor, FETCH cursor INTO variable, CLOSE cursor.)
Lastly, I think there may be a problem in your logic. You're asking for the value in QUALIFY for a FID, but then you're asking for the number of records that have that FID. That implies that the FID isn't the primary key on your table, which means there could be different records with the same FID but different values in the QUALIFY field. So if you're going to be using that variable in your logic later on, then that may be a problem, since the same FID can have one record with QUALIFY = 'Y', and another record with QUALIFY = 'N'
You have used the BEGIN after DECLARE part. And am not sure why you are using SET .. AS. We can combine both selects into one and use it in IF condition.
I don't think you can trigger on the same table and do insert at the same time. You will end up with ORA-04088 error.
Instead, you can restrict the insertion by throwing an error.
(my option would be a Foreign Key Constraint over the ASSIGN table)
--Creating Tables
create table ASSIGN (FID number, CID number);
create table QUALIFY (FID number, QUALIFY char);
-- Loading sample data
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (2, 'Y');
insert into QUALIFY values (2, 'Y');
insert into QUALIFY values (3, 'N');
insert into QUALIFY values (4, 'Y');
CREATE OR REPLACE trigger "ASSIGN_T1"
BEFORE
INSERT ON "ASSIGNEE" --< change table name to yours
FOR EACH ROW
DECLARE
A_COUNT NUMBER;
BEGIN
SELECT COUNT(QUALIFY) into A_COUNT FROM QUALIFY WHERE QUALIFY='Y' AND FID = :NEW.FID;
-- If they are qualified and already has 3 classes. They are not allowed/record is not inserted.
IF A_COUNT = 3 THEN
Raise_Application_Error (-20343, 'FID is not Qualified or already has 3 Classes.');
END IF;
END;
/
Test by inserting data into the ASSIGNEE table
-- FID 1 already assigned to 3 classes, should not be allowed any more.
insert into ASSIGNEE values (1,3);
-- See error below
Error report -
ORA-20343: FID is not Qualified or already has 3 Classes.
-- FID 2 has only 2 classes, so allowed to insert.
insert into ASSIGNEE values (2,3);
1 row inserted.
One way to accomplish your goal is to do something like this:
CREATE OR REPLACE TRIGGER ASSIGN_T1
BEFORE INSERT ON ASSIGN
FOR EACH ROW
BEGIN
FOR aRow IN (SELECT q.QUALIFY,
COUNT(*) OVER (PARTITION BY q.FID) AS FID_COUNT
FROM QUALIFY q
WHERE q.FID = :NEW.FID)
LOOP
IF aRow.QUALIFY = 'Y' AND aRow.FID_COUNT < 3 THEN
INSERT INTO ASSIGN (FID, CID) VALUES (:NEW.FID, :NEW.CID);
END IF;
END LOOP;
END ASSIGN_T1;

how to make sure that trigger generated value is being returned?

I have this INSERT query, which purpose is to insert the one row in my database.
Similarly I also have a INSERT query which insert multiple rows.
One of the columns in the table is generated after the values has been generated, since it combines a set of column values to construct a name. The name itself it generated from a Trigger, and its triggered After insert, since the column values has to exist for me to generate the name.
my problem now is when I insert one row or multiple rows, I want to know the the generated column value, but when I return it, it states its null?
#$"INSERT INTO registration_table (id, ...,)
VALUES (1,...,)
RETURNING row_id, name;";
which in return gives me an id the one I inserted, but the not actual name but instead I get null..
The trigger is pretty straight forward
CREATE TRIGGER name_insert_trigger
AFTER INSERT
ON registration_table
REFERENCING NEW TABLE AS new_inserts
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE registration_entry_name();
CREATE OR REPLACE FUNCTION registration_entry_name()
RETURNS trigger AS
$$
DECLARE
BEGIN
UPDATE registration_table
SET name = |Pattern| -- This one being the actual name generated..
FROM new_inserts
WHERE new_inserts.row_id = registration_table.row_id;
RETURN null;
END;
$$
LANGUAGE plpgsql;
but the insert query above does not return the name?
why not?
You actually need a BEFORE trigger, your data values will be there. The designation of Before and After very often causes misconceptions especially of row level triggers. The terms do not indicate their timing in relation to the DML. I have found it useful to think of them as "before final data values are set" and "after final data values are set" but both run before the invoking DML completes (for now we will bypass deferred triggers). Lets look at inserts. When the before row trigger fires the NEW row contains the values at that point for every column in the row, any value not specified in the statement will be null or contain the specified default if any. Before row triggers can can change any column. After row triggers cannot change columns, if present any change is ignored.
Your description and code imply you need to combine a couple columns to generate the content of another. Since you did not specify exactly that I will build an example and demo.
create table users ( usr_id integer generated always as identity
, lname text not null
, fname text not null
, full_name text not null
) ;
create or replace
function users_bir()
returns trigger
language plpgsql
as $$
begin
if new.full_name is null
then
new.full_name = trim(new.fname) || ' ' || trim(new.lname);
end if;
return new;
end;
$$;
create trigger users_bir_trg
before insert on users
for each row
execute procedure users_bir();
insert into users(fname, lname)
values ( 'George', 'Henery')
, ( 'Samatha', 'van Horm');
insert into users(fname, lname, full_name)
values ( 'Wacky', 'Warriors','Not so tough guys');
This setup allows the full_name to be specified or generated. If only generation is desired remove the IF leaving only the assignment statement. Even better if you have Postgres 12 or higher just define the the column as a generated column. This is also in the demo.

Oracle trigger insert other table

I have 2 tables which are my_school and my_class
And "my_school" table has 'info_id' column and also "my_class" table has 'info_id' then I want to get a query that automatically generate "info_id" then I found solution..
Here are my working TRIGGER on "my_school" table...
CREATE OR REPLACE TRIGGER info_id
before insert on my_direction
for each row
begin
if :NEW.WAY_ID is null then
:NEW.WAY_ID := example_id_seq.nextval;
end if;
end;
It works and it's generating auto id when inserting value.
But now how to get this trigger do it on "my_class" table when users insert value on my_school's table then take id with "my_class" table's "info_id" column same time?
You can create trigger on my_school table to update info_id similar to that you have explained and while inserting records, use returning into clause.
Declare a variable to store returned value, for example
v_info_id number(9);
And use it in returning into clause
insert into my_school(column.......list)
values (values........list)
RETURNING info_id INTO v_info_id;
Use v_info_id in your program to insert value of info_id into another tables.

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.

INSERT ALL INTO and Sequence.nextval for a Surrogate Key

I'm trying to insert 40 rows using an INSERT ALL INTO and I'm not certain on how to insert the surrogate key. Here's what I have
BEGIN
INSERT ALL
INTO question(question_id)
VALUES (question_seq.nextval)
END
Now if I add another INTO VALUES then I get a unique constraint violation.
BEGIN
INSERT ALL
INTO question(question_id)
VALUES (question_seq.nextval)
INTO question(question_id)
VALUES (question_seq.nextval)
END
How can I update the sequences nextval value for each INTO VALUES so that I can avoid the unique constraint violation? I assumed that nextval would automatically update itself.
UPDATE: I don't know if this is the best way to handle this but here's the solution I came up with:
first I created a function that returns a value
then I called that function in the id field of the VALUES clause
create or replace
FUNCTION GET_QUESTION_ID RETURN NUMBER AS
num NUMBER;
BEGIN
SELECT UHCL_QUESTIONS_SEQ.nextval
INTO num
FROM dual;
return num;
END GET_QUESTION_ID;
INSERT ALL
INTO question(question_id)
VALUES (GET_QUESTION_ID())
INTO question(question_id)
VALUES (GET_QUESTION_ID())
Being from a SQL Server background, I've always created a trigger on the table to basically emulate IDENTITY functionality. Once the trigger is on, the SK is automatically generated from the sequence just like identity and you don't have to worry about it.
You can use something like this:
insert into question(question_id)
select question_seq.nextval from
(
select level from dual connect by level <= 40
);
Although it's not a very convenient format, especially if there are other columns you want to add. You'd probably need to create another UNION ALL query, and join it by the LEVEL or ROWNUM.
My first thought was to do something like this:
insert into question(question_id)
select question_seq.nextval value from dual
union all
select question_seq.nextval from dual;
But it generates ORA-02287: sequence number not allowed here, due to the restrictions on sequence values.
By the way, are you sure your INSERT ALL works without a subquery? I get the error ORA-00928: missing SELECT keyword, and the diagram from the 11.2 manual implies there must be a subquery:
I don't know if this is the best way to handle this but here's the solution I came up with:
first I created a function that returns a value
then I called that function in the id field of the VALUES clause
create or replace
FUNCTION GET_QUESTION_ID RETURN NUMBER AS
num NUMBER;
BEGIN
SELECT UHCL_QUESTIONS_SEQ.nextval
INTO num
FROM dual;
return num;
END GET_QUESTION_ID;
INSERT ALL
INTO question(question_id)
VALUES (GET_QUESTION_ID())
INTO question(question_id)
VALUES (GET_QUESTION_ID())