Subtract two timestamptz values and insert the result into a third column - sql

I have the following table:
CREATE TABLE duration
(
departure_time TIMESTAMPTZ,
arrival_time TIMESTAMPTZ,
duration TIME NOT NULL, -- Not sure about the datatype..
flight_id INT UNIQUE NOT NULL,
CHECK (scheduled_duration > 0),
CHECK (scheduled_arrival_time > scheduled_departure_time),
FOREIGN KEY (flight_id) REFERENCES flight(flight_id),
PRIMARY KEY (scheduled_departure_time, scheduled_arrival_time)
);
I want to calculate arrival_time - departure_time and then insert the result into the column duration. Preferably, the result of the duration subtraction would be 6h 30m. I am new to databases and PostgreSQL and I can't find a way to calculate a subtraction of two timestamps, taking into consideration their timezones at the same time.

Use a generated column
CREATE TABLE duration
(
departure_time TIME WITH TIME ZONE,
arrival_time TIME WITH TIME ZONE,
scheduled_duration INT,
flight_id INT,
duration2 TIME GENERATED ALWAYS AS ("arrival_time"::time - "departure_time"::time) STORED,
CHECK (scheduled_duration > 0),
CHECK (arrival_time > departure_time),
FOREIGN KEY (flight_id) REFERENCES flight(flight_id),
PRIMARY KEY (departure_time, arrival_time)
);

SELECT
EXTRACT(EPOCH FROM '2022-07-07 15:00:00.00000'::TIMESTAMP - '2022-07-07 15:00:00.00000'::TIMESTAMP)

Related

CUSTOM UNIQUE CHECK POSTGRES

I am working on a task where I need to store the interviewer's time slot in the table INTERVIEW_SLOT. The table schema is like this:
CREATE TABLE INTERVIEW_SLOT (
ID SERIAL PRIMARY KEY NOT NULL,
INTERVIEWER INTEGER REFERENCES USERS(ID) NOT NULL,
START_TIME TIMESTAMP NOT NULL, -- start time of interview
END_TIME TIMESTAMP NOT NULL, -- end time of interview
IS_BOOKED BOOL NOT NULL DEFAULT 'F', -- slot is booked by any candidate or not
CREATED_ON TIMESTAMP,
-- interviewer can't give the same slot twice
CONSTRAINT UNIQUE_INTERVIEW_SLOT UNIQUE (start_time, INTERVIEWER)
);
We want to ensure that the interviewer can not give the same slot twice but the problem is with second and millisecond values of start_time. I want the UNIQUE_INTERVIEW_SLOT constant like this:
UNIQUE_INTERVIEW_SLOT UNIQUE(TO_TIMESTAMP(start_time::text, 'YYYY-MM-DD HH24:MI'), INTERVIEWER)
Is there any way to add a unique constraint that ignores the second and millisecond value?
You are looking for an exclusion constraint
create table interview_slot
(
id integer primary key generated always as identity,
interviewer integer references users(id) not null,
start_time timestamp not null, -- start time of interview
end_time timestamp not null, -- end time of interview
is_booked bool not null default 'f', -- slot is booked by any candidate or not
created_on timestamp,
constraint unique_interview_slot
exclude using gist (interviewer with =,
tsrange(date_trunc('minute', start_time), date_trunc('minute', end_time), '[]') with &&)
);
This prevents rows with overlapping start/end ranges for the same interviewer. The timestamps are "rounded" to the full minute. You need the extension btree_gist in order to create that constraint.
You can use an UNIQUE INDEX to make this check for you and truncate the timestamp to minutes:
CREATE UNIQUE INDEX idx_interview_slot_ts
ON interview_slot (interviewer, date_trunc('minutes',start_time));
Demo: db<>fiddle

Timezone aware uniqueness constraint

I am working with a timesheet app, used by users from multiple timezones. I am trying to introduce a unique constraint, that only allows users to clock_in or clock_out once per day in the local timezone.
Please refer to the following table declaration:
Table "public.entries"
---------------------------------------------
Column | Type |
---------------------------------------------
id | bigint |
user_id | bigint |
entry_type | string | enum(clock_in, clock_out)
created_at | timestamp(6) without time zone |
But little lost on how to handle the timezone-aware uniqueness.
Update:
I am considering 0:00 hrs to 23:55 hrs of local time zone as day.
User's timezone is stored in the users table but can move to the entries table if it helps with constraints.
I misread the question and wrote a bad answer, so here's a new one...
I assume this is a typical client-server-db setup. You need to obtain the local time zone from the client that's clocking in/out the user; Postgres doesn't know what it is. We'll figure out the user's local date from that and store it. Then we'll have a uniqueness index on <user, local date>.
I thought there'd be fancier ways to do this by storing the timestamptz with a separate time zone col and calculating the date within the uniqueness index, but Postgres doesn't allow us to use date_trunc within an index. So we're going to denormalize just a little and make things a lot easier with this additional date col.
CREATE TABLE clock_in (
user_id bigint NOT NULL,
created_at timestamptz NOT NULL, -- stores microseconds since epoch
local_date date NOT NULL, -- stores the <year, month, day> in whatever timezone the user clocked in from
-- optional for bookkeeping purposes: time_zone text NOT NULL,
UNIQUE(user_id, local_date)
);
Take a look at the official date/time type docs for further explanation of the above. IMO you shouldn't rely on DB constraints to reject bad user input. They're more of a second line of defense meant to ensure a self-consistent database. First your server should query the last clock-in and error out if it was in the same day, and also error if there was no clock-in that day. You'll be able to yield more useful error messages that way. Then you can insert...
INSERT INTO clock_in(user_id, created_at, local_date) (
SELECT 1, now(),
(date_trunc('day', now() AT TIME ZONE 'insert_users_timezone_here'))::date
);
Usage example for a client who has indicated it's in the PST timezone:
me=# CREATE TABLE clock_in ( user_id bigint NOT NULL, created_at timestamptz NOT NULL, local_date date NOT NULL, UNIQUE(user_id, local_date) );
CREATE TABLE
me=# INSERT INTO clock_in(user_id, created_at, local_date) ( SELECT 1, now(), (date_trunc('day', now() AT TIME ZONE 'PST'))::date );
INSERT 0 1
me=# INSERT INTO clock_in(user_id, created_at, local_date) ( SELECT 1, now(), (date_trunc('day', now() AT TIME ZONE 'PST'))::date );
ERROR: duplicate key value violates unique constraint "clock_in_user_id_local_date_key"
DETAIL: Key (user_id, local_date)=(1, 2022-04-13) already exists.
me=# INSERT INTO clock_in(user_id, created_at, local_date) ( SELECT 1, now(), (date_trunc('day', now() AT TIME ZONE 'PST' + interval '10' hour))::date );
INSERT 0 1
me=#
Then you'd do the same for clock-outs.
Using timestamptz instead of timestamp is deliberate. You should almost never use timestamp, for reasons other answers describe well.
Firstly, you'll probably want to use a native datetime datatype and a range one at that, e.g. tstzrange (with timezone) / tsrange (without timezone) – they allow you to natively store a start and end time – see https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-BUILTIN
You can optionally add an exclusion constraint to ensure no two shifts overlap – see: https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-CONSTRAINT if that's all you really want to ensure, then that might be enough.
If you definitely want to ensure there's only one shift starting or ending per day, you can use a function to derive a unique index:
create unique index INDEX_NAME on TABLE_NAME (date_trunc('day', lower(column_name)))
For your example specifically:
create unique index idx_unique_shift_start_time on entries (user_id, date_trunc('day', lower(active_during)))
create unique index idx_unique_shift_end_time on entries (user_id, date_trunc('day', upper(active_during)))
These two indexes take the lower or upper bounds of the range (i.e. the start time or end time), then truncate to just the day (i.e. drop the hours, minutes, seconds etc) and then combine with the user_id to give us a unique key.

How to specify my table to insert data as time in minutes and second into a column?

I have been struggling for several hours now and I have not been able to find the solution to my problem. This is an assignment, but I am stuck in this part.
CREATE TABLE Trip
(
Trip_Id SERIAL,
Origin VARCHAR(50) NOT NULL,
Destination VARCHAR(50) NOT NULL,
Date_Time_Picked TIMESTAMP NOT NULL DEFAULT CURRENT_DATE,
Estimated_Time TIME NOT NULL DEFAULT CURRENT_TIME,
Price DECIMAL NOT NULL,
PRIMARY KEY(Trip_Id)
);
INSERT INTO Trip (Origin, Destination, Estimated_Time, Price )
VALUES ('Hialeah' ,'Miami Beach', 30:00, 40.00);
The insert statement in postgreSQL shows a error because the time format. The column Estimated_Time is supposed to store the time in minutes and seconds, but the compiler shows an error because interprets 30:00 as hours and seconds. How can I handle the input of the user to save 30:00 as 30 minutes and 0 seconds. The Trip table can be modified, obviously, the insert statement requires a conversion or cast from '30:00' to Time type, but I am lost in how to do it. Unfortunately, books do not explain how this is done. I would greatly appreciate any hint or example. Thanks in advance.
as pointed out by a_horse_with_no_name and jarlh,
Estimated_Time is the duration of the trip, so the format should be interval
CREATE TABLE trip (
trip_id SERIAL,
origin VARCHAR(50) NOT NULL,
destination VARCHAR(50) NOT NULL,
date_time_picked TIMESTAMP WITHOUT TIME ZONE DEFAULT 'now'::text::date NOT NULL,
estimated_time INTERVAL,
price NUMERIC NOT NULL,
CONSTRAINT trip_pkey PRIMARY KEY(trip_id)
)
and the insert sould be
INSERT INTO Trip (Origin, Destination, Estimated_Time, Price )
VALUES ('Hialeah' ,'Miami Beach', '00:30:00', 40.00);

Constraint to check if time greater than 9 am

How to check time entry only so that any entry before is not acceptable?
CREATE TABLE demo.event(
ecode CHAR(4) UNIQUE NOT NULL PRIMARY KEY,
edesc VARCHAR(20) NOT NULL,
elocation VARCHAR(20) NOT NULL,
edate DATE NOT NULL
CONSTRAINT date_check CHECK(edate BETWEEN '2016/04/01' AND '2016/04/30'),
etime TIME NOT NULL
CONSTRAINT time_check CHECK(etime (HOUR > '08:00:00')),
emax SMALLINT NOT NULL
CONSTRAINT emax_check CHECK(emax >=1 OR emax<=1000)
);
The above code gave me an error
ERROR: column "hour" does not exist
To write a time literal, you need to use the keyword time not hour:
CONSTRAINT time_check CHECK(etime > TIME '08:00:00'),
so any entry before 9:00 am not acceptable.
contradicts the 08:00:00' you have used, > TIME '08:00:00' will allow 08:00:01 (one second after 8am). If you really only want to allowed values after 9am, then use:
CONSTRAINT time_check CHECK(etime > TIME '09:00:00'),
You should also use proper ISO formatted dates to avoid any ambiguity:
CONSTRAINT date_check CHECK(edate BETWEEN DATE '2016-04-01' AND DATE '2016-04-30')
More details on the proper syntax for date and time literals can be found in the manual:
http://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-INPUT

ORA-00984 Column not allowed here with date in oracle SQL

Im inserting values into this table
CREATE TABLE Flight (
FlightNumber char(7) primary key,
ArrivalAirportCode char(6) references Airport (Airport_code),
DepartureAirportCode char(6) references Airport (Airport_code),
AircraftNumber varchar2(25) references Aircraft (AircraftNumber),
ArrivalDate date,
ArrivalTime Varchar2(5),
DepartureDate date,
DepartureTime varchar2(5)
);
and here are the values Im inserting into it
INSERT INTO FLIGHT values
('CA3048',
'LHR',
'EDI',
'N859E',
'14-NOV-2014',
'22:15',
'14-NOV-2014',
'20:15');
And I get the column not allowed here error for the 2nd date I insert, but not the first one. I've tried putting quotes around the date but I just get another error.
'14-NOV-2014'
Why are you inserting a string in a DATE column? '14-NOV-2014' is a STRING and NOT a DATE. You should not depend on implicit data type conversion.
Always, convert the string into a DATE explicitly using TO_DATE and proper format mask.
For example,
TO_DATE('14-NOV-2014','DD-MON-YYYY')
One more thing,
DepartureTime varchar2(5)
Makes no sense. You already have a DATE column, a DATE would have the time element too.
No need of a separate time column. A DATE has both date and time elements stored in 7 bytes.
Oracle stores DATE in total of 7 bytes. Each byte in it stores values for an element of the DATE as follows:
Byte Description
---- ------------------------------------------------
1 Century value but before storing it add 100 to it
2 Year and 100 is added to it before storing
3 Month
4 Day of the month
5 Hours but add 1 before storing it
6 Minutes but add 1 before storing it
7 Seconds but add 1 before storing it
All you need to do is just have 2 DATE columns:
CREATE TABLE Flight (
FlightNumber char(7) primary key,
ArrivalAirportCode char(6) references Airport (Airport_code),
DepartureAirportCode char(6) references Airport (Airport_code),
AircraftNumber varchar2(25) references Aircraft (AircraftNumber),
ArrivalDate date,
DepartureDate date
);
And then insert the values as:
INSERT INTO FLIGHT values
('CA3048',
'LHR',
'EDI',
'N859E',
TO_DATE('14-NOV-2014 22:15:00','DD-MON-YYYY HH24:MI:SS'),
TO_DATE('14-NOV-2014 20:15:00','DD-MON-YYYY HH24:MI:SS')
);
Update
As mentioned in the comments by #GriffeyDog and #a_horse_with_no_name.
Alternatively, you could also the ANSI literal instead, for example:
timestamp '2014-11-14 22:15'