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
Related
--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
How to truncate partitions with data older than 2 months?
For an example, i have below table/partition names:
select table_name, partition_name from all_tab_partitions where table_name='TABLENAME';
TABLENAME partitionname1_P30 30
TABLENAME partitionname2_P60 60
TABLENAME partitionname3_P90 90
TABLENAME partitionname4_P120 120
TABLENAME partitionname5_P150 150
TABLENAME partitionname6_P180 180
TABLENAME partitionname7_210 210
TABLENAME partitionname8_P240 240
TABLENAME partitionname9_P270 270
TABLENAME partitionname10_P300 300
TABLENAME partitionname11_P330 330
TABLENAME partitionname12_P360 360
Table is partitioned per month. If we're currently on September, how do I truncate partitions older than 2 months?
Expectation is that only records from Aug-Sep (partitionname8-9) will remain while the rest will be truncated.
CREATE TABLE dbo1.TABLENAME
( PARTITION_ID NUMBER(4, 0) NOT NULL,
TABLE_DATE DATE NOT NULL,
TABLE_TIMESTAMP NUMBER(19, 0) NOT NULL,
TABLE_BUNDLE_ID VARCHAR2(240 BYTE) NOT NULL,
TABLE_TYPE NUMBER(8, 0) NOT NULL,
TABLE_SEVERITY NUMBER(19, 0) NOT NULL,
TABLE_FACILITY NUMBER(19, 0) NOT NULL,
TABLE_HOST VARCHAR2(120 BYTE) NOT NULL,
TABLE_PROCESS VARCHAR2(240 BYTE) NOT NULL,
TABLE_SYSTEM VARCHAR2(240 BYTE) NOT NULL,
TABLE_SESSION_ID VARCHAR2(240 BYTE) NOT NULL,
TABLE_PRINCIPAL VARCHAR2(120 BYTE) NOT NULL,
OBJECT_ID VARCHAR2(120 BYTE),
OBJECT_TYPE VARCHAR2(2 BYTE),
CLIENT_HOST VARCHAR2(120 BYTE),
ACCESS_HOST VARCHAR2(120 BYTE),
SCOPE_ID VARCHAR2(120 BYTE),
STATUS NUMBER(19, 0),
OBJECT_HISTORY NUMBER(19, 0),
TABLE_DETAILS VARCHAR2(4000 BYTE)
)
PARTITION BY RANGE (PARTITION_ID)
(
PARTITION partitionname1_P30 VALUES LESS THAN (30)
,<repeat partition by 30s up to 360, total of 12 partitions>
You can do it like this:
DECLARE
CURSOR PartTables IS
SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'TABLENAME';
highValue TIMESTAMP;
BEGIN
FOR aTab IN PartTables LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.HIGH_VALUE||'; END;' USING OUT highValue;
IF highValue < ADD_MONTHS(SYSDATE, -2) THEN
EXECUTE IMMEDIATE 'ALTER TABLE TABLENAME TRUNCATE PARTITION '||aTab.PARTITION_NAME||' UPDATE INDEXES';
END IF;
END LOOP;
END;
This would work for a RANGE or INTERVAL based partitions, however in this case your requirement is rather useless because you would keep empty partitions for ever. Usually you drop old partitions, for that just replace TRUNCATE by DROP.
In case your partition is base on LIST, i.e. month number the solution would be this:
DECLARE
CURSOR PartTables IS
SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'TABLENAME';
highValue INTEGER;
BEGIN
FOR aTab IN PartTables LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.HIGH_VALUE||'; END;' USING OUT highValue;
IF highValue NOT IN (
EXTRACT(MONTH FROM SYSDATE),
EXTRACT(MONTH FROM ADD_MONTHS(SYSDATE, -1))
)
THEN
EXECUTE IMMEDIATE 'ALTER TABLE TABLENAME TRUNCATE PARTITION '||aTab.PARTITION_NAME||' UPDATE INDEXES';
END IF;
END LOOP;
END;
Based on your strange partition definition, the conditions would be
IF highValue NOT IN (
30*CEIL(TO_CHAR(SYSDATE, 'fmddd')/30),
30*CEIL(TO_CHAR(ADD_MONTHS(SYSDATE, -1), 'fmddd')/30)
)
THEN
but you may run into issues if you run the procedure between 26th - 31st of December.
I have been tasked to populate a table called Sales_Facts with a PL/SQL block but I return 0 results. The procedure is executed with out error and I run my script to populate my table but my SELECT COUNT script returns nothing. I cannot see what I am doing wrong.
Here's what I have:
CREATE TABLE Sales (
sale_ID VARCHAR2(10) NOT NULL,
salesperson_ID VARCHAR2(10) NOT NULL,
cust_ID VARCHAR2(10) NOT NULL,
sale_date DATE,
VIN VARCHAR2(20) NOT NULL,
mileage INT,
vehicle_status VARCHAR2(15),
gross_sale_price NUMBER(8,2) NOT NULL,
PRIMARY KEY (sale_ID),
CONSTRAINT FK_Customer_ID FOREIGN KEY (cust_ID) REFERENCES Customers(cust_ID),
CONSTRAINT FK_VIN_ID FOREIGN KEY (VIN) REFERENCES Sale_Vehicles(VIN));
CREATE TABLE Times (
sale_day DATE NOT NULL, --populated from Sales sale_date
day_type VARCHAR2(50) NOT NULL,
PRIMARY KEY (sale_day));
CREATE TABLE Vehicles (
vehicle_Code VARCHAR2(10),
description VARCHAR2(100),
PRIMARY KEY (vehicle_Code));
Vehicles is populated with this:
CREATE SEQUENCE veh_code_seq
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 20;
COMMIT;
--PL/SQL Block
SET SERVEROUTPUT ON
DECLARE
vehType VARCHAR2(50);
v_make OLTP_Vehicles.make%type;
v_model OLTP_Vehicles.model%type;
CURSOR v_type IS SELECT DISTINCT make, model FROM OLTP_Vehicles;
BEGIN
OPEN v_type;
LOOP
FETCH v_type INTO v_make, v_model;
vehType := v_make || ', ' || v_model;
INSERT INTO Vehicles (vehicle_Code, description)
VALUES (veh_code_seq.NEXTVAL, vehType);
EXIT WHEN v_type%notfound;
END LOOP;
CLOSE v_type;
END;
/
CREATE TABLE Financing_Plans (
plan_ID VARCHAR2(10) NOT NULL,
institution VARCHAR2(25) NOT NULL,
loan_type VARCHAR2(15) NOT NULL,
percentage DECIMAL(4,2) NOT NULL,
min_down NUMBER(8,2) NOT NULL,
max_loan_amt NUMBER(8,2) NOT NULL,
max_term INT NOT NULL,
PRIMARY KEY (plan_ID));
CREATE TABLE Dealerships (
dealer_ID VARCHAR2(5) NOT NULL,
location VARCHAR(30) NULL,
region_ID VARCHAR(5) NULL,
street_address VARCHAR2(100) NOT NULL,
city VARCHAR2(25) NOT NULL,
state VARCHAR2(15) NOT NULL,
zip VARCHAR2(5) NOT NULL,
phone VARCHAR2(10) NOT NULL,
sqft NUMERIC(8,2) NULL,
opened_date DATE,
manager VARCHAR2(100) NULL,
district_ID VARCHAR2(5) NOT NULL,
PRIMARY KEY (dealer_ID),
CONSTRAINT UC_Dealership UNIQUE (dealer_ID,district_ID));
CREATE TABLE Sales_Facts (
sale_day DATE NOT NULL,
vehicle_Code VARCHAR2(10) NOT NULL,
plan_ID VARCHAR2(10) NOT NULL,
dealer_ID VARCHAR2(5) NOT NULL,
vehicles_sold NUMBER(8,2) NOT NULL,
gross_sales_amt NUMBER(8,2) NOT NULL,
CONSTRAINT PK_Sales_Facts PRIMARY KEY (sale_day, vehicle_Code, plan_ID, dealer_ID),
CONSTRAINT FK_Sale_Day FOREIGN KEY(sale_day) References Times(sale_day),
CONSTRAINT FK_Vehicle_Code FOREIGN KEY(vehicle_Code) References Vehicles(vehicle_Code),
CONSTRAINT FK_Fin_Plan_ID FOREIGN KEY(plan_ID) References Financing_Plans(plan_ID),
CONSTRAINT FK_Dealer_ID FOREIGN KEY(dealer_ID) References Dealerships(dealer_ID));
And here is my procedure that is not returning any results:
CREATE OR REPLACE PROCEDURE Populate_Sales_Facts
AS
l_sale_day DATE;
l_vehicle_Code VARCHAR2(10);
l_plan_ID VARCHAR2(10);
l_dealer_ID VARCHAR2(5);
l_vehicles_sold NUMBER(8,2);
l_gross_sales_amt NUMBER(8,2);
CURSOR c1 IS SELECT sale_day,vehicle_Code,fp.plan_ID,d.dealer_ID,
COUNT (*) AS vehicles_sold,
SUM (s.gross_sale_price) AS gross_sales_amt
FROM Times t, Sales s, Financing_Plans fp, Dealerships d, Vehicles v
WHERE t.sale_day = s.sale_date
GROUP BY sale_day, vehicle_Code, fp.plan_ID, d.dealer_ID;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO l_sale_day, l_vehicle_Code, l_plan_ID, l_dealer_ID, l_vehicles_sold, l_gross_sales_amt;
EXIT WHEN c1%NOTFOUND;
IF l_vehicles_sold <> 0 THEN
INSERT INTO SALES_FACTS (sale_day,vehicle_Code,plan_ID,dealer_ID,vehicles_sold, gross_sales_amt)
VALUES (l_sale_day,l_vehicle_Code,l_plan_ID,l_dealer_ID,l_vehicles_sold,l_gross_sales_amt);
END IF;
END LOOP;
CLOSE c1;
END;
/
BEGIN
Populate_Sales_Facts;
END;
/
I was given the fields to populate the Sales table and they cannot be changed
per my requirements but I did fix my WHERE statement to pull sale_day from the Times table where equal to the sale_date in the Sales table because those are the only fields that linked. So I was able to get the table to populate but now instead of getting no more than 200 rows, I am getting 61065 rows of data. Here are my requirements: get every possible combination of the dimension tables’ primary keys and then the total vehicles sold and gross sales amount for each combination. If these values for Total_Vehicles_Sold and Gross_Sales_Amount for a combination are zero then don’t INSERT a row into the SALES_FACT table. Only insert rows for combinations of the four foreign key columns where there
were some vehicles sold. Maybe I am just misunderstanding the task but I feel like I am getting too many rows now.
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;
I have to create more than 500 tables having the same columns,indexes and constraints.
In The below code I have created table called TABLE_1 like wise i have to create tables from TABLE_1 to TABLE_500....
It takes more time for creating 500 tables and indexes.
Is there any way to increase speed of table creation.?
CREATE TABLE TABLE_1 (FEATURE_ID NUMBER(*,0) NOT NULL ENABLE,
COL_1 VARCHAR2(3 CHAR),
COL_2 VARCHAR2(5 CHAR),
COL_3 NUMBER(*,0),
COL_4 NUMBER(*,0),
COL_5 VARCHAR2(5 CHAR),
COL_6 CHAR(5 BYTE),
COL_7 NUMBER(*,0),
COL_8 VARCHAR2(50 CHAR),
COL_9 NUMBER(*,0),
COL_10 VARCHAR2(20 CHAR),
GEOMETRY SDO_GEOMETRY);
CREATE BITMAP INDEX TABLE_1_DM_IDX ON TABLE_1(COL_3);
CREATE BITMAP INDEX TABLE_1_ATR_IDX ON TABLE_1(COL_4);
CREATE INDEX TABLE_1_SPATIAL_IDX ON TABLE_1(GEOMETRY)
INDEXTYPE IS MDSYS.SPATIAL_INDEX;
ALTER TABLE TABLE_1 ADD CONSTRAINT TABLE_1_PK PRIMARY KEY (FEATURE_ID);
ALTER TABLE TABLE_1 ADD CONSTRAINT TABLE_1_PK PRIMARY KEY (FEATURE_ID);
Create a PLSQL script that creates the names for you.
begin
for i in 1..500 loop
execute immediate 'CREATE TABLE TABLE_'||i|| '
(FEATURE_ID NUMBER(*,0) NOT NULL ENABLE,
COL_1 VARCHAR2(3 CHAR),
COL_2 VARCHAR2(5 CHAR),
... ';
execute immediate 'CREATE BITMAP INDEX TABLE_'||i||'_DM_IDX ON TABLE_'||i||'(COL_3)';
... etc.
end loop;
end;
/