Here are the columns in my table, TB_KELUHAN
"IDKELUHAN" NUMBER(20,0) NOT NULL ENABLE,
"NAMA" VARCHAR2(10 BYTE),
"IDUNIT" NUMBER(10,0),
"TGL_KELUHAN" DATE DEFAULT sysdate,
"KELUHAN" VARCHAR2(200 BYTE),
"STATUS" VARCHAR2(10 BYTE),
"IDPEGAWAI" NUMBER(10,0),
"TGL_SELESAI" DATE DEFAULT sysdate,
"ID_JENISKELUHAN" NUMBER(5,0),
CONSTRAINT "TB_KELUHAN_PK" PRIMARY KEY ("IDKELUHAN")
I want a trigger that will update a row's TGL_SELESAI column to SYSDATE, when a row's STATUS becomes 'SELESAI'. Here is the text of the trigger I've tried:
TRIGGER SELESAI
AFTER UPDATE OF STATUS ON TB_KELUHAN
FOR EACH ROW
DECLARE
TGL_SELESAI DATE;
BEGIN
IF :new.STATUS = 'SELESAI'
THEN
TGL_SELESAI:=SYSDATE;
END IF;
END;
When I change the value of STATUS to "SELESAI", the corresponding TGL_SELESAI did not change. Why?
Your original code was setting a local PL/SQL variable, not the row's column and you were creating an AFTER UPDATE trigger, not a BEFORE UPDATE trigger. Try this:
CREATE OR REPLACE TRIGGER SELESAI
BEFORE UPDATE OF STATUS ON TB_KELUHAN
FOR EACH ROW
BEGIN
IF :new.STATUS = 'SELESAI'
THEN
:new.TGL_SELESAI := SYSDATE;
END IF;
END;
Related
I have a table like this
CREATE TABLE data_audit(
id_col NUMBER(*,0) NOT NULL ENABLE,
col_2 VARCHAR2(10) NOT NULL ENABLE,
col_3 NUMBER(*,0) NOT NULL ENABLE,
col_4 VARCHAR2(10) NOT NULL ENABLE,
col_5 VARCHAR2(10) NOT NULL ENABLE,
created_at TIMESTAMP (3) DEFAULT current_timestamp,
CONSTRAINT DATA_AUDIT_PK PRIMARY KEY (col_2,col_3,col_4)
USING INDEX ENABLE
)
PARTITION BY RANGE(created_at)
(
PARTITION p2022_jan
VALUES LESS THAN (TO_DATE('01-jan-2022')),
PARTITION p2022_feb
VALUES LESS THAN (TO_DATE('01-feb-2022')),
PARTITION p2022_mar
VALUES LESS THAN (TO_DATE('01-mar-2022'))
)
Here - in above example - i need to explicitly mention the name in DDL
I want to create new partition automatically for every month data
How can i achieve it naming it automatically - i am ok with any random name of partition
I am using oracle - Oracle Database 19c Enterprise Edition Release 19.0.0.0
Instead of a RANGE partition I would suggest an INTERVAL partition:
CREATE TABLE DATA_AUDIT (
id_col NUMBER(*,0) NOT NULL ENABLE,
col_2 VARCHAR2(10) NOT NULL ENABLE,
col_3 NUMBER(*,0) NOT NULL ENABLE,
col_4 VARCHAR2(10) NOT NULL ENABLE,
col_5 VARCHAR2(10) NOT NULL ENABLE,
CREATED_AT TIMESTAMP (3) default current_timestamp ,
CONSTRAINT DATA_AUDIT_PK PRIMARY KEY (col_2,col_3,col_4) USING INDEX ENABLE
)
partition by range (CREATED_AT) INTERVAL (INTERVAL '1' MONTH)
(PARTITION p2021_dec VALUES LESS THAN (TIMESTAMP '2022-01-01 00:00:00'));
Then Oracle will create new partition automatically every months.
For renaming you can runs this procedure by daily scheduler job as proposed by Barbaros Özhan.
PROCEDURE RenamePartitions IS
ts TIMESTAMP;
newName VARCHAR2(30);
CURSOR TabPartitions IS
SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'DATA_AUDIT'
ORDER BY 1,2;
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION SET DDL_LOCK_TIMEOUT = 180';
FOR aPart IN TabPartitions LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT ts;
ts := ADD_MONTHS(ts, -1);
newName := 'p'||TO_CHAR(ts,'yyyy_mon', 'NLS_DATE_LANGUAGE = american');
IF aPart.PARTITION_NAME <> newName THEN
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' RENAME PARTITION '||aPart.PARTITION_NAME||' TO '||newName;
END IF;
END LOOP;
END RenamePartitions;
Or see a more generic one: Partition table rename automatically in ORACLE
Assume that the table only has the first partition(p2022_jan) and you'll add new partitions to the table every month, then firstly create a stored procedure such as
CREATE OR REPLACE PROCEDURE Pr_Add_Part_to_Data_Audit is
v_ddl VARCHAR2(32767);
v_date DATE := TO_DATE(TO_CHAR(sysdate,'yyyy-mm-')||'01','yyyy-mm-dd');
BEGIN
v_ddl :='ALTER TABLE data_audit ADD PARTITION p'||TO_CHAR(v_date,'yyyy')||'_'||TO_CHAR(v_date,'mon')||' VALUES LESS THAN ('''||v_date||''')';
EXECUTE IMMEDIATE v_ddl;
END;
/
then, call that from a scheduler at the beginning of the upcoming months such as
DECLARE
v_job_name VARCHAR2(32) := 'jb_add_part_data';
BEGIN
DBMS_SCHEDULER.CREATE_JOB(job_name => v_job_name,
job_type => 'STORED_PROCEDURE',
job_action => 'Pr_Add_Part_to_Data_Audit',
start_date => TO_DATE('01-02-2021 01:00:10',
'DD-MM-YYYY HH24:MI:SS'),
repeat_interval => 'FREQ=MONTHLY; BYHOUR=1;',
auto_drop => false,
comments => 'Adds a new partition every month');
DBMS_SCHEDULER.ENABLE(v_job_name);
END;
/
Here the answergiven by Wernfried Domscheit - is correct
thank you for your Answer - it showed the way of controlling partition name - in my case i didn't need to care about partition name - i just needed to make sure - new partition is created with new name - so i used first half of your comment -
to verify it - i used it with DAY - partitioning
CREATE TABLE DATA_AUDIT (
id_col NUMBER(*,0) NOT NULL ENABLE,
col_2 VARCHAR2(10) NOT NULL ENABLE,
col_3 NUMBER(*,0) NOT NULL ENABLE,
col_4 VARCHAR2(10) NOT NULL ENABLE,
col_5 VARCHAR2(10) NOT NULL ENABLE,
CREATED_AT TIMESTAMP (3) default current_timestamp ,
CONSTRAINT DATA_AUDIT_PK PRIMARY KEY (col_2,col_3,col_4) USING INDEX ENABLE
)
partition by range (CREATED_AT) INTERVAL (INTERVAL '1' DAY)
(PARTITION p2021_dec VALUES LESS THAN (TIMESTAMP '2022-01-01 00:00:00'));
and then it created partitions like
partition name - table name
P2021_DEC - DATA_PURGE_AUDIT
SYS_P8592 - DATA_PURGE_AUDIT
--dept table
create table department(
dept_id number(5) ,
dept_name varchar2(100),
dept_city varchar2(100) ,
dept_country varchar2(100),
CONSTRAINT dept_pk PRIMARY KEY(dept_id)
);
insert into department( dept_id, dept_name, dept_city, dept_country )values(1,'hr','hyderabad','india');
insert into department( dept_id, dept_name, dept_city, dept_country )values(2,'marketing','banglore','india');
insert into department(dept_id, dept_name, dept_city, dept_country)values(3,'sales','dhaka','bangladesh');
create sequence s1
start with 1
increment by 1;
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_age number(3) ,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee_details
FOR EACH ROW
DECLARE
emp_age number;
BEGIN
IF (employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'ravi',45,7333,1);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'sai',74,4451,2);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'chandu',35,9428,3);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'raju',7,25422,2);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'teja',36,7955,1);
select * from employee
You want to use the :NEW record to get the value from the row being inserted (and to use the EMPLOYEE table rather than EMPLOYEE_DETAILS):
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF (:NEW.employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here
However, you should consider storing date of birth rather than age as tomorrow (or definitely next year) the age value will be outdated but storing the date of birth and calculating the age would not.
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_dob DATE,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF :NEW.employee_dob > TRUNC(ADD_MONTHS(SYSDATE, -18*12)) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here
I have table p_it_people with structure as below and trigger created on it.
CREATE TABLE "P_IT_PEOPLE"
( "PERSON_ID" NUMBER NOT NULL ENABLE,
"PERSON_NAME" VARCHAR2(255) NOT NULL ENABLE,
"PERSON_EMAIL" VARCHAR2(255) NOT NULL ENABLE,
"PERSON_ROLE" VARCHAR2(30) NOT NULL ENABLE,
"USERNAME" VARCHAR2(255) NOT NULL ENABLE,
"ASSIGNED_DEPT" NUMBER,
"CREATED_ON" DATE NOT NULL ENABLE,
"CREATED_BY" VARCHAR2(255) NOT NULL ENABLE,
"MODIFIED_ON" DATE,
"MODIFIED_BY" VARCHAR2(255),
"PERSON_PASSWORD" VARCHAR2(100),
"APPROVER" VARCHAR2(50),
CONSTRAINT "P_IT_PEOPLE_PK" PRIMARY KEY ("PERSON_ID")
USING INDEX ENABLE,
CONSTRAINT "P_IT_PEOPLE_NAME_UK" UNIQUE ("PERSON_NAME")
USING INDEX ENABLE,
CONSTRAINT "P_IT_PEOPLE_USERNAME_UK" UNIQUE ("USERNAME")
Existing trigger on the table:
CREATE OR REPLACE EDITIONABLE TRIGGER "P_IT_PEOPLE_BIU"
before insert or update on p_it_people
for each row
begin
if inserting then
if :NEW.PERSON_ID is null then
:NEW.PERSON_ID := it_api.gen_pk;
end if;
:NEW.CREATED_ON := sysdate;
:NEW.CREATED_BY := nvl(v('APP_USER'),USER);
end if;
if updating then
:NEW.MODIFIED_ON := sysdate;
:NEW.MODIFIED_BY := nvl(v('APP_USER'),USER);
end if;
end;
Apart from this i want to create another trigger which would send email whenever new entry is made .
I deployed this trigger:
CREATE OR REPLACE EDITIONABLE TRIGGER "P_IT_ISSUES_AIU_NEW_PASSWORD"
AFTER
insert on P_IT_PEOPLE
for each row
DECLARE
v_person_id number;
v_username varchar2(50);
v_Email varchar2(255);
Begin
select person_id,username,person_email into v_person_id,v_username,v_email from p_it_people where person_id=v_person_id;
APEX_MAIL.SEND(
p_to => v_email,
p_from => v_email,
p_body => 'Your account has been created ' ||chr(10)||
' Username'|| v_username||chr(10)||
' Password'||v_username ,
p_subj => 'New User');
end;
Now when i try inserting row, it is throwing error-p_it_people is mutating. How can i counter this?
You are selecting from the same table as the row-level trigger is firing on - exactly what causes "table is mutating". But in this case you don't even need to. Just use the :NEW pseudo-record like this:
CREATE OR REPLACE EDITIONABLE TRIGGER "P_IT_ISSUES_AIU_NEW_PASSWORD"
AFTER
insert on P_IT_PEOPLE
for each row
Begin
APEX_MAIL.SEND(
p_to => :NEW.person_email,
p_from => :NEW.person_email ,
p_body => 'Your account has been created ' ||chr(10)||
' Username'|| :NEW.username||chr(10)||
' Password'||:NEW.username ,
p_subj => 'New User');
end;
I am working on online platform to make an ERD and to get the PL/SQL code of it but I get the following code (see below) but I am not sure if it is PL/SQL.
I need to verify if this code is PL/SQL or not:
CREATE TABLE "CATEGORY" (
"ID" NUMBER(10) PRIMARY KEY,
"THLEVEL" NUMBER(10) NOT NULL
);
CREATE SEQUENCE "CATEGORY_SEQ" NOCACHE;
CREATE TRIGGER "CATEGORY_BI"
BEFORE INSERT ON "CATEGORY"
FOR EACH ROW
BEGIN
IF :NEW."ID" IS NULL THEN
SELECT "CATEGORY_SEQ".NEXTVAL INTO :NEW."ID" FROM DUAL;
END IF;
END;;
CREATE TABLE "REPORT" (
"ID" NUMBER(10) PRIMARY KEY,
"CLIENT" CLOB NOT NULL,
"VERSION" NUMBER(10) NOT NULL
);
CREATE SEQUENCE "REPORT_SEQ" NOCACHE;
CREATE TRIGGER "REPORT_BI"
BEFORE INSERT ON "REPORT"
FOR EACH ROW
BEGIN
IF :NEW."ID" IS NULL THEN
SELECT "REPORT_SEQ".NEXTVAL INTO :NEW."ID" FROM DUAL;
END IF;
END;;
CREATE TABLE "ASSET" (
"ID" NUMBER(10) PRIMARY KEY,
"REPORT" NUMBER(10) NOT NULL,
"IP" VARCHAR2(1000 CHAR) NOT NULL,
"NAME" VARCHAR2(1000 CHAR)
);
CREATE INDEX "IDX_ASSET__REPORT" ON "ASSET" ("REPORT");
ALTER TABLE "ASSET" ADD CONSTRAINT "FK_ASSET__REPORT" FOREIGN KEY ("REPORT") REFERENCES "REPORT" ("ID");
CREATE SEQUENCE "ASSET_SEQ" NOCACHE;
CREATE TRIGGER "ASSET_BI"
BEFORE INSERT ON "ASSET"
FOR EACH ROW
BEGIN
IF :NEW."ID" IS NULL THEN
SELECT "ASSET_SEQ".NEXTVAL INTO :NEW."ID" FROM DUAL;
END IF;
END;;
CREATE TABLE "SOLUTION" (
"ID" NUMBER(10) PRIMARY KEY,
"IMPLEVEL" VARCHAR2(1000 CHAR) NOT NULL,
"DIFFICULTY" VARCHAR2(1000 CHAR) NOT NULL,
"DESCRIPTION" CLOB NOT NULL
);
CREATE SEQUENCE "SOLUTION_SEQ" NOCACHE;
CREATE TRIGGER "SOLUTION_BI"
BEFORE INSERT ON "SOLUTION"
FOR EACH ROW
BEGIN
IF :NEW."ID" IS NULL THEN
SELECT "SOLUTION_SEQ".NEXTVAL INTO :NEW."ID" FROM DUAL;
END IF;
END;;
CREATE TABLE "VULNERABILITY" (
"ID" VARCHAR2(1000 CHAR) PRIMARY KEY,
"ASSET" NUMBER(10) NOT NULL,
"SOLUTION" NUMBER(10) NOT NULL,
"CATEGORY" NUMBER(10) NOT NULL,
"CVE" VARCHAR2(1000 CHAR),
"DATE" TIMESTAMP NOT NULL,
"LOCATION" CLOB NOT NULL
);
CREATE INDEX "IDX_VULNERABILITY__ASSET" ON "VULNERABILITY" ("ASSET");
CREATE INDEX "IDX_VULNERABILITY__CATEGORY" ON "VULNERABILITY" ("CATEGORY");
CREATE INDEX "IDX_VULNERABILITY__SOLUTION" ON "VULNERABILITY" ("SOLUTION");
ALTER TABLE "VULNERABILITY" ADD CONSTRAINT "FK_VULNERABILITY__ASSET" FOREIGN KEY ("ASSET") REFERENCES "ASSET" ("ID");
ALTER TABLE "VULNERABILITY" ADD CONSTRAINT "FK_VULNERABILITY__CATEGORY" FOREIGN KEY ("CATEGORY") REFERENCES "CATEGORY" ("ID");
ALTER TABLE "VULNERABILITY" ADD CONSTRAINT "FK_VULNERABILITY__SOLUTION" FOREIGN KEY ("SOLUTION") REFERENCES "SOLUTION" ("ID");
CREATE TABLE "EXPLOIT" (
"ID" NUMBER(10) PRIMARY KEY,
"VULNERABILITY" VARCHAR2(1000 CHAR) NOT NULL,
"NAME" VARCHAR2(1000 CHAR) NOT NULL,
"TYPE" CLOB NOT NULL,
"DESCRIPTION" CLOB NOT NULL
);
CREATE INDEX "IDX_EXPLOIT__VULNERABILITY" ON "EXPLOIT" ("VULNERABILITY");
ALTER TABLE "EXPLOIT" ADD CONSTRAINT "FK_EXPLOIT__VULNERABILITY" FOREIGN KEY ("VULNERABILITY") REFERENCES "VULNERABILITY" ("ID");
CREATE SEQUENCE "EXPLOIT_SEQ" NOCACHE;
CREATE TRIGGER "EXPLOIT_BI"
BEFORE INSERT ON "EXPLOIT"
FOR EACH ROW
BEGIN
IF :NEW."ID" IS NULL THEN
SELECT "EXPLOIT_SEQ".NEXTVAL INTO :NEW."ID" FROM DUAL;
END IF;
END;
So is this PL/SQL or is somethin else from Oracle? How this will sound in PL/SQL?
Well that is PL/SQL and it will work in Oracle if you remove the double semi-colon you have on create trigger.
https://dba.stackexchange.com/questions/1121/how-to-differentiate-between-sql-and-pl-sql
That is NOT PL/SQL. That is DDL (Data Definition Language). Specifically it is Oracle DDL.
These are DDL statements in which we can create, alter tables ,PL/SQL is a Procedural language SQL in which we use DML Statements for any Process.
These are a set of DDL commands and not PL/SQL as such. However if you incorporate these commands in a nice stored procedure or package, it might be termed as PL/SQL (procedural part of it) but not currently in its current shape.
I keep getting the error below and I can't figure out why. I've included the tables in the code and then also the code portion that is presenting the error.
Tables in the code:
CREATE TABLE Employee (
emp_id number(8) primary key,
f_name VarChar2(20),
l_name VarChar2(20),
address VarChar2(40),
city VarChar2(20),
state Char(2),
zip Number(5),
phone_Number Number(10),
email_address VarChar2(30),
dept_id number(8) references department(Dept_ID),
office_location VarChar2(30)
);
CREATE TABLE audit_detail (
audit_detail_id number(8) primary key,
field varchar2(30),
old_value varchar2(30),
new_value varchar2(30)
);
CREATE TABLE audit_trail (
audit_trail_id number(8) primary key,
user_id number(8)references user_id_table(user_id),
table_name VarChar2(25),
process VarChar2(25),
emp_id number(8)references employee(emp_id),
timestamp Date
);
Create table Department (
dept_id number(8) primary key,
dept_name VarChar2(20) not null,
dept_start_date date
);
create table user_id_table(
user_id number(8) primary key,
emp_id number(8) references Employee(emp_id)
);
CREATE SEQUENCE audit_seq;
This is the portion that is presenting the error, Specifically the elsif Deleting portion
CREATE OR REPLACE TRIGGER audit_employee
AFTER INSERT OR UPDATE OR DELETE ON Employee
FOR EACH ROW
DECLARE
timestamp DATE;
session_user number(8);
BEGIN
timestamp := SYSDATE;
session_user := USERENV('SESSION_USER');
IF INSERTING THEN
INSERT INTO audit_trail VALUES
(Audit_seq.NEXTVAL, session_user, 'Employee', 'INSERT', :new.emp_id, timestamp);
ElSIF DELETING THEN
INSERT INTO audit_trail Values
(Audit_seq.NEXTVAL, session_user, 'Employee', 'DELETE', :old.emp_id, timestamp);
Else
INSERT INTO audit_trail
Values(Audit_seq.NEXTVAL, session_user, 'Employee', 'UPDATE', :old.emp_id);
IF UPDATING ('f_name') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'f_name', :old.f_name, :new.f_name);
ELSIF UPDATING('l_name') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'l_name', :old.l_name, :new.l_name);
ELSIF UPDATING('address') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'address', :old.address, :new.address);
ELSIF UPDATING('city') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'city', :old.city, :new.city);
ELSIF UPDATING('state') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'state', :old.state, :new.state);
ELSIF UPDATING('zip') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'zip', :old.zip, :new.zip);
ELSIF UPDATING('phone_Number') THEN
INSERT INTO Audit_detail VALUES
(Audit_seq.CURRVAL, 'phone_Number', :old.phone_number, :new.phone_number);
ELSIF UPDATING('email_address') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'email_address', :old.email_address, :new.email_address);
ELSIF UPDATING('dept_id') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'dept_id', :old.dept_id, :new.dept_id);
ELSIF UPDATING('office_location') THEN
INSERT INTO audit_detail VALUES
(Audit_seq.CURRVAL, 'office_location', :old.office_location, :new.office_location);
End IF;
End IF;
End;
/
Your else statement after the deleting has 5 columns whereas audit_trail has 6.
ElSIF DELETING THEN
INSERT INTO audit_trail
Values ( Audit_seq.NEXTVAL, session_user, 'Employee'
, 'DELETE', :old.emp_id, timestamp);
Else
INSERT INTO audit_trail
Values (Audit_seq.NEXTVAL, session_user, 'Employee'
, 'UPDATE', :old.emp_id );
You're also referencing columns in lower case not upper, UPDATING ('L_NAME'). I'm not sure whether that would cause a problem.
You're doing a lot of implicit number to character conversion which will eventually catch you out and table_name in audit_trail should by 30 characters not 25. Why risk it?