How to add minutes to a date(timestamp) in Oracle? - sql

I created the following tables:
create table travel
(
code VARCHAR2(3),
date timestamp,
constraint pk_code_travel primary key(code)
);
create table extras
(
code_extra VARCHAR2(3),
code_travel VARCHAR2(3),
minutes NUMBER(3),
constraint pk_code_extra primary key(code_extra)
);
I inserted the following dates:
insert into travel (code, date)
values('T01',TO_TIMESTAMP('10/01/2016 15:30','DD/MM/YYYY HH24:MI'));
insert into travel (code, date)
values('T02',TO_TIMESTAMP('15/02/2016 17:45','DD/MM/YYYY HH24:MI'));
insert into travel (code, date)
values('T03',TO_TIMESTAMP('01/04/2016 22:00','DD/MM/YYYY HH24:MI'));
insert into extras (code_extra, code_travel, minutes)
values('E01', 'T01', 50);
insert into extras (code_extra, minutes)
values('E02', 'T02', 123);
insert into extras (code_extra, minutes)
values('E03', 'T03', 48);
The question is: how to add the minutes from the table "extras" to the date of "travel" table ?
For example the table "travel" should be:
T01', 10/01/2016 15:30 + 50 minutes.
T02', 15/02/2016 17:45 + 123 'minutes.
T03', 01/04/2016 22:00 + 48 minutes.
All help will be appreciated.

You can create a table like this:
create table extras
(
code_extra VARCHAR2(3),
code_travel VARCHAR2(3),
minutes INTERVAL DAY TO SECOND(0),
constraint pk_code_extra primary key(code_extra)
);
Then you can insert interval values by one of the following methods:
INSERT INTO extras (code_extra, code_travel, minutes)
VALUES('E01', 'T01', INTERVAL '50' MINUTE);
INSERT INTO extras (code_extra, minutes)
VALUES('E02', 'T02', 123 * INTERVAL '1' MINUTE);
INSERT INTO extras (code_extra, minutes)
VALUES('E03', 'T03', NUMTODSINTERVAL(48, 'MINUTE'));
Or as a select statement from your existing tables:
SELECT code, "DATE" + minutes * INTERVAL '1' MINUTE
FROM travel
JOIN extras ON code = code_extra;
or
SELECT code, "DATE" + NUMTODSINTERVAL(minutes, MINUTE)
FROM travel
JOIN extras ON code = code_extra;
btw, you should not use reserved words like DATE as column name (that's the reason why I enclosed it by double-quotes)

Related

Select date in oracle db

I set or insert time in a postgres table value with now()::timestamp
But it does not work in oracle db.
This is the table:
create table types
(
id number not null,
description varchar(255) not null,
created_at timestamp null,
updated_at timestamp null,
deleted_at timestamp null,
CONSTRAINT autenticacion_id PRIMARY KEY (id)
);
So to insert the data I make:
insert into types (id, description) values (1, 'hello world', now()::timestamp)
But I get:
ORA-00917: missing comma
insert into types (id, description) values (1, 'hello world', (select sysdate from dual))
I get:
ORA-00942: table or view does not exist
You are providing 3 values and have only given 2 columns that you want to insert into.
SYSDATE is a DATE data type and SYSTIMESTAMP is a TIMESTAMP data type; they both have a date and time component but SYSTIMESTAMP also has fractional seconds (and a time zone, but that will be ignored as the column you are inserting into is a TIMESTAMP and not a TIMESTAMP WITH TIME ZONE data type).
What you probably want is:
INSERT INTO types (id, description, created_at)
VALUES (1, 'hello world', SYSTIMESTAMP)
However, you may want to consider that the timestamp should be in a specific time zone (typically UTC):
INSERT INTO types (id, description, created_at)
VALUES (1, 'hello world', SYSTIMESTAMP AT TIME ZONE 'UTC')
Note: You can wrap a value in (SELECT value FROM DUAL) but it is not necessary.
fiddle

How to insert a date value stored in another table and increment it by x days?

I'm using a PostgreSQL database and have one table "event_date" with "started_at" (DATE format, e.g. '2019-10-29') values which are also the PRIMARY KEY in this table.
Now I want to insert this "started_at" value in another table "event_days" but increasing the date by x days.
Can someone explain how to achieve this? Thanks a lot!
Use an INSERT with a SELECT:
insert into other_table (started_at, location, moderator)
select started_at + 42, 'Hall 5', 'Tom'
from event_days;
+ 42 add 42 days to the date value.
if you really need insert the related record you could use a insert select
insert into event_days(started_at, location , moderator)
select started_at + interval '10 day', 'hall 5' , 'Tom'
from event_date

How to calculate value with multiple condition and how to design database?

I need to calculated data with multiple condition
MinOTHrs MaxOTHs DayType Rate
------------------------------ -----------
3 - Working_day 18
4 11 Weekend 18
11 - Weekend 36
For example
if employee do OT on working_day > 3 Hrs. they will get rate 18 (MAX 18 for working_day)
if employee do OT on weekend > 4 Hrs. but < 11 Hrs. they will get rate 18
if employee do OT on weekend > 11 Hrs. they will get rate 36(MAX 36 for weekend)
Could you suggest me about coding and design database ?
Thank you very much
I'd probably do most of the work as multiple joins between Work and OT tables. That way you can be fairly specific about the scenario you want.
Below is a nice primer for you. It's not perfect but it should get you a long way towards where I think you want to go! (IDs included are just for debugging purposes - they're not required of course).
//setup
CREATE TABLE overTime (
MinOTHrs integer,
MaxOTHrs integer,
DayType varchar(max),
payRate integer
);
CREATE TABLE workLog (
logID integer,
empID integer,
DayType varchar(max),
hoursWorked integer
);
insert into overTime values (3, null, 'Working_day', 18);
insert into overTime values (4, 11, 'Weekend', 18);
insert into overTime values (11, null, 'Weekend', 36);
insert into workLog values (567, 1234, 'Working_day', 2);
insert into workLog values (568, 1234, 'Working_day', 5);
insert into workLog values (569, 1234, 'Weekend', 2);
insert into workLog values (570, 1234, 'Weekend', 9);
insert into workLog values (571, 1234, 'Weekend', 14);
//query
select
wl.logID
, wl.empID
, wl.DayType
, wl.hoursWorked
, coalesce(weekdayOverTime.MinOTHrs, weekendLowOverTime.MinOTHrs, weekendHighOverTime.MinOTHrs) as MINOTHrs
, coalesce(weekdayOverTime.MaxOTHrs, weekendLowOverTime.MaxOTHrs, weekendHighOverTime.MaxOTHrs) as MAXOTHrs
, coalesce(weekdayOverTime.payRate, weekendLowOverTime.payRate, weekendHighOverTime.payRate) as maxOTPayBracket
from workLog wl
left join overTime weekdayOverTime on wl.DayType = weekdayOverTime.dayType and wl.hoursWorked > weekdayOverTime.MinOTHrs and wl.dayType = 'Working_day'
left join overTime weekendLowOverTime on wl.DayType = weekendLowOverTime.dayType and wl.hoursWorked > weekendLowOverTime.MinOTHrs and wl.hoursWorked < weekendLowOverTime.MaxOTHrs and wl.dayType= 'Weekend'
left join overTime weekendHighOverTime on wl.DayType = weekendHighOverTime.dayType and wl.hoursWorked > weekendHighOverTime.MinOTHrs and weekendHighOverTime.MaxOTHrs is null and wl.dayType= 'Weekend'

Writing triggers of a table to automatically update another table

I got these tables (Oracle):
Doctors (Doctor_ID (PK),
Doctor_Name,
DoB,
Specialization)
Doctors_At_Work (Doctor_ID (PK),
The_Date (PK),
Hour_Start (PK),
Hour_Stop,
Room)
Consultations_Intervals (Doctor_ID (PK),
The_Date (PK),
Start_Hour_Consult (PK),
Stop_Hour_Consut,
Room)
OBS: A consultation last for only 30 minutes.
My task is to create the necessary triggers (insert/update/delete) of Doctors_At_Work in order to automatically update the Consultations_Intervals table.
What I did so far:
Create sequence seq_id_doctor START with 1 INCREMENT BY 1 ORDER NOCACHE;
CREATE OR REPLACE trigger t_1
BEFORE INSERT ON Doctors_At_Work FOR EACH ROW
BEGIN
:NEW.Doctor_ID:=seq_id_doctor.NextValue;
:NEW.The_Date:=CURRENT_TIMESTAMP;
:NEW.Hour_Start:=Select Extract (Hour from CURRENT_TIMESTAMP);
END;
/
CREATE OR REPLACE trigger t_2
AFTER INSERT OR UPDATE ON Consultations_Intervals FOR EACH ROW
BEGIN
INSERT INTO Consultations_Intervals VALUES
(:NEW.Doctor_ID, :NEW.The_Date, :NEW.Hour_Start, :NEW.Hour_Start +
interval '30' minute, :NEW.Room);
END;
/
What is wrong with it? How should I solve this task? (if there are other ideas)
All you need a simple trigger on your Doctors_At_work table. See example below:
Tables:
create table Doctors_At_Work (Doctor_ID number Primary key,
The_Date date ,
Hour_Start date ,
Hour_Stop date,
Room number);
create table Consultations_Intervals (Doctor_ID number,
The_Date date ,
Start_Hour_Consult number,
Stop_Hour_Consut number,
Room number);
Trigger:
create or replace trigger t_1
before insert or update or delete on doctors_at_work
for each row
begin
insert into consultations_intervals (doctor_id,
the_date,
start_hour_consult,
stop_hour_consut,
room)
values (:new.doctor_id,
current_timestamp,
extract (minute from current_timestamp),
extract (minute from current_timestamp),
:new.room);
end;
/
Output:
SQL> Prompt "Before trigger"
"Before trigger"
SQL> select * from Doctors_At_Work;
no rows selected
SQL> select * from Consultations_Intervals;
no rows selected
SQL> create or replace trigger t_1
before insert or update or delete on doctors_at_work
2 3 for each row
4 begin
insert into consultations_intervals (doctor_id,
5 6 the_date,
7 start_hour_consult,
8 stop
.
.
.
Trigger created.
SQL> Insert into DOCTORS_AT_WORK
(DOCTOR_ID, THE_DATE, HOUR_START, HOUR_STOP, ROOM)
Values
(2, TO_DATE('12/18/2016 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), TO_DATE('12/13/2016 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), TO_DATE('12/14/2016 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 12);
COMMIT;
2 3 4
1 row created.
SQL>
Commit complete.
SQL> select * from Doctors_At_Work;
DOCTOR_ID THE_DATE HOUR_STAR HOUR_STOP ROOM
---------- --------- --------- --------- ----------
2 18-DEC-16 13-DEC-16 14-DEC-16 12
SQL> select * from Consultations_Intervals;
DOCTOR_ID THE_DATE START_HOUR_CONSULT STOP_HOUR_CONSUT ROOM
---------- --------- ------------------ ---------------- ----------
2 19-DEC-16 17 17 12

How to find first free start times from reservations in Postgres

People work from 10:00AM to 21:00PM except Sundays and public holidays.
Jobs for them are reserved at 15 minute intervals. Job duration is from 15 minutes to 4 hours. Whole job must fit to single day.
How to find first nearest free start times which are not reserved for given duration in Postgres 9.3 starting from current date and time ?
For example, Mary has already reservation at 12:30 .. 16:00 and
John has already reservation at 12:00 to 13:00
Reservat table contains reservations, yksus2 table contains workes and
pyha table contains public holidays. Table structures are below. Reservat structure can changed if this helps.
Query for ealiest start times for duration of 1.5 hours should return
John 2014-10-28 10:00
Mary 2014-10-28 10:00
John 2014-10-28 10:15
Mary 2014-10-28 10:15
John 2014-10-28 10:30
Mary 2014-10-28 10:30
Mary 2014-10-28 11:00
John 2014-10-28 13:00
Mary 2014-10-28 16:00
Mary 2014-10-28 16:15
Mary 2014-10-28 16:30
... etc and also starting from next days
I tried query based on answer in How to return only work time from reservations in PostgreSql? below but it returns wrong result:
MARY 2014-10-28 13:00:00
MARY 2014-10-29 22:34:40.850255
JOHN 2014-10-30 22:34:40.850255
MARY 2014-10-31 22:34:40.850255
MARY 2014-11-03 22:34:40.850255
Also sliding start times 10:00, 10:30 etc are not returned.
How to get proper first reservations?
Query which returns wrong result is:
insert into reservat (objekt2, during) values
('MARY', '[2014-10-28 11:30:00,2014-10-28 13:00:00)'),
('JOHN', '[2014-10-28 10:00:00,2014-10-28 11:30:00)');
with gaps as (
select
yksus,
upper(during) as start,
lead(lower(during),1,upper(during)) over (ORDER BY during) - upper(during) as gap
from (
select
yksus2.yksus,
during
from reservat join yksus2 on reservat.objekt2=yksus2.yksus
where upper(during)>= current_date
union all
select
yksus2.yksus,
unnest(case
when pyha is not null then array[tsrange1(d, d + interval '1 day')]
when date_part('dow', d) in (0, 6) then array[tsrange1(d, d + interval '1 day')]
when d::date = current_Date then array[
tsrange1(d, current_timestamp ),
tsrange1(d + interval '20 hours', d + interval '1 day')]
else array[tsrange1(d, d + interval '8 hours'),
tsrange1(d + interval '20 hours', d + interval '1 day')]
end)
from yksus2, generate_series(
current_timestamp,
current_timestamp + interval '1 month',
interval '1 day'
) as s(d)
left join pyha on pyha = d::date
) as x
)
select yksus, start
from gaps
where gap >= interval'1hour 30 minutes'
order by start
limit 30
Schema:
CREATE EXTENSION btree_gist;
CREATE TABLE Reservat (
id serial primary key,
objekt2 char(10) not null references yksus2 on update cascade deferrable,
during tsrange not null check(
lower(during)::date = upper(during)::date
and lower(during) between current_date and current_date+ interval'1 month'
and (lower(during)::time >= '10:00'::time and upper(during)::time < '21:00'::time)
AND EXTRACT(MINUTE FROM lower(during)) IN (0, 15, 30,45)
AND EXTRACT(MINUTE FROM upper(during)) IN (0, 15, 30, 45)
and (date_part('dow', lower(during)) in (1,2,3,4,5,6)
and date_part('dow', upper(during)) in (1,2,3,4,5,6))
),
EXCLUDE USING gist (objekt2 WITH =, during WITH &&)
);
create or replace function holiday_check() returns trigger language plpgsql stable as $$
begin
if exists (select * from pyha where pyha in (lower(NEW.during)::date, upper(NEW.during)::date)) then
raise exception 'public holiday %', lower(NEW.during) ;
else
return NEW;
end if;
end;
$$;
create trigger holiday_check_i before insert or update on Reservat for each row execute procedure holiday_check();
CREATE OR REPLACE FUNCTION public.tsrange1(start timestamp with time zone,
finish timestamp with time zone ) RETURNS tsrange AS
$BODY$
SELECT tsrange(start::timestamp without time zone, finish::timestamp without time zone );
$BODY$ language sql immutable;
-- Workers
create table yksus2( yksus char(10) primary key);
insert into yksus2 values ('JOHN'), ('MARY');
-- public holidays
create table pyha( pyha date primary key);
Also posted to the pgsql-general mailing list.
Adapted schema
CREATE EXTENSION btree_gist;
CREATE TYPE timerange AS RANGE (subtype = time); -- create type once
-- Workers
CREATE TABLE worker(
worker_id serial PRIMARY KEY
, worker text NOT NULL
);
INSERT INTO worker(worker) VALUES ('JOHN'), ('MARY');
-- Holidays
CREATE TABLE pyha(pyha date PRIMARY KEY);
-- Reservations
CREATE TABLE reservat (
reservat_id serial PRIMARY KEY
, worker_id int NOT NULL REFERENCES worker ON UPDATE CASCADE
, day date NOT NULL CHECK (EXTRACT('isodow' FROM day) < 7)
, work_from time NOT NULL -- including lower bound
, work_to time NOT NULL -- excluding upper bound
, CHECK (work_from >= '10:00' AND work_to <= '21:00'
AND work_to - work_from BETWEEN interval '15 min' AND interval '4 h'
AND EXTRACT('minute' FROM work_from) IN (0, 15, 30, 45)
AND EXTRACT('minute' FROM work_from) IN (0, 15, 30, 45)
)
, EXCLUDE USING gist (worker_id WITH =, day WITH =
, timerange(work_from, work_to) WITH &&)
);
INSERT INTO reservat (worker_id, day, work_from, work_to) VALUES
(1, '2014-10-28', '10:00', '11:30') -- JOHN
, (2, '2014-10-28', '11:30', '13:00'); -- MARY
-- Trigger for volatile checks
CREATE OR REPLACE FUNCTION holiday_check()
RETURNS trigger AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pyha WHERE pyha = NEW.day) THEN
RAISE EXCEPTION 'public holiday: %', NEW.day;
ELSIF NEW.day < now()::date OR NEW.day > now()::date + 31 THEN
RAISE EXCEPTION 'day out of range: %', NEW.day;
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql STABLE; -- can be "STABLE"
CREATE TRIGGER insupbef_holiday_check
BEFORE INSERT OR UPDATE ON reservat
FOR EACH ROW EXECUTE PROCEDURE holiday_check();
Major points
Don't use char(n). Rather varchar(n), or better yet, varchar or just text.
Any downsides of using data type "text" for storing strings?
Don't use the name of a worker as primary key. It's not necessarily unique and can change. Use a surrogate primary key instead, best a serial. Also makes entries in reservat smaller, indexes smaller, queries faster, ...
Update: For cheaper storage (8 bytes instead of 22) and simpler handling I save start and end as time now and construct a range on the fly for the exclusion constraint:
EXCLUDE USING gist (worker_id WITH =, day WITH =
, timerange(work_from, work_to) WITH &&)
Since your ranges can never cross the date border by definition, it would be more efficient to have a separate date column (day in my implementation) and a time range. The type timerange is not shipped in default installations, but easily created. This way you can largely simplify your check constraints.
Use EXTRACT('isodow', ...) to simplify excluding sundays
The day of the week as Monday(1) to Sunday(7)
I assume you want to allow the upper border of '21:00'.
Borders are assumed to be including for the lower and excluding for the upper bound.
The check whether new / updated days lie within a month from "now" is not IMMUTABLE. Moved it from the CHECK constraint to the trigger - else you might run into problems with dump / restore! Details:
Disable all constraints and table checks while restoring a dump
Aside
Besides simplifying input and check constraints I expected timerange to save 8 bytes of storage as compared to tsrange since time only occupies 4 bytes. But it turns out timerange occupies 22 bytes on disk (25 in RAM), just like tsrange (or tstzrange). So you might go with tsrange as well. The principle of query and exclusion constraint are the same.
Query
Wrapped into an SQL function for convenient parameter handling:
CREATE OR REPLACE FUNCTION f_next_free(_start timestamp, _duration interval)
RETURNS TABLE (worker_id int, worker text, day date
, start_time time, end_time time) AS
$func$
SELECT w.worker_id, w.worker
, d.d AS day
, t.t AS start_time
,(t.t + _duration) AS end_time
FROM (
SELECT _start::date + i AS d
FROM generate_series(0, 31) i
LEFT JOIN pyha p ON p.pyha = _start::date + i
WHERE p.pyha IS NULL -- eliminate holidays
) d
CROSS JOIN (
SELECT t::time
FROM generate_series (timestamp '2000-1-1 10:00'
, timestamp '2000-1-1 21:00' - _duration
, interval '15 min') t
) t -- times
CROSS JOIN worker w
WHERE d.d + t.t > _start -- rule out past timestamps
AND NOT EXISTS (
SELECT 1
FROM reservat r
WHERE r.worker_id = w.worker_id
AND r.day = d.d
AND timerange(r.work_from, r.work_to) && timerange(t.t, t.t + _duration)
)
ORDER BY d.d, t.t, w.worker, w.worker_id
LIMIT 30 -- could also be parameterized
$func$ LANGUAGE sql STABLE;
Call:
SELECT * FROM f_next_free('2014-10-28 12:00'::timestamp, '1.5 h'::interval);
SQL Fiddle on Postgres 9.3 now.
Explain
The function takes a _start timestamp as minimum starting time and _duration interval. Be careful to only rule out earlier times on the starting day, not the following days. Simplest by just adding day and time: t + d > _start.
To book a reservation starting "now", just pass now()::timestamp:
SELECT * FROM f_next_free(`now()::timestamp`, '1.5 h'::interval);
Subquery d generates days starting from the input value _day. Holidays excluded.
Days are cross-joined with possible time ranges generated in subquery t.
That is cross-joined to all available workers w.
Finally eliminate all candidates that collide with existing reservations using an NOT EXISTS anti-semi-join, and in particular the overlaps operator && .
Related:
How do you do date math that ignores the year? (for date math example)
Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL
Calculate working hours between 2 dates in PostgreSQL
Thom Brown in psql-general mailing list recommends the following solution.
It is more readable but Erwin answer looks more optimized.
I have 10 workes and 1 month reservation with 15 minute offsess from 8 to 20:00, so perfomance is hopafully not and issue.
Which to use ?
Which solution is better ?
create table pyha (pyha date primary key);
insert into pyha(pyha) values('2014-10-29');
create table yksus2(yksus char(10) primary key);
insert into yksus2 values ('JOHN'),('MARY');
CREATE EXTENSION btree_gist;
CREATE TABLE reservat
(
reservat_id serial primary key,
objekt2 char(10) not null references yksus2 on update cascade deferrable,
during tstzrange not null,
EXCLUDE USING gist (objekt2 WITH =, during WITH &&),
CONSTRAINT same_date
CHECK (lower(during)::date = upper(during)::date),
CONSTRAINT max_1month_future
CHECK (lower(during) between current_date and current_date+ interval'1 month' ),
CONSTRAINT time_between_1000_and_2100
CHECK (lower(during)::time >= '10:00'::time and upper(during)::time < '21:00'::time),
CONSTRAINT lower_bound_included
CHECK (lower_inc(during)),
CONSTRAINT upper_bound_excluded
CHECK (not upper_inc(during)),
CONSTRAINT start_time_at_15minute_offset
CHECK (EXTRACT(MINUTE FROM lower(during)) IN (0, 15, 30,45)),
-- or (extract(epoch from lower(during)::time)::int % (60*15) = 0)
CONSTRAINT end_time_at_15minute_offset
CHECK (EXTRACT(MINUTE FROM upper(during)) IN (0, 15, 30,45)),
CONSTRAINT duration_between_15min_and_4hours
CHECK (upper(during) - lower(during) between '15 mins'::interval and '4 hours'::interval),
CONSTRAINT exclude_sundays
CHECK (date_part('dow', lower(during)) in (1,2,3,4,5,6) )
);
create or replace function holiday_check() returns trigger language plpgsql stable as $$
begin
if exists (select * from pyha where pyha between lower(NEW.during)::date and upper(NEW.during)::date) then
raise exception 'public holiday %', lower(NEW.during) ;
else
return NEW;
end if;
end;
$$;
create trigger holiday_check_i before insert or update on Reservat for each row execute procedure holiday_check();
INSERT INTO reservat (objekt2, during)
VALUES ('MARY','[2014-10-29 11:30+2,2014-10-29 13:00+2)'::tstzrange);
INSERT INTO reservat (objekt2, during)
VALUES ('JOHN','[2014-10-29 10:00+2,2014-10-29 11:30+2)'::tstzrange);
SELECT yksus2.yksus, times.period
FROM generate_series(now()::date::timestamptz, now()::date::timestamptz + '3 months'::interval, '15 mins'::interval) times(period)
CROSS JOIN yksus2
LEFT JOIN reservat ON tstzrange(times.period,times.period + '1 hour 30 mins'::interval, '[)') && reservat.during
AND yksus2.yksus = reservat.objekt2
LEFT JOIN pyha ON times.period::date = pyha.pyha::date
WHERE reservat.during IS NULL
AND pyha.pyha IS NULL
AND times.period::timetz BETWEEN '10:00'::timetz AND '21:00'::timetz - '1 hour 30 mins'::interval
AND times.period >= now()
AND EXTRACT(isoDOW FROM times.period) != 7 -- exclude sundays
ORDER BY 2, 1
LIMIT 300;