Checking timestamps in a constraint in Oracle - sql

I have created a simple table:
CREATE TABLE Messages
( msgID number(10) PRIMARY KEY,
sender_ID number(10),
time_sent TIMESTAMP,
);
Now I want to add a constraint to it that ensures that time sent will be after the year 2014. I wrote:
alter table Messages
add constraint year_check
check (time_sent > to_timestamp('2014-12-31 23:59:59'));
However I get the following error:
ORA-30075: TIME/TIMESTAMP WITH TIME ZONE literal must be specified in CHECK constraint
I don't want to have a TIME ZONE in my timestamp and have inserted values like this:
INSERT INTO Messages VALUES(1, 1, TIMESTAMP '2014-12-24 07:15:57');
How can I fix my constraint to make this error go away?

When you lookup the error message in the manual you will see the recommendation:
Action: Use time or timestamp with time zone literals only.
to_timestamp('2014-12-31 23:59:59') returns a timestamp (without a time zone), but Oracle requires a timezone with time zone in a check constraint (although I have to admit I don't understand why)
You can either use an ANSI timestamp literal which is resolved to a timestamp with time zone:
alter table Messages
add constraint year_check
check (time_sent > timestamp '2014-12-31 23:59:59');
or use to_timestamp_tz with an explicit time zone:
alter table Messages
add constraint year_check
check (time_sent > to_timestamp_tz('2014-12-31 23:59:59 +00:00', 'YYYY-MM-DD HH:MI:SS TZH:TZM'));
Btw: I would rather change the condition to use >= on the first of January:
alter table Messages
add constraint year_check
check (time_sent >= timestamp '2015-01-01 00:00:00');
Otherwise you could add a row with 2014-12-31 23:59:59.567

Try using a format mask:
select to_timestamp('2014-12-31 23:59:59') timest from dual
2 /
select to_timestamp('2014-12-31 23:59:59') timest from dual
*
ERROR at line 1:
ORA-01843: not a valid month
select to_timestamp('2014-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS') timest from dual
2 /
TIMEST
---------------------------------------------------------------------------
31-DEC-14 11.59.59.000000000 PM

Related

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.

"Not a valid month" or number

Getting this error while trying to put a few inserts into a table.
Getting an error regarding not a valid month and when I try change it around i'm getting invalid number error.
ORA-01843: not a valid month ORA-06512: at "SYS.DBMS_SQL"
Code:
CREATE TABLE ExpenseReport (
ERNo NUMERIC(10) NOT NULL,
ERDesc VARCHAR(255) NOT NULL,
ERSubmitDate DATE DEFAULT CURRENT_TIMESTAMP,
ERStatusDate DATE NOT NULL,
ERStatus VARCHAR(8) DEFAULT 'PENDING',
SubmitUserNo NUMERIC(10) NOT NULL,
ApprUserNo NUMERIC(10) NOT NULL,
CONSTRAINT ExpenseReport_CK1 CHECK (ERStatusDate >= ERSubmitDate),
CONSTRAINT ExpenseReport_CK2 CHECK (ERStatus = 'PENDING'/'APPROVED'/'DENIED'),
CONSTRAINT ExpenseReport_PK1 PRIMARY KEY(ERNo),
CONSTRAINT ExpenseReport_FK1 FOREIGN KEY(SubmitUserNo) REFERENCES Users(UserNo),
CONSTRAINT ExpenseReport_FK2 FOREIGN KEY(ApprUserNo) REFERENCES (USerNo)
);
INSERT INTO ExpenseReport
(ERNo, ERDesc, ERSubmitDate, ERStatusDate, ERStatus, SubmitUserNo, ApprUSerNo)
VALUES (1,'Sales Presentation','8/10/2002','8/26/2002','APPROVED',3,4);
I've also tried using the TO_DATE but having no luck there,
by any chance can anyone see where i'm going wrong.
Use the DATE keyword and standard date formats:
INSERT INTO ExpenseReport (ERNo, ERDesc, ERSubmitDate, ERStatusDate, ERStatus, SubmitUserNo, ApprUSerNo)
VALUES (1, 'Sales Presentation', DATE '2001-08-10', DATE '2001-08-2006', 'APPROVED', 3, 4);
In addition to the satisfaction of using standard date formats, this protects you against changes in local settings.
In your DDL statement:
CONSTRAINT ExpenseReport_CK2 CHECK (ERStatus = 'PENDING'/'APPROVED'/'DENIED')
Should be:
CONSTRAINT ExpenseReport_CK2 CHECK (ERStatus IN ( 'PENDING', 'APPROVED', 'DENIED' ) )
When you are trying to insert values the check constraint is being evaluated and it is trying to perform a division operation on the three string values'PENDING'/'APPROVED'/'DENIED' which results in ORA-01722: invalid number.
Once you change this then using TO_DATE('01/01/02','DD/MM/YY') (as you wrote in comments) or an ANSI date literal DATE '2002-01-01' should work in your DML statements.
(Note: Be careful using 2-digit years or you can find that dates are inserted with the wrong century.)
Check your date format: select sysdate from dual;
and enter as it show. OR
change your date format: alter session set nls_date_format= 'DD-Mon-YYYY HH24:MI:SS';
It Was Easy :
if Your code Like This just remove hem and write that
Example :
Your code : values ('30178','K111', '22/12/2008')
Do This : values ('30178','K111', '22/Dec/2008')

Redshift: creating a table with Timestamp column defaulting to Now()

Is there a way to create a table with a timestamp column defaulting to now?
Any of the following succeeds on creation but fails on insertion.
CREATE TABLE MyTable
(
device_id VARCHAR(64) NOT NULL encode lzo,
app_id VARCHAR(150) NOT NULL distkey encode lzo,
retrieval_date TIMESTAMP without time zone default (now() at time zone 'utc') encode lzo
)
diststyle key
compound sortkey(app_id,device_id);
Then on insert:
insert into MyTable (device_id,app_id) values('a','b');
INFO: Function "timezone(text,timestamp with time zone)" not supported.
INFO: Function "now()" not supported.
ERROR: Specified types or functions (one per INFO message) not supported on Redshift tables.
I tried other flavors as below but all fail.
a) Tried with now in single quotes 'now' , create succeeded but failed with another error
b) Tried without the timezone, create succeeded, insert failed.
You can use SYSDATE or GETDATE() to put a current timestamp value. Here is an example.
dev=> create table my_table (id int, created_at datetime default sysdate);
CREATE TABLE
dev=> insert into my_table (id) values (1);
INSERT 0 1
dev=> select * from my_table;
id | created_at
----+---------------------------
1 | 2016-01-04 19:07:14.18745
(1 row)
SYSDATE (Oracle Compatibility Function)
http://docs.aws.amazon.com/redshift/latest/dg/r_SYSDATE.html
GETDATE()
http://docs.aws.amazon.com/redshift/latest/dg/r_GETDATE.html

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

How to add check constraint on time with a timestamp?

I need to add a constraint that time entered in a column is only after 8:00 am. I've tried
alter table toy_store ADD CHECK (store_opening_time > TIMESTAMP '2013-05-05 08:00:00');
This only allows entry of timestamp greater than 5th May 8:00 am. I want to write a query that checks if the entry is after 8:00 am notwithstanding the date.
you can do it like this :
alter table toy_store ADD CHECK (EXTRACT(HOUR FROM store_opening_time) > 8 || NULL);