PostgreSQL: Rules and serial attributes - postgresql

This is a basic schema of my PostgreSQL database:
create table client(numclient serial not null,
age int not null,
amountDue decimal(10,2) not null default 0.0,
primary key (numclient) );
create table payment( numpayment serial not null,
amountPaid decimal(10,2) not null default 0.0,
numclient int not null,
primary key (numpayment),
foreign key (numclient) references client
on delete cascade );
create or replace rule on_insert_client as on insert to client
do also insert into payment(numclient) values(new.numclient);
This works fine but when I want to insert an element into the table client by doing:
insert into client(age) values (25);
I get this error message:
ERROR: insert or update on table "payment" violates foreign key constraint "payment_numclient_fkey"
DETAIL: Key (numclient)=(2) is not present in table "client".
If I understand well, there is an error because it tries to insert a new row in the table payment before inserting the row in the table client. However, when I use this:
insert into client(numclient, amountDue, age) values (4, 0.0, 25);
It works fine. However, this is quite restrictive because the numclient is not a serial attribute anymore if I have to choose the value of the attribute myself.
So it seems the problem comes from the default serial attribute that creates the value after the execution of the rule. Is there any trick to bypass this problem ?
Thanks

Related

Postgresql: how to create types in postgresql

I am new to postgresql. I want to create types in SQL with below values but got stuck to create the same.
I understand its similar like table but I am unable to figure out the solution.
I want to create following types using postgresql
"Completed", "Pending", "Failed", "Created"
The correct way to do this, is to use a lookup table and a foreign key:
create table status
(
id integer primary key,
name text not null
);
insert into status (id, name)
values
(1, 'Completed'),
(2, 'Pending'),
(3, 'Failed'),
(4, 'Created');
create table some_table
(
id integer primary key,
status_id integer not null references status
);
That is the most flexible way to handle this in a relational database.
If you know that you will hardly ever change those values, you can use a check constraint:
create table some_table
(
id integer primary key,
status text not null,
constraint check_status
status in ('Completed', 'Pending', 'Failed', 'Created')
);
This has the disadvantage that you are storing the same values over and over again, so the size of the table will be bigger compared to the foreign key solution.
The third option is to use an enum type
create type status_type AS ENUM (''Completed', 'Pending', 'Failed', 'Created');
Then use that type in the table:
create table some_table
(
id integer primary key,
status status_type not null
);
This has a similar storage requirement as the foreign key solution but displays the status as "clear text".
CREATE TYPE color AS ENUM ('red', 'green', 'blue');

How to insert a primary/foreign key row in PostgreSQL

I'm populating a database in PostgreSQL for a Newspaper Online. Now my doubt lies on how to insert a value into a table which only attribute is both a primary and a foreign key.
In this context, the admin is the first person to ever register an account. So idAdmin = idA = 1:
CREATE TABLE AUTENTICADO (
idA serial NOT NULL ,
login VARCHAR(45) NOT NULL,
password VARCHAR(45) NOT NULL,
email VARCHAR(40) NOT NULL,
PRIMARY KEY (idA) );
CREATE TABLE ADMIN (
idAdmin INT NOT NULL REFERENCES AUTENTICADO (idA),
PRIMARY KEY (idAdmin) );
It would be logical to insert values into 'ADMIN' as I tried below, although it is obviously not possible considering 'idAdmin' is a primary key (and a foreign key).
INSERT INTO AUTENTICADO VALUES ('john','adadfsfsdfs', 'john#random.com')
INSERT INTO ADMIN VALUES (1)
Is there a way to register that the first user to create an account (idA = 1) is the admin (idAdmin = idA = 1) ?
although it is obviously not possible considering 'idAdmin' is a
primary key (and a foreign key).
So what?
If you fix the first query to list the columns and use a returning clause to get the auto-generated value for the SERIAL ID, it just works:
INSERT INTO AUTENTICADO(login,password,email)
VALUES ('john','adadfsfsdfs', 'john#random.com')
returning idA;
Result:
ida
-----
1
(1 row)
Second query:
insert into admin values(1);
select * from admin;
Result:
idadmin
---------
1

JSON foreign keys in PostgreSQL

Is it possible to assign a foreign key to a json property in PostgreSQL? Here is an example what I would like to achieve, but it doesn't work:
CREATE TABLE Users (Id int NOT NULL PRIMARY KEY);
CREATE TABLE Data (
Id int NOT NULL PRIMARY KEY,
JsonData json NOT NULL, -- [{Id: 1, somedata: null},{Id: 2, somedata: null}, ...]
CONSTRAINT FK_Users_Data FOREIGN KEY (JsonData->Id) REFERENCES Users(Id) -- this constraint will fail
);
It is not possible, and may not ever be possible, to assign a foreign key to a json property. It'd be a major and quite complicated change to PostgreSQL's foreign key enforcement. I don't think it's impossible to do, but would face similar issues to those experienced by the foreign-keys-to-arrays patch.
With 9.4 it'll be possible to make a whole json object a foreign key as jsonb supports equality tests. In 9.3 you can't even do that.
Here's a little SPI function have_ids which I use for an integrity constraint on a one-to-many relationship with a jsonb column
CREATE TABLE foo (
id INTEGER NOT NULL
)
CREATE TABLE bar (
foo_ids pg_catalog.jsonb DEFAULT '[]'::jsonb NOT NULL,
CONSTRAINT bar_fooids_chk CHECK (have_ids ('foo', foo_ids))
)
With a couple of triggers on foo it's almost as good as a foreign key.
The foreign key parameter must be a column name:
http://www.postgresql.org/docs/current/static/sql-createtable.html
You will have to normalize
create table user_data (
id int not null primary key,
user_id int not null,
somedata text,
constraint fk_users_data foreign key (user_id) references Users(Id)
);
Yes it is possible but you will have to store another value. If you change your schema to:
CREATE TABLE Users (Id int NOT NULL PRIMARY KEY);
CREATE TABLE Data (
Id int NOT NULL PRIMARY KEY,
JsonData json NOT NULL,
UserId int generated always as ((JsonData->>'Id')::int) stored references Users(Id)
);
INSERT INTO Users VALUES (1);
Foreign key that doesn't exist:
INSERT INTO Data VALUES (1, '{"Id": 3}');
Returns the error:
ERROR: insert or update on table "data" violates foreign key constraint "data_userid_fkey" DETAIL: Key (userid)=(3) is not present in table "users".
Foreign key that does work:
INSERT INTO Data VALUES (1, '{"Id": 1}');

How to insert value into a column with a default value? [PostgreSQL 9.1]

I have such table:
CREATE TABLE employee (
id INTEGER DEFAULT NEXTVAL('ids'::regclass) NOT NULL,
name CHARACTER VARYING NOT NULL,
employer INTEGER DEFAULT (-1)
);
And I want to insert sth into this table (I want to leave employer as default, -1):
INSERT INTO employee (name, id) VALUES('Doe', 2);
but my PostgreSQL 9.1 is complaining:
ERROR: insert or update on table "employee" violates foreign key constraint "FK_employer"
DETAIL: Key (employer)=(-1) is not present in table "employer".
I know that theres no employer with id = -1 but still, I want it that way. I want to set employer as -1 for this emplyee. Is it possible with postgreSQL?
Make the default null. Is it good?
employer INTEGER DEFAULT null

PostgreSQL foreign key not existing, issue of inheritance?

I am struggling with foreign keys in my DB, possibly it has something to do with inheritance?
So here's the basic setup:
-- table address
CREATE TABLE address
(
pk_address serial NOT NULL,
fk_gadmid_0 integer NOT NULL, -- this table already exists, no problem here
street character varying(100),
zip character varying(10),
city character varying(50),
public boolean,
CONSTRAINT address_primarykey PRIMARY KEY (pk_address),
CONSTRAINT gadmid_0_primarykey FOREIGN KEY (fk_gadmid_0)
REFERENCES adm0 (gadmid_0) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE address OWNER TO postgres;
-- table stakeholder (parent)
CREATE TABLE stakeholder
(
pk_stakeholder integer DEFAULT nextval('common_stakeholder_seq') NOT NULL,
fk_stakeholder_type integer NOT NULL, -- this table also exists, no problem here
name character varying(255) NOT NULL,
CONSTRAINT stakeholder_primarykey PRIMARY KEY (pk_stakeholder),
CONSTRAINT stakeholder_fk_stakeholder_type FOREIGN KEY (fk_stakeholder_type)
REFERENCES stakeholder_type (pk_stakeholder_type) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE stakeholder OWNER TO postgres;
-- table individual (child of stakeholder)
CREATE TABLE individual
(
firstname character varying(50),
fk_title integer, -- this table also exists, no problem here
email1 character varying (100),
email2 character varying (100),
phone1 character varying (50),
phone2 character varying (50),
CONSTRAINT individual_primarykey PRIMARY KEY (pk_stakeholder),
CONSTRAINT title_foreignkey FOREIGN KEY (fk_title)
REFERENCES title (pk_title) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
) INHERITS (stakeholder)
WITH (
OIDS=FALSE
);
ALTER TABLE individual OWNER TO postgres;
-- link between stakeholder and address
CREATE TABLE l_stakeholder_address
(
pk_l_stakeholder_address serial NOT NULL,
fk_stakeholder integer NOT NULL REFERENCES stakeholder,
fk_address integer NOT NULL REFERENCES address,
CONSTRAINT l_stakeholder_address_primarykey PRIMARY KEY (pk_l_stakeholder_address),
CONSTRAINT l_stakeholder_address_fk_stakeholder FOREIGN KEY (fk_stakeholder)
REFERENCES stakeholder (pk_stakeholder) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION,
CONSTRAINT l_stakeholder_address_fk_address FOREIGN KEY (fk_address)
REFERENCES address (pk_address) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE l_stakeholder_address OWNER TO postgres;
So far, no problem. Then I tried to add some values:
INSERT INTO individual (pk_stakeholder, fk_stakeholder_type, name, firstname, fk_title, email1, email2, phone1, phone2)
VALUES (1, 8, 'Lastname', 'Firstname', 1, 'me#you.com', '', '', '');
INSERT INTO address (pk_address, fk_gadmid_0, street, zip, city, public)
VALUES (1, 126, 'Address', '', 'City', FALSE);
INSERT INTO l_stakeholder_address (pk_l_stakeholder_address, fk_stakeholder, fk_address)
VALUES (DEFAULT, 1, 1);
And finally I end up having an error (SQL state 23503) saying that the key (fk_stakeholder)=(1) is not existing in table "stakeholder".
The first 2 inserts are fine, I can see them in the databases:
stakeholder:
pk_stakeholder | ...
----------------------
1 | ...
address:
pk_address | ...
--------------------
1 | ...
What am I doing wrong? I must admit that I am rather new to PostgreSQL (using 8.4) but I'm not even sure if that is an issue of PG at all, maybe I'm just lacking some basic database design understandings ...
Either way, by now I tried pretty much everything I could think of, I also tried to make the FK deferrable as in PostgreSQL : Transaction and foreign key problem but somehow that doesn't work either.
You can work around it using additional table individual_pks (individual_pk integer primary key) with all primary keys from both parent and child, which will be maintained using triggers (very simple — insert to individual_pks on insert, delete from it on delete, update it on update, if it changes individual_pk).
Then you point foreign keys to this additional table instead of a child. There'll be some small performance hit, but only when adding/deleting rows.
Or forget inheritance and do it the old way - simply one table with some nullable columns.
Your analysis is exactly right: It's because of the inheritance. When checking the foreign key, child tables are not considered.
In general, inheritance and foreign keys don't mix well in PostgreSQL. A major problem is that you can't have unique constraints across tables.
Reference