Orace: Default column value based on a filter - sql

Hi a developer asked to add a column on a table which will have a default value of 'N', however if the entry has an id = 3 then the default value of this column should be 'Y', is there anyway I can achieve this in oracle?

I agree with the commenters who have mentioned that this is not a good database design. That said, making compromises with database design is not unusual in real-life situations.
I am not sure that a virtual column is what is wanted. The OP asked for a way to have a default; a virtual column works differently than a default constraint (e.g., with a default constraint we can insert a value other than the default into the column. The best route to take might be to use a trigger to set the "default" value:
CREATE OR REPLACE TRIGGER mytrigger
BEFORE INSERT ON mytable FOR EACH ROW
WHEN (new.mycolumn IS NULL)
BEGIN
SELECT DECODE(id, 3, 'Y', 'N') INTO :new.mycolumn FROM dual;
END;
/
A trigger will also work whether you're using Oracle 10g or 11g (both of which you've tagged).
Hope this helps.

11g approach
From Oracle 11g and up, you could do this in one step using VIRTUAL columns.
Test case
SQL> CREATE TABLE tab_default (
2 ID NUMBER,
3 flag varchar2(1) GENERATED ALWAYS AS (decode(id, 3, 'Y', 'N')) VIRTUAL
4 );
Table created.
SQL>
SQL> INSERT INTO tab_default (ID) VALUES (1);
1 row created.
SQL> INSERT INTO tab_default (ID) VALUES (3);
1 row created.
SQL> INSERT INTO tab_default (ID) VALUES (10);
1 row created.
SQL> SELECT * FROM tab_default;
ID F
---------- -
1 N
3 Y
10 N
SQL>
So, the DECODE function in the VIRTUAL column declaration handles the requirement for you.
10g approach
You could fulfill the requirement using -
DEFAULT value
AFTER INSERT TRIGGER whenever id = 3
Create the table to have DEFAULT value as 'N'. Let the trigger fire only when a new row is inserted with value in id column = 3, such that the trigger updates the value to 'Y'. Else for all other cases the default value would be 'N'.

After adding new column to your table you can insert value in column using below query :
update table_name set column_name = ( case when id = 3 then 'Y' else 'N' end );
At the of inserting new records you may use below approach :
1) Decide column at the time of creating insert query, you can add logic for that when you create query.
2) Create a trigger in database that should update you column value after inserting any new row to table.

This is a very poor database design. It doesn't respect relational database normal form.
I suggest keeping that table as it is and create a new view on the table with an extra column which is calculated using DECODE or CASE WHEN ...

Create a new table with the additional value column:
create table table1 as
select u.*,
case when id=3 then 'Y' ELSE 'N'
END value
from table2 u

Related

is there a way to automatically update a column value when a new record is inserted into an oracle sql table

we have an api operation that enters a row into our table with a report_type=5, is there some sort of operation i can apply to the table to make it so whenever a record gets entered or pulled
with a report_id=12 it returns the report_type as 4?
As commented, trigger would do. Here's an example.
Sample table:
SQL> create table test
2 (report_id number,
3 report_type number);
Table created.
Trigger:
SQL> create or replace trigger trg_bi_test
2 before insert on test
3 for each row
4 when (new.report_id = 12)
5 begin
6 :new.report_type := 4;
7 end;
8 /
Trigger created.
Testing:
SQL> insert into test (report_id, report_type) values (1, 13);
1 row created.
SQL> insert into test (report_id, report_type) values (12, 99);
1 row created.
SQL> select * from test;
REPORT_ID REPORT_TYPE
---------- -----------
1 13
12 4 --> I inserted report_type = 99, but trigger modified it to 4
--> because report_id = 12
SQL>
It's not clear which value you want to be STORED in the database: 12 (as entered), or 4 (as translated).
A trigger as proposed by another commenter would certainly be able to translate the value on insert or update.
If you want the original value to be stored, you'd need to set up a different column, that is derived based on the original one. An example swiped from an Oracle publication:
create table PERSON (
(employee_id integer,
employee_id_disp computed by
SUBSTRING (CAST(employee_id + 100000 as VARCHAR (6)) from 2)
);
In your case, you might do something like
create table MYTABLE (
somekey varchar(20) not null,
entered_office int,
display_office computed by decode(entered_office,12,4,entered_office)
);
Then, anything that needs to display the office number would need to use the display_office field, not the entered one. Any tool that does an insert into the table would also need to insert the entered_office field, as display_office is not updateable.

In an INSERT, how to make the null values to be zero or null?

I have a table of 10 columns, and my INSERT statement only refers to specific columns in the table.
INSERT INTO SCHEMA.TABLE
(COL_1, COL_2)
VALUES
(VAL_1, VAL_2);
... or...
INSERT INTO SCHEMA.TABLE
(COL_1, COL_2)
SELECT VAL_1, VAL_2 FROM SCHEMA_2.TABLE_2;
However, when I execute it, the other columns are inserted always with a null value, instead of having the corresponding one depending on the column type (i.e. number). This is, if I have a numeric column, I should see a zero.
How can I do that insert properly?
*** Please consider I have no DDL privileges & I'm trying to insert into an existing table.
The easiest approach would probably to give your columns default values:
ALTER TABLE schema.table MODIFY (COL_1 NUMBER DEFAULT 0);
DEFAULT value it is; however, note that you have to pay attention to what you do because column might not get its default value. Here's an example:
SQL> create table test
2 (id number primary key,
3 name varchar2(10),
4 address varchar2(20) default 'Unknown', --> columns with default
5 num_val number default 0 --> values
6 );
Table created.
If you're inserting values without specifying column(s) that are supposed to get default values, everything will be as you'd want it to be:
SQL> insert into test (id, name) values (1, 'Little');
1 row created.
SQL> select * from test;
ID NAME ADDRESS NUM_VAL
---------- ---------- -------------------- ----------
1 Little Unknown 0
See? Both ADDRESS and NUM_VAL got default values.
However, if you mention those columns in INSERT statement, although setting them to NULL, they won't be set to their default values but NULL:
SQL> insert into test (id, name, address, num_val)
2 values (2, 'Foot', null, null);
1 row created.
SQL> select * from test;
ID NAME ADDRESS NUM_VAL
---------- ---------- -------------------- ----------
1 Little Unknown 0
2 Foot
As you can see, row with ID = 2 didn't get default values in ADDRESS and NUM_VAL columns.
Therefore, pay attention to what you do.
USE DEFAULT AS 0 for that column
or
use NVL( column_name, 0 ) --as per oracle syntax
--this would mean whenever theres null found for
-- that column set it to 0 (will work on insert)
or
Update column set column=0 where column IS NULL
--(will work after insert as the name suggests update)
Although frankly I don't recommend doing this, you can use a trigger to accomplish your goal:
CREATE OR REPLACE TRIGGER SCHEMA.TABLE_BI
BEFORE INSERT ON SCHEMA.TABLE
FOR EACH ROW
BEGIN
:NEW.COL_1 := COALESCE(:NEW.COL_1, 0); -- NUMBER column
:NEW.COL_2 := COALESCE(:NEW.COL_2, ' '); -- VARCHAR column
:NEW.COL_3 := COALESCE(:NEW.COL_3, SYSDATE); -- DATE column
END SCHEMA.TABLE_BI;
However, creating a trigger may require privileges you don't have.
To answer the question: that needs to be defined at table creation, determining default values. In this case, I wasn't able to do that because the table definition indicated NULL, even in the case of numbers.
Thanks anyway.

Oracle trigger to prevent inserting the new row upon a condition

I've found few questions addressing the same question but without a better solution.
I need to create an Oracle trigger which will prevent new inserts upon a condition, but silently (without raising an error).
Ex : I need to stop inserting rows with bar='FOO' only. (I can't edit the constraints of the table, can't access the procedure which really does the insertion etc so the trigger is the only option)
Solutions so far confirms that it isn't possible. One promising suggestion was to create an intermediate table, insert key values to that when bar='FOO' and then delete those records from original table once insertion is done, which is not correct I guess.
Any answer will be highly appreciated.
Apparently, it is not possible to use a trigger to stop inserts without raising an exception.
However, if you have access to the schema (and asking about a trigger this is probably ok), you could think about replacing the table with a view and an instead of trigger.
As a minimal mock up for your current table. myrole is just a stand in for the privileges granted on the table:
CREATE ROLE myrole;
CREATE TABLE mytable (
bar VARCHAR2(30)
);
GRANT ALL ON mytable TO myrole;
Now you rename the table and make sure nobody can directly access it anymore, and replace it with a view. This view can be protected by a instead of trigger:
REVOKE ALL ON mytable FROM myrole;
RENAME mytable TO myrealtable;
CREATE OR REPLACE VIEW mytable AS SELECT * FROM myrealtable;
GRANT ALL ON mytable TO myrole;
CREATE OR REPLACE TRIGGER myioftrigger
INSTEAD OF INSERT ON mytable
FOR EACH ROW
BEGIN
IF :new.bar = 'FOO' THEN
NULL;
ELSE
INSERT INTO myrealtable(bar) VALUES (:new.bar);
END IF;
END;
/
So, if somebody is inserting a normal row into the fake view, the data gets inserted into your real table:
INSERT INTO mytable(bar) VALUES('OK');
1 row inserted.
SELECT * FROM mytable;
OK
But if somebody is inserting the magic value 'FOO', the trigger silently swallows it and nothing gets changed in the real table:
INSERT INTO mytable(bar) VALUES('FOO');
1 row inserted.
SELECT * FROM mytable;
OK
Caution: If you want to protect your table from UPDATEs as well, you'd have to add a second trigger for the updates.
One way would be to hide the row. From 12c this is reasonably easy:
create table demo
( id integer primary key
, bar varchar2(10) );
-- This adds a hidden column and registers the table for in-database archiving:
alter table demo row archival;
-- Set the hidden column to '1' when BAR='FOO', else '0':
create or replace trigger demo_hide_foo_trg
before insert or update on demo
for each row
begin
if :new.bar = 'FOO' then
:new.ora_archive_state := '1';
else
:new.ora_archive_state := '0';
end if;
end demo_hide_foo_trg;
/
-- Enable in-database archiving for the session
-- (probably you could set this in a log-on trigger):
alter session set row archival visibility = active;
insert into demo (id, bar) values (1, 'ABC');
insert into demo (id, bar) values (2, 'FOO');
insert into demo (id, bar) values (3, 'XYZ');
commit;
select * from demo;
ID BAR
-------- --------
1 ABC
3 XYZ
-- If you want to see all rows (e.g. to delete hidden rows):
alter session set row archival visibility = all;
In earlier versions of Oracle, you could achieve the same thing using a security policy.
Another way might be to add a 'required' flag which defaults to 'Y' and set it to to 'N' in a trigger when bar = 'FOO', and (assuming you can't change the application to use a view etc) have a second trigger delete all such rows (or perhaps better, move them to an archive table).
create table demo
( id integer primary key
, bar varchar2(10) );
alter table demo add required_yn varchar2(1) default on null 'Y';
create or replace trigger demo_set_not_required_trg
before insert or update on demo
for each row
begin
if :new.bar = 'FOO' then
:new.required_yn := 'N';
end if;
end demo_hide_foo_trg;
/
create or replace trigger demo_delete_not_required_trg
after insert or update on demo
begin
delete demo where required_yn = 'N';
end demo_delete_not_required_trg;
/

Values of the inserted row in a Trigger Oracle

I want a trigger that updates the value of a column, but I just want to update a small set of rows that depends of the values of the inserted row.
My trigger is:
CREATE OR REPLACE TRIGGER example
AFTER INSERT ON table1
FOR EACH ROW
BEGIN
UPDATE table1 t
SET column2 = 3
WHERE t.column1 = :new.column1;
END;
/
But as I using FOR EACH ROW I have a problem when I try it, I get the mutating table runtime error.
Other option is not to set the FOR EACH ROW, but if I do this, I dont know the inserted "column1" for comparing (or I dont know how to known it).
What can I do for UPDATING a set of rows that depends of the last inserted row?
I am using Oracle 9.
You should avoid the DML statements on the same table as defined in a trigger. Use before DML to change values of the current table.
create or replace trigger example
before insert on table1
for each row
begin
:new.column2 := 3;
end;
/
You can modify the same table with pragma autonomous_transaction:
create or replace trigger example
after insert on table1 for each row
declare
procedure setValues(key number) is
pragma autonomous_transaction;
begin
update table1 t
set column2 = 3
where t.column1 = key
;
end setValues;
begin
setValues(:new.column1);
end;
/
But I suggest you follow #GordonLinoff answere to your question - it's a bad idea to modify the same table in the trigger body.
See also here
If you need to update multiple rows in table1 when you are updating one row, then you would seem to have a problem with the data model.
This need suggests that you need a separate table with one row per column1. You can then fetch the value in that table using join. The trigger will then be updating another table, so there will be no mutation problem.
`create table A
(
a INTEGER,
b CHAR(10)
);
create table B
(
b CHAR (10),
d INTEGER
);
create trigger trig1
AFTER INSERT ON A
REFERENCING NEW AS newROW
FOR EACH ROW
when(newROW.a<=10)
BEGIN
INSERT into B values(:newROW.b,:newROW.a);
END trig1;
insert into A values(11,'Gananjay');
insert into A values(5,'Hritik');
select * from A;
select * from B;`

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.