I have the following table definition, with data that creates fine but I like to convert it to a more generic format but I'm having issues. Can someone point me in the right direction
CREATE TABLE partition_retention
(
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
TABLE_NAME VARCHAR2(30),
DAYS NUMBER(6),
CONSTRAINT
partition_retention_pk primary key (table_name));
/
INSERT into partition_retention(TABLE_NAME, DAYS)
WITH data as (
select 'T1', 0
from dual union all
select 'T3', 15
from dual union all
select 'T4', 10
from dual union all
select 'T5', 5
from dual)
SELECT * from data;
/
-- having problem creating
CREATE TABLE PARTITION_RETENTION AS (
TABLE_NAME AS VARCHAR2(30)
RETENTION DAY AS INTERVAL DAY(3) TO SECOND(0)
);
The three AS keywords are invalid (create table .. as select .. is valid, but you aren't doing that); you are missing a comma; and you have an unquoted column name with a space.
Correcting those things, this works:
CREATE TABLE PARTITION_RETENTION (
TABLE_NAME VARCHAR2(30),
RETENTION_DAY INTERVAL DAY(3) TO SECOND(0)
);
Your inserts will then have to insert interval values, not simple numbers, obviously.
db<>fiddle
how can I add a CONSTRAINT on the table to ensure the day>0 and the time is always 0
You can add separate constraints to check both things:
CREATE TABLE PARTITION_RETENTION (
TABLE_NAME VARCHAR2(30),
RETENTION_DAY INTERVAL DAY(3) TO SECOND(0),
CONSTRAINT CHK_NON_ZERO_DAYS CHECK (
RETENTION_DAY > INTERVAL '0' DAY
),
CONSTRAINT CHK_WHOLE_DAYS CHECK (
EXTRACT(HOUR FROM RETENTION_DAY) = 0
AND EXTRACT(MINUTE FROM RETENTION_DAY) = 0
AND EXTRACT(SECOND FROM RETENTION_DAY) = 0
)
);
to give slightly different errors (via the constraint names) - db<>fiddle - or combine them into one.
I'm not sure this is really any clearer or easier than having a number column constrained to integers between 1 and 999.
Related
I am getting this error.
ORA-14074: partition bound must collate higher than that of the last
partition
14074.
I am unable to solve this error.
I am trying to adding partition to the table.
The limit value for the partition you're adding is lower that the highest limit already in the table:
create table t (
c1 int
) partition by range ( c1 ) (
partition p1 values less than ( 9 ),
partition p2 values less than ( 99 )
);
alter table t
add partition p3 values less than ( 0 );
ORA-14074: partition bound must collate higher than that of the last partition
0 < 99 => you can't add the partition. If you want a partition with this upper bound you'll need to split an existing partition.
You can find the current highest value by querying *tab_partitions and getting the high_value for the partition with the max partition_position
with rws as (
select table_name, partition_position, high_value,
row_number () over (
partition by table_name
order by partition_position desc
) rn
from user_tab_partitions
)
select table_name, high_value
from rws
where rn = 1;
TABLE_NAME HIGH_VALUE
---------- ----------
T 99
have some PLSQL code that generates a list of dates from a range, which seems to be working fine.
As part of a larger project I want to generate a procedure that will create a list of absences for each employee.
My first step is to use the MINUS command to remove all the holidays, which fall into the range of dates. Is there an easy way of doing this instead of comparing each holiday one at a time (there maybe several in the table) against the GENERATED dates.
If possible, I would prefer breaking all these tasks into small procedures or functions for easy debugging and legibility.
If there is an easier way to do this I am open to all suggestions. Thanks in advance for your help, expertise and patience.
ALTER SESSION SET
NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table holidays(
holiday_date DATE,
holiday_name VARCHAR2(20)
);
INSERT into holidays
(holiday_date,
holiday_name)
VALUES
(
TO_DATE('2021/07/21 00:00:00', 'yyyy/mm/dd hh24:mi:ss'), 'July 21 2021');
CREATE OR REPLACE PROCEDURE generate_dates
(
p_start_date IN DATE,
p_end_date IN DATE
)
AS
l_day DATE := p_start_date;
BEGIN
WHILE l_day <= p_end_date
LOOP
DBMS_OUTPUT.PUT_LINE(l_day);
l_day := l_day + 1;
END LOOP;
END generate_dates;
EXEC generate_dates(TRUNC(SYSDATE),TRUNC(SYSDATE+10));
Create table employees(
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(20),
card_num VARCHAR2(10),
work_days VARCHAR2(7)
);
ALTER TABLE employees
ADD ( CONSTRAINT employees_pk
PRIMARY KEY (employee_id));
INSERT INTO employees
(
EMPLOYEE_ID,
first_name,
last_name,
card_num,
work_days
)
WITH names AS (
SELECT 1, 'Jane', 'Doe', 'F123456', 'NYYYYYN' FROM dual UNION ALL
SELECT 2, 'Madison', 'Smith', 'R33432','NYYYYYN'
FROM dual UNION ALL
SELECT 3, 'Justin', 'Case', 'C765341','NYYYYYN'
FROM dual UNION ALL
SELECT 4, 'Mike', 'Jones', 'D564311','NYYYYYN' FROM dual
) SELECT * FROM names
-- check to see if working for that day. Byte=Y for Yes
SELECT SUBSTR( work_days, to_char(TRUNC(SYSDATE), 'D'),1) Work_Day
FROM employees
create table timeoff(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
timeoff_date DATE,
timeoff_type VARCHAR2(1),
constraint timeoff_chk check (timeoff_date=trunc(timeoff_date, 'dd')),
constraint timeoff_pk primary key (employee_id, timeoff_date)
);
INSERT INTO timeoff (EMPLOYEE_ID,TIMEOFF_DATE,TIMEOFF_TYPE
)
WITH dts AS (
SELECT 1, to_date('20210726 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual UNION ALL
SELECT 2, to_date('20210726 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual UNION ALL
SELECT 2, to_date('20210727 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual )
SELECT * FROM dts
CREATE TABLE emp_attendance(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
start_date DATE,
end_date DATE,
week_number NUMBER(2),
create_date DATE DEFAULT SYSDATE
);
create table absences(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
absent_date DATE,
constraint absence_chk check (absent_date=trunc(absent_date, 'dd')),
constraint absence_pk primary key (employee_id, absent_date)
);
INSERT INTO emp_attendance ( EMPLOYEE_ID, START_DATE,END_DATE,WEEK_NUMBER)
WITH dts AS (
SELECT 1, to_date('20210728 13:10:00','YYYYMMDD HH24:MI:SS'),
to_date('20210728 23:15:00','YYYYMMDD HH24:MI:SS'), 30 FROM dual UNION ALL
SELECT 2, to_date('20210728 12:10:10','YYYYMMDD HH24:MI:SS'),
to_date('20210728 20:15:01','YYYYMMDD HH24:MI:SS'), 30 FROM dual)
SELECT * FROM dts
CREATE OR REPLACE TYPE obj_date IS OBJECT (
date_val DATE
);
CREATE OR REPLACE TYPE nt_date IS TABLE OF obj_date;
CREATE OR REPLACE FUNCTION generate_dates(
p_from IN DATE
,p_to IN DATE)
RETURN nt_date PIPELINED
IS
-- normalize inputs to be as-of midnight
v_from DATE :=
TRUNC(NVL(p_from, SYSDATE));
v_to DATE := TRUNC(NVL(p_to, SYSDATE));
BEGIN
LOOP
EXIT WHEN v_from > v_to;
PIPE ROW (obj_date(v_from));
v_from := v_from + 1; -- next. calendar day
END LOOP;
RETURN;
END generate_dates;
INSERT INTO absences
(employee_id, absent_date)
SELECT e.employee_id,
c.date_val
FROM employees e
INNER JOIN table(generate_dates(date '2021-07-20', DATE '2021-07-31')) c
PARTITION BY ( e.employee_id )
ON (SUBSTR(e.work_days,
TRUNC(c.date_val) -
TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
WHERE NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.date_val = h.holiday_date
)
AND NOT EXISTS(
SELECT 1
FROM timeoff t
WHERE e.employee_id = t.employee_id
AND t.timeoff_date = c.date_val
)
AND NOT EXISTS(
SELECT 1
FROM emp_attendance ea
WHERE e.employee_id = ea.employee_id
AND TRUNC(ea.start_date) = c.date_val
)
ORDER BY
e.employee_id,
c.date_val
;
Don't use lots of procedures and/or a functions; just use a single query:
SELECT e.employee_id,
c.day
FROM employees e
INNER JOIN (
WITH calendar ( start_date, end_date ) AS (
SELECT DATE '2021-07-01', DATE '2021-07-30' FROM DUAL
UNION ALL
SELECT start_date + 1, end_date
FROM calendar
WHERE start_date + 1 <= end_date
)
SELECT start_date AS day
FROM calendar
) c
PARTITION BY ( e.employee_id )
ON (SUBSTR(e.work_days, TRUNC(c.day) - TRUNC(c.day, 'IW') + 1, 1) = 'Y')
WHERE NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.day = h.holiday_date
)
AND NOT EXISTS(
SELECT 1
FROM timeoff t
WHERE e.employee_id = t.employee_id
AND t.timeoff_date = c.day
)
ORDER BY
e.employee_id,
c.day
Notes:
This assumes that your work_days column aligns with the ISO week; if it does not then you will need to adjust the substring.
Do not use TO_CHAR(date_value, 'D') as users will get different results depending on their NLS_TERRITORY session setting.
db<>fiddle here
I would like to insert data in to two tables. Will be one-to-many connection. For this, I have to use Foreign Key, of course.
I think, table1 - ID column is an ideal for this a Primary Key. But I generate it always with a trigger, automatically, every line. SO,
How can I put Table1.ID (auto generated, Primary Key) column in to table2.Fkey column in the same insert query?
INSERT ALL INTO table1 ( --here (before this) generated the table1.id column automatically with a trigger.
table1.food,
table1.drink,
table1.shoe
) VALUES (
'apple',
'water',
'slippers'
)
INTO table2 (
fkey,
color
) VALUES (
table1.id, -- I would like table2.fkey == table1.id this gave me error
'blue'
) SELECT
*
FROM
table1
INNER JOIN table2 ON table1.id = table2.fkey;
The error message:
"00904. 00000 - "%s: invalid identifier""
As suggested by #OldProgrammer, use sequence
INSERT ALL INTO table1 ( --here (before this) generated the table1.id column automatically with a trigger.
table1_id,
table1.food,
table1.drink,
table1.shoe
) VALUES (
<sequecename_table1>.nextval,
'apple',
'water',
'slippers'
)
INTO table2 (
fkey,
color
) VALUES (
<sequecename_table2>.nextval,
<sequecename_table1>.currval, -- returns the current value of a sequence.
'blue'
) SELECT
*
FROM
table1
INNER JOIN table2 ON table1.id = table2.fkey;
Since you're using Oracle DB's 12c version, then might use Identity Column Property. Then easily return the value of first table's (table1) to a local variable by charging of returning clause just after an insert statement for table1, and use inside the next insert statement which is for table2 as stated below :
SQL> create table table1(
2 ID integer generated always as identity primary key,
3 food varchar2(50), drink varchar2(50), shoe varchar2(50)
4 );
SQL> create table table2(
2 fkey integer references table1(ID),
3 color varchar2(50)
4 );
SQL> declare
2 cl_tab table1.id%type;
3 begin
4 insert into table1(food,drink,shoe) values('apple','water','slippers' )
5 returning id into cl_tab;
6 insert into table2 values(cl_tab,'blue');
7 end;
8 /
SQL> select * from table1;
ID FOOD DRINK SHOE
-- ------- ------- -------
1 apple water slippers
SQL> select * from table2;
FKEY COLOR
---- --------------------------------------------------
1 blue
Anytime you issue the above statement for insertions between begin and end, both table1.ID and table2.fkey columns will be populated by the same integer values. By the way do not forget to commit the changes by insertions, if you need these values throughout the DB(i.e.from other sessions also).
I have a table with one column giving me dates in the form 24-JUL-17 and another column a string telling me the hour in the form hh:mm:ss.several_decimals but they are in 24 hours and not AM/PM, could someone point me on how I can add a column in oracle that combines them both into a timestamp?
CAST the date to a timestamp data type and use TO_DSINTERVAL to convert the time column to a DAY TO SECOND INTERVAL and then add one to the other:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name (
date_column DATE,
time_column VARCHAR2(15)
);
INSERT INTO table_name ( date_column, time_column )
VALUES ( DATE '2017-06-24', '12:34:56.789012' );
Query 1:
SELECT CAST( date_column AS TIMESTAMP )
+ TO_DSINTERVAL( '+0 ' || time_column ) AS datetime
FROM table_name
Results:
| DATETIME |
|----------------------------|
| 2017-06-24 12:34:56.789012 |
Here is an example with a fabricated table.
WITH test_data AS (
SELECT
SYSDATE date_val,
'14:23:15.123' AS time_val
FROM
dual
) SELECT
to_timestamp(TO_CHAR(date_val,'MM/DD/YYYY')
|| ' '
|| time_val,'MM/DD/YYYY HH24:MI:SS.FF3')
FROM
test_data
Just concatenate both input strings with || and convert it with TO_TIMESTAMP. The format is a bit tricky, took me a while to get it right.
CREATE TABLE mytable (mydate VARCHAR2(9), mytime VARCHAR2(20), mytimestamp TIMESTAMP);
INSERT INTO mytable (mydate, mytime) VALUES ('24-JUL-17', '22:56:47.1915010');
Test it
SELECT TO_TIMESTAMP(mydate||mytime, 'DD-MON-YYHH24:MI:SS.FF9') FROM mytable;
2017-07-24 22:56:47,191501000
Or, in a table:
UPDATE mytable
SET mytimestamp = TO_TIMESTAMP(mydate||mytime, 'DD-MON-YYHH24:MI:SS.FF9');
I have 2 tables,
lv_data,
It has the following fields,
emp_name tot_days
guru 18
leave_data
it has the following fields,
emp_name From_date to_date no_of_days remaining_days
guru 02/05/2012 03/05/2012 2
In second table if the data is inserted, the no_of_days will be automatically calculated (from to_date - From_date)+1
Here I need to write the trigger to update the remaining_days column,
In first table for all emp_name, tot_days is 18 days, so in second table whenever the record is inserted, the remaining_days should be calculated like this
remaining_days := tot_days - no_of_days
And this(calculated) value should be updated in tot_days column in first table(lv_data),
Sample Example:
emp_name tot_days
guru 18
leave_data
emp_name From_date to_date no_of_days remaining_days
guru 02/05/2012 03/05/2012 2 16
Now the first table should be updated like,
emp_name tot_days
guru 16
So I need to update 2 tables. Can someone help me to update these 2 tables through trigger?
Have an before insert trigger on the table which will set the record before inserting it
Few things to note:
Your data model doesn't have a unique identifier for a row
I don't think your "no_of_days" calculation is right.
CREATE OR replace TRIGGER leave_data_before_insert
BEFORE INSERT ON LEAVE_DATA
FOR EACH ROW
DECLARE
CURSOR c_lv_data(
p_emp_id IN lv_data.id%TYPE) IS
SELECT tot_days
FROM lv_data
WHERE id = p_emp_id;
v_tot_days NUMBER;
BEGIN
OPEN c_lv_data(:new.id);
FETCH c_lv_data INTO v_tot_days;
:new.no_of_days := ( :new.from_date - :new.TO_DATE ) + 1;
:new.remaining_days := v_tot_days - :new.no_of_days;
UPDATE lv_data
SET tot_days = :new.remaining_days
WHERE id = :new.id;
CLOSE c_lv_data;
END;
DDL used to test:
CREATE TABLE lv_data
(
id NUMBER,
emp_name VARCHAR2(240),
tot_days NUMBER
);
CREATE TABLE leave_data
(
id NUMBER,
emp_name VARCHAR2(240),
from_date DATE,
to_date DATE,
no_of_days NUMBER,
remaining_days NUMBER
);
DML used to test:
INSERT INTO lv_data
VALUES (1,
'sathya',
18);
INSERT INTO LEAVE_DATA
VALUES ('1',
'sathya',
SYSDATE,
SYSDATE + 2,
NULL,
NULL);