Timetravel in postgres - violating PRIMARY KEY constraint - postgresql

I wanted to use timetravel function (F.39. spi, PostgreSQL 9.1 Documentation) in my application, however it doesn't seem to work properly for me. With inserting rows into table everything works just fine, I get start and stop date properly, but when I'm trying to update those rows postgres gives me error about violating of PRIMARY KEY constraint. He's trying to insert a tuple with the same primary id as previous tuple...
It's insane to remove primary key constraints from all tables in the database but it's the functionality I need. So maybe you have some expierience with timetravel?
Any sort of help will be appreciated. Thanks in advance.
DDL:
CREATE TABLE cities
(
city_id serial NOT NULL,
state_id integer,
name character varying(80) NOT NULL,
start_date abstime,
stop_date abstime,
CONSTRAINT pk_cities PRIMARY KEY (city_id ),
CONSTRAINT fk_cities_states FOREIGN KEY (state_id)
REFERENCES states (state_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
-- Trigger: time_travel on cities
-- DROP TRIGGER time_travel ON cities;
CREATE TRIGGER time_travel
BEFORE INSERT OR UPDATE OR DELETE
ON cities
FOR EACH ROW
EXECUTE PROCEDURE timetravel('start_date', 'stop_date');
STATEMENT GIVEN:
INSERT INTO cities(
state_id, name)
VALUES (20,'Paris');
and that's ok. I get start_date and stop_date.
But by:
UPDATE cities SET name='Rome' WHERE name='Paris'
I get error- described earlier.
Schema of states
-- Table: states
-- DROP TABLE states;
CREATE TABLE states
(
state_id serial NOT NULL, -- unikatowy numer wojewodztwa
country_id integer, -- identyfikator panstwa, w ktorym znajduje sie wojewodztwo
name character varying(50), -- nazwa wojewodztwa
CONSTRAINT pk_states PRIMARY KEY (state_id ),
CONSTRAINT uq_states_state_id UNIQUE (state_id )
)
WITH (
OIDS=FALSE
);
Unfortunately,as a new user I'm not allowed to post images here.
You can see them there:
Sample data from table cities: korpusvictifrew.cba.pl/postgres_cities.png
Sample data from table states: korpusvictifrew.cba.pl/states_data.png

Time travel converts an UPDATE into an UPDATE of the old record's stop_date and an INSERT of a new one with the changed data plus an infinity stop_date. You can't have more than one record for city_id due to pk_cities. The time travel triggers do not allow you to break that requirement.
You cannot use this:
CONSTRAINT pk_cities PRIMARY KEY (city_id )
You must use this
CONSTRAINT pk_cities PRIMARY KEY (city_id, stop_date)

Related

Change PRIMARY KEY CONSTRAINT in PostgreSql using Play Framework Evolutions

I need to change a PRIMARY KEY CONSTRAINT in PostgreSql from a single to a composite key, using Scala's Play Framework Evolutions. This Change Primary Key post gives me a good head start, so I will adapt their example; suppose my db evolutions 1.sql file is:
-- !Ups
CREATE TABLE city (
city_id BIGSERIAL PRIMARY KEY,
group_id int,
"version" int
);
CREATE INDEX city__version ON city("version");
CREATE INDEX city__group_id ON city(group_id);
-- !Downs
DROP TABLE city;
I want to change the PK to include 2 more columns, like:
CREATE TABLE city (
city_id BIGSERIAL,
group_id int,
"version" int,
CONSTRAINT city_pk PRIMARY KEY (city_id, group_id, "version");
);
-- ...
I think the 2.sql file should look like:
-- !Ups
ALTER TABLE city
DROP COLUMN city_id,
ADD COLUMN city_id BIGSERIAL,
ADD CONSTRAINT city_pk PRIMARY KEY (city_id, group_id, "version");
-- !Downs
ALTER TABLE city
DROP CONSTRAINT city_pk,
DROP COLUMN city_id,
ADD COLUMN city_id BIGSERIAL PRIMARY KEY;
But I wonder if I need to truncate the table first*, since I'm dropping an essential column... If so, should I include a TRUNCATE command to the evolution file before the ALTER TABLE command? Otherwise, is there another way to change the PRIMARY KEY w/o DROP + ADD COLUMN?
* it's OK to do it on my use case.
Just tested my suggested changes and it worked. The 2.sql file is the same as suggested; there was no need to truncate the table first, and the original data was preserved, including the city_id:
-- !Ups
ALTER TABLE city
DROP COLUMN city_id,
ADD COLUMN city_id BIGSERIAL,
ADD CONSTRAINT city_pk PRIMARY KEY (city_id, group_id, "version");
-- !Downs
ALTER TABLE city
DROP CONSTRAINT city_pk,
DROP COLUMN city_id,
ADD COLUMN city_id BIGSERIAL PRIMARY KEY;

Not able to create a foreign key; Relation " " does not exist

I am trying to create a foreign key called 'user_id' for a 'transactions' table where the user_id references the 'user_accounts' table 'id' column. I keep getting an error when I execute the script that says:
SQL Error [42P01]: ERROR: relation "user_accounts" does not exist
The table clearly exists as I have been populating the user_accounts table with data that can be viewed in dbeaver. I am using Postgres and I am aware that quotes/capitalization can really make things difficult but I have executed my entire script without capitalizing or using quotes on any of the table or column names. Although, I did capitalize some of my column data types and I am wondering if that is the issue here? If so, what direction should I take to get my foreign key to work?
My script:
create table if not exists user_accounts (
id serial primary key,
first_name VARCHAR(30),
last_name VARCHAR(30),
username VARCHAR(20),
password VARCHAR(20),
deposit INT,
creditScore INT
)
create table if not exists transactions (
transaction_id serial primary key,
user_id INT references user_accounts(id) not null,
transaction_type VARCHAR(20),
amount INT
)

How to restrict values in a column based on values from another column in PostgreSQL?

I would like to prevent a mismatch between course_code and course_namewhen inserting values to the table below.
CREATE TABLE course (
course_id INT4 NOT NULL PRIMARY KEY,
course_code CHAR(4) NOT NULL,
course_name VARCHAR(30) NOT NULL
);
For both I created an enumeration (see below), now I want to link 'C101' to 'Computer Science' etc.
CREATE TYPE e_course_code AS ENUM (
'C101',
'B102',
'E103',
'V104',
'A105',
'E104'
);
CREATE TYPE e_course_name AS ENUM (
'Computer Science',
'Business Information Management',
'Electronics',
'Visual Programming',
'Audio Technology',
'Engineering'
);
Is it possible to link specified (enumerated) values for two (or even more) columns? Something that returns an error message when inserting a course_code and course_name that do not match?
The fast and reliable tool to implement what you ask in the title would be a foreign key constraint with MATCH FULL:
CREATE TABLE course (
course_code char(4) PRIMARY KEY
, course_name text NOT NULL
);
CREATE TABLE some_other_table (
some_other_id serial PRIMARY KEY
, course_code char(4)
, course_name text
, -- more columns
, CONSTRAINT course_fk FOREIGN KEY (course_code, course_name)
REFERENCES course(course_code, course_name) MATCH FULL
);
Related:
MATCH FULL vs MATCH SIMPLE in foreign key constraints
However, some_other_table.course_name would be completely redundant, and the clean implementation would be the normalized form instead:
CREATE TABLE some_other_table (
some_other_id serial PRIMARY KEY
, course_code char(4)
, -- more columns
, CONSTRAINT course_fk FOREIGN KEY (course_code) REFERENCES course(course_code)
);
Or you add course_id as PK to the course table and use that as FK column.
You can always add a VIEW to display course_name additionally.
The simplest way to solve this (as i see it) would make two separate tables- one with id and code, another with code and name. See this question - Difference between 3NF and BCNF in simple terms (must be able to explain to an 8-year old) - the example in the answer is similar to your problem.

How do I create a check to make sure a value exists in another table?

Right now I have two tables, one that contains a compound primary key and another that that references one of the values of the primary key but is a one-to-many relationship between Product and Mapping. The following is an idea of the setup:
CREATE TABLE dev."Product"
(
"Id" serial NOT NULL,
"ShortCode" character(6),
CONSTRAINT "ProductPK" PRIMARY KEY ("Id")
)
CREATE TABLE dev."Mapping"
(
"LookupId" integer NOT NULL,
"ShortCode" character(6) NOT NULL,
CONSTRAINT "MappingPK" PRIMARY KEY ("LookupId", "ShortCode")
)
Since the ShortCode is displayed to the user as a six character string I don't want to have a another table to have a proper foreign key reference but trying to create one with the current design is not allowed by PostgreSQL. As such, how can I create a check so that the short code in the Mapping table is checked to make sure it exists?
Depending on the fine print of your requirements and your version of Postgres I would suggest a TRIGGER or a NOT VALID CHECK constraint.
We have just discussed the matter in depth in this related question on dba.SE:
Disable all constraints and table checks while restoring a dump
If I understand you correctly, you need a UNIQUE constraint on "Product"."ShortCode". Surely it should be declared NOT NULL, too.
CREATE TABLE dev."Product"
(
"Id" serial NOT NULL,
"ShortCode" character(6) NOT NULL UNIQUE,
CONSTRAINT "ProductPK" PRIMARY KEY ("Id")
);
CREATE TABLE dev."Mapping"
(
"LookupId" integer NOT NULL,
"ShortCode" character(6) NOT NULL REFERENCES dev."Product" ("ShortCode"),
CONSTRAINT "MappingPK" PRIMARY KEY ("LookupId", "ShortCode")
);
Your original "Product" table will allow this INSERT statement to succeed, but it shouldn't.
insert into dev."Product" ("ShortCode") values
(NULL), (NULL), ('ABC'), ('ABC'), ('ABC');
Data like that is just about useless.
select * from dev."Product"
id ShortCode
--
1
2
3 ABC
4 ABC
5 ABC

parent and child table foreign key

I currently have a parent table:
CREATE TABLE members (
member_id SERIAL NOT NULL, UNIQUE, PRIMARY KEY
first_name varchar(20)
last_name varchar(20)
address address (composite type)
contact_numbers varchar(11)[3]
date_joined date
type varchar(5)
);
and two related tables:
CREATE TABLE basic_member (
activities varchar[3]) // can only have 3 max activites
INHERITS (members)
);
CREATE TABLE full_member (
activities varchar[]) // can 0 to many activities
INHERITS (members)
);
I also have another table:
CREATE TABLE planner (
day varchar(9) FOREIGN KEY REFERENCES days(day)
time varchar(5) FOREIGN KEY REFERENCES times(time)
activity varchar(20) FOREIGN KEY REFERENCES activities(activity)
member bigint FOREIGN KEY REFERENCES members(member_id)
);
ALTER TABLE planner ADD CONSTRAINT pk_planner PRIMARKY KEY (day,time,activity,member);
I am currently trying to add with
INSERT INTO planner VALUES ('monday','09:00','Weights',2);
I have added a set into full_members with
INSERT INTO full_members
VALUES (Default, 'Hayley', 'Sargent', (12,'Forest Road','Mansfield','Nottinghamshire','NG219DX'),'{01623485764,07789485763,01645586754}',20120418,'Full');
My insert into Planner is currently not working — can you explain why?
i managed ot answer my own question it was becuase at the moment posgreSQL doesn't work very well with inheritence and foreign keys, so i have ot create a rule
CREATE RULE member_ref
AS ON INSERT TO planner
WHERE new.member NOT IN (SELECT member_id FROM members)
DO INSTEAD NOTHING;
this is basically the same as a foreign key
Not sure if this will be better solution but here it goes...
The principle is quite simple:
create new table lets call it table_with_pkeys which will replicate primary key column(s) of inherited tables child1, child2, child3...
create triggers on inherited tables, after insert, insert new PK into table_with_pkeys newly created PK, after update if it changes update it and after delete delete the same PK from table_with_pkeys.
Then in every table which should reference child1, child2 or whichever through parent table's PK using FK, point that FK not to parent's PK, but to table_with_pkeys which has copies of all childs PK's, and so you will have easy manageable way to have foreign keys that can cascade updates, restrict updates and so on.
Hope it helps.
You are missing an open quote before the 12 in the address:
INSERT INTO full_members
VALUES (Default, 'Hayley', 'Sargent', (12 Forest Road', 'Mansfield', 'Nottinghamshire', 'NG219DX'),
'{01623485764,07789485763,01645586754}',20120418,'Full');
should be:
INSERT INTO full_members
VALUES (Default, 'Hayley', 'Sargent', ('12 Forest Road', 'Mansfield', 'Nottinghamshire', 'NG219DX'),
'{01623485764,07789485763,01645586754}',20120418,'Full');
If the materialized view approach doesn't work for you above, create constraint triggers to check the referential integrity. Unfortunately declarative referential integrity doesn't work well with inheritance at present.