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

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

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.

table partioning - keep base and partition childs?

I have a table structure I want to partition by timestamp, in ranges of day.
It consists of two tables which has common fields, to make it simple I will use an example of tables A,B as follows:
create table A (
id serial not null,
ts timestamp without time zone not null,
name text,
some_custom_A_field_1 numeric,
some_custom_A_field_2 numeric
);
create table B (
id serial not null,
ts timestamp without time zone not null,
name text,
some_custom_B_field_1 numeric,
some_custom_B_field_2 numeric,
some_custom_B_field_3 numeric,
some_custom_B_field_4 numeric
);
So I have two options, and I'm not sure which one is preferred.
Option #1 - create base table, inherit, partition childs:
create table Base (
id serial not null,
ts timestamp without time zone not null,
name text
)
create table ABase (
some_custom_A_field_1 numeric,
some_custom_A_field_2 numeric
)INHERITS (Base );
create table BBase (
some_custom_B_field_1 numeric,
some_custom_B_field_2 numeric,
some_custom_B_field_3 numeric,
some_custom_B_field_4 numeric
)INHERITS (Base );
CREATE TABLE a_2018_06_24 (CHECK (ts >= '2018-06-24 00:00:00' AND ts < '2018-06-25 00:00:00')) INHERITS (ABase);
CREATE TABLE b_2018_06_24 (CHECK (ts >= '2018-06-24 00:00:00' AND ts < '2018-06-25 00:00:00')) INHERITS (BBase);
Option #2 - two base tables partitioned:
create table ABase (
id serial not null,
ts timestamp without time zone not null,
name text,
some_custom_A_field_1 numeric,
some_custom_A_field_2 numeric
);
create table BBase (
id serial not null,
ts timestamp without time zone not null,
name text,
some_custom_B_field_1 numeric,
some_custom_B_field_2 numeric,
some_custom_B_field_3 numeric,
some_custom_B_field_4 numeric
);
CREATE TABLE a_2018_06_24 (CHECK (ts >= '2018-06-24 00:00:00' AND ts < '2018-06-25 00:00:00')) INHERITS (ABase);
CREATE TABLE b_2018_06_24 (CHECK (ts >= '2018-06-24 00:00:00' AND ts < '2018-06-25 00:00:00')) INHERITS (BBase);
Additional info:
I'm using version 9.6
Table A approx 2M records per day , Table B approx 120M records per day.
Use cases - ingest data to tables once a day, run select queries (based on time-series).
What will be the better practice, pros / cons?
Thanks in advance.

Oracle: Insert default value 0 into timestamp column

Is it possible to insert a default value of 0 into an oracle timestamp(6) column?
I have a column CS_Date_Time defined as Timestamp(6) NOT NULL. In certain cases it is not valid to insert any value to this column. So instead of changing the definition to allow NULL values and leaving this column as NULL in these cases, I was looking for any alternatives.
0 is a number, not a timestamp. You can use a date which is clearly outside your business rule, e.g 0000-01-01 00:00:00 or 1900-01-01 00:00:00 or 2999-12-31 23:59:59
Of course, it should be only one special value over entire application, these values are just examples.
Example:
CREATE TABLE T
(
END_TIME TIMESTAMP(6) DEFAULT TIMESTAMP '9999-12-31 23:59:59' NOT NULL,
...
);

Checking timestamps in a constraint in Oracle

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

Postgres date overlapping constraint

I have a table like this:
date_start date_end account_id product_id
2001-01-01 2001-01-31 1 1
2001-02-01 2001-02-20 1 1
2001-04-01 2001-05-20 1 1
I want to disallow overlapping intervals a given (account_id, product_id)
EDIT: I found something:
CREATE TABLE test (
from_ts TIMESTAMPTZ,
to_ts TIMESTAMPTZ,
account_id INTEGER,
product_id INTEGER,
CHECK ( from_ts < to_ts ),
CONSTRAINT overlapping_times EXCLUDE USING GIST (
account_id WITH =,
product_id WITH =,
box(
point( extract(epoch FROM from_ts at time zone 'UTC'), extract(epoch FROM from_ts at time zone 'UTC') ),
point( extract(epoch FROM to_ts at time zone 'UTC') , extract(epoch FROM to_ts at time zone 'UTC') )
) WITH &&
)
);
If you want to know more about this http://www.depesz.com/2010/01/03/waiting-for-8-5-exclusion-constraints/
My only problem is that it doesn't work with null values as a ending timestamp, I thought of replace it with infinite values but does not work as well.
Ok i ended up doing this :
CREATE TABLE test (
from_ts TIMESTAMPTZ,
to_ts TIMESTAMPTZ,
account_id INTEGER DEFAULT 1,
product_id INTEGER DEFAULT 1,
CHECK ( from_ts < to_ts ),
CONSTRAINT overlapping_times EXCLUDE USING GIST (
account_id WITH =,
product_id WITH =,
period(from_ts, CASE WHEN to_ts IS NULL THEN 'infinity' ELSE to_ts END) WITH &&
)
);
Works perfectly with infinity, transaction proof.
I just had to install temporal extension which is going to be native in postgres 9.2 and btree_gist available as an extension in 9.1 CREATE EXTENSION btree_gist;
nb : if you don't have null timestamp there is no need to use the temporal extension you could go with the box method as specified in my question.
In up to date postgres versions (I tested it in 9.6 but I assume it's working in >=9.2) you can use the build in function tstzrange() as mentioned in some other comments. Null values will be treated as positive or negative infinity by default and the CHECK contraint is then not explicitly needed anymore (if you are fine that the check is only <= and a range can start and end with the same date). Only the extension btree_gist is still needed:
CREATE EXTENSION btree_gist;
CREATE TABLE test (
from_ts TIMESTAMPTZ,
to_ts TIMESTAMPTZ,
account_id INTEGER DEFAULT 1,
product_id INTEGER DEFAULT 1,
CONSTRAINT overlapping_times EXCLUDE USING GIST (
account_id WITH =,
product_id WITH =,
TSTZRANGE(from_ts, to_ts) WITH &&
)
);
This is a difficult problem because constraints can only reference the "current row", and may not contain subqueries. (otherwise the trivial solution would be to add some NOT EXISTS() subquery in the check)
A check constraint specified as a column constraint should reference that column's value only, while an expression appearing in a table constraint can reference multiple columns.
Currently, CHECK expressions cannot contain subqueries nor refer to variables other than columns of the current row.
Popular work-arounds are: use a trigger function which does the dirty work (or use the rule system, which is deprecated by most people)
Because most people favor triggers, I'll repost a rule-system hack here... (it does not have the extra "id" key element, but that's a minor detail)
-- Implementation of A CONSTRAINT on non-overlapping datetime ranges
-- , using the Postgres rulesystem.
-- We need a shadow-table for the ranges only to avoid recursion in the rulesystem.
-- This shadow table has a canary variable with a CONSTRAINT (value=0) on it
-- , and on changes to the basetable (that overlap with an existing interval)
-- an attempt is made to modify this variable. (which of course fails)
-- CREATE SCHEMA tmp;
DROP table tmp.dates_shadow CASCADE;
CREATE table tmp.dates_shadow
( time_begin timestamp with time zone
, time_end timestamp with time zone
, overlap_canary INTEGER NOT NULL DEFAULT '0' CHECK (overlap_canary=0)
)
;
ALTER table tmp.dates_shadow
ADD PRIMARY KEY (time_begin,time_end)
;
DROP table tmp.dates CASCADE;
CREATE table tmp.dates
( time_begin timestamp with time zone
, time_end timestamp with time zone
, payload varchar
)
;
ALTER table tmp.dates
ADD PRIMARY KEY (time_begin,time_end)
;
CREATE RULE dates_i AS
ON INSERT TO tmp.dates
DO ALSO (
-- verify shadow
UPDATE tmp.dates_shadow ds
SET overlap_canary= 1
WHERE (ds.time_begin, ds.time_end) OVERLAPS ( NEW.time_begin, NEW.time_end)
;
-- insert shadow
INSERT INTO tmp.dates_shadow (time_begin,time_end)
VALUES (NEW.time_begin, NEW.time_end)
;
);
CREATE RULE dates_d AS
ON DELETE TO tmp.dates
DO ALSO (
DELETE FROM tmp.dates_shadow ds
WHERE ds.time_begin = OLD.time_begin
AND ds.time_end = OLD.time_end
;
);
CREATE RULE dates_u AS
ON UPDATE TO tmp.dates
WHERE NEW.time_begin <> OLD.time_begin
AND NEW.time_end <> OLD.time_end
DO ALSO (
-- delete shadow
DELETE FROM tmp.dates_shadow ds
WHERE ds.time_begin = OLD.time_begin
AND ds.time_end = OLD.time_end
;
-- verify shadow
UPDATE tmp.dates_shadow ds
SET overlap_canary= 1
WHERE (ds.time_begin, ds.time_end) OVERLAPS ( NEW.time_begin, NEW.time_end)
;
-- insert shadow
INSERT INTO tmp.dates_shadow (time_begin,time_end)
VALUES (NEW.time_begin, NEW.time_end)
;
);
INSERT INTO tmp.dates(time_begin,time_end) VALUES
('2011-09-01', '2011-09-10')
, ('2011-09-10', '2011-09-20')
, ('2011-09-20', '2011-09-30')
;
SELECT * FROM tmp.dates;
EXPLAIN ANALYZE
INSERT INTO tmp.dates(time_begin,time_end) VALUES ('2011-09-30', '2011-10-04')
;
INSERT INTO tmp.dates(time_begin,time_end) VALUES ('2011-09-02', '2011-09-04')
;
SELECT * FROM tmp.dates;
SELECT * FROM tmp.dates_shadow;
How to create unique constraint to a group of columns :
CREATE TABLE table (
date_start date,
date_end date,
account_id integer,
UNIQUE (account_id , date_start ,date_end) );
in your case you will need to ALTER TABLE if the table already exists, check the documentation it would be helpful for you :
- DDL Constraints
- ALTER Table