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

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

Related

How to use timescale hypertables with foreign keys and keep a one-to-many relation?

I am trying to create a database with minimum redundancy in mind. We would like to use the timescaledb hypertables (I run postgreSQL v. 12 and timescaledb v. 1.7.4). The postgreSQL code to create the tables are as follows - you can see the dbdiagram here https://dbdiagram.io/d/5f992f0e3a78976d7b797ca2 or view the tables here Image of database
CREATE TABLE "datapoints" (
"id" bigserial UNIQUE NOT NULL,
"tstz" timestamptz NOT NULL,
"entity_id" bigint NOT NULL,
"value" real NOT NULL,
PRIMARY KEY ("tstz", "entity_id")
);
CREATE TABLE "datapoint_quality" (
"tstz" timestamptz NOT NULL,
"datapoint_id" bigint NOT NULL,
"flag_id" bigint NOT NULL,
PRIMARY KEY ("tstz", "datapoint_id", "flag_id")
);
CREATE TABLE "quality_flags" (
"id" bigserial PRIMARY KEY,
"value" text
);
CREATE TABLE "sensor_types" (
"id" bigserial PRIMARY KEY,
"name" text UNIQUE NOT NULL
);
CREATE TABLE "sensors" (
"tstz" timestamptz NOT NULL DEFAULT (now()),
"id" bigserial UNIQUE NOT NULL,
"name" text NOT NULL,
"parent" bigint NOT NULL,
"type" bigint NOT NULL,
PRIMARY KEY ("tstz", "id")
);
CREATE TABLE "datapoint_annotation" (
"tstz" timestamptz NOT NULL,
"datapoint_id" bigint NOT NULL,
"annotation_id" bigint NOT NULL,
PRIMARY KEY ("tstz", "datapoint_id", "annotation_id")
);
CREATE TABLE "annotations" (
"id" bigserial PRIMARY KEY NOT NULL,
"value" text NOT NULL
);
ALTER TABLE "datapoints" ADD FOREIGN KEY ("entity_id") REFERENCES "sensors" ("id");
ALTER TABLE "datapoint_quality" ADD FOREIGN KEY ("datapoint_id") REFERENCES "datapoints" ("id");
ALTER TABLE "datapoint_quality" ADD FOREIGN KEY ("flag_id") REFERENCES "quality_flags" ("id");
ALTER TABLE "sensors" ADD FOREIGN KEY ("parent") REFERENCES "sensors" ("id");
ALTER TABLE "sensors" ADD FOREIGN KEY ("type") REFERENCES "sensor_types" ("id");
ALTER TABLE "datapoint_annotation" ADD FOREIGN KEY ("datapoint_id") REFERENCES "datapoints" ("id");
ALTER TABLE "datapoint_annotation" ADD FOREIGN KEY ("annotation_id") REFERENCES "annotations" ("id");
CREATE UNIQUE INDEX ON "quality_flags" ("value");
CREATE UNIQUE INDEX ON "annotations" ("value");
So far so good - next I want to create the hypertables, which I do as:
SELECT create_hypertable('datapoint_annotation', 'tstz');
SELECT create_hypertable('datapoint_quality', 'tstz');
SELECT create_hypertable('datapoints', 'tstz');
SELECT create_hypertable('sensors', 'tstz');
This works well for the first two lines, but for the latter two I get the following error:
ERROR: cannot create a unique index without the column "tstz" (used in partitioning)
SQL state: TS103
I can include the tstz in the primary key as ("id", "tstz") and use that as foreign key, but this gives me a one-to-one relation, and for minimum redundancy I would like to have a one-to-many relation.
I am sure there should be some way to do this - so what am I missing?
I'll take the foreign key constraint from datapoint_quality to datapoints as an example.
To make that work with a partitioned table, you need a unique constraint on datapoint. As the error message tell you, such a constraint must contain the partitioning key. So you end up with
ALTER TABLE datapoints ADD UNIQUE (id, tstz);
To reference that unique constraint from datapoint_quality, you need to have the timestamp there too:
ALTER TABLE datapoint_quality ADD datapoints_tstz timestamp with time zone;
You have to fill it with the appropriate values:
UPDATE datapoint_quality AS dq
SET datapoints_tstz = d.tstz
FROM datapoints AS d
WHERE d.id = dq.datapoint_id;
Then set it NOT NULL:
ALTER TABLE datapoint_quality ALTER datapoints_tstz SET NOT NULL;
Now you can define your foreign key:
ALTER TABLE datapoint_quality
ADD FOREIGN KEY (datapoint_id, datapoints_tstz)
REFERENCES datapoints (id, tstz) MATCH FULL;
There is no other way to have foreign key constraints with partitioned tables.
After testing the proposed solution by Laurenz in a database I have and also after replicating the original database of this case. I use PostgreSQL 12.6 and timescaledb 1.7.5.
Basically, I arrived well until defining the Foreign Key for Table datapoint_quality:
ALTER TABLE datapoint_quality
ADD FOREIGN KEY (datapoint_id, datapoints_tstz)
REFERENCES datapoints (id, tstz) MATCH FULL;
The next error is present in both databases I've tested after several attempts (included above one) to define the foreign key to a hypertable:
ERROR: foreign keys to hypertables are not supported Blockquote SQL state: 0A000
According to https://docs.timescale.com/timescaledb/latest/overview/limitations/##distributed-hypertable-limitations, it looks like the above error is part of the hypertable limitations:
Foreign key constraints referencing a hypertable are not supported.
Considering this, does anyone know any solution at the DB level to establish the relationships (1..* or ...) among a table without hypertables to other tables with hypertables behind?
Maybe could be a solution to deal with this at even a REST API level (e.g. Django or Flask) given at timescaledb or PostgreSQL I have not found much more solutions.

Weak Entity postgresql

I want to create a table with primary key email,nro being nro a sequential number for each email ex:
user1#e.com, 1
user1#e.com, 2
user2#e.com, 1
create table proposta_de_correcao(
email varchar(255) not null,
nro serial not null,
unique(nro,email),
PRIMARY KEY(nro, email),
FOREIGN KEY (email) REFERENCES Utilizador(email),
);
But I get the following error:
ERROR: there is no unique constraint matching given keys for referenced table "proposta_de_correcao"
I have already tried:
unique(nro,email)
contraint keys unique(nro,email)
Two things here, to help with your problem:
Why nro is serial if is not a sequential field in your database? serial type is used when you want that the field be incremented automatically (as in single integer primary keys). Maybe is better that you put an int type here.
You have in your table no unique constraints. If you create a fiddle, you can see that code runs fine:
CREATE TABLE proposta_de_correcao(
email VARCHAR(255) not null,
nro SERIAL not null,
UNIQUE(nro, email),
PRIMARY KEY(nro, email)
-- FOREIGN KEY (email) REFERENCES Utilizador(email)
);
INSERT INTO proposta_de_correcao VALUES ('user1#e.com', 1);
INSERT INTO proposta_de_correcao VALUES ('user1#e.com', 2);
INSERT INTO proposta_de_correcao VALUES ('user2#e.com', 1);
So, I can conclude that when you want to add the constraint, your database already have duplicated data in those two columns. Try to create in a test database the data mentioned above and you will see that runs perfectly.
I just removed the foreign key constraint to allow to run the code as we don't have the referenced table in example and we can consider that the referenced table don't have influence on the problem cited on answer.
Here's the fiddle.

Postgres: "Abstract" is-a relations / ERROR: there is no unique constraint matching given keys for referenced table

I gotta create some tables from the following ER diagram:
(not sure if the way I've done it is correct, so here's a short textual explanation:
Users can create 0-n Things, a Thing is created by a single User. Things consist of Subthings. A thing has at least one Subthing, and a Subthing is part of one Thing. Subthings are either made of Metal or Wood (XOR) - they're in a is-a relationship. Subthings are identified by their SubthingID and the ThingID belonging to it)
First of all, I'm really unsure on how to deal with the whole Subthing/Wood/Metal thing. Subthing is supposed to be "abstract", similar to abstract classes in OOP so that things are either made out of Wood or Metal, but not both. I also need the Subthing Entity later on, so I can't just remove it in favour of one of them.
I've come up with the following commands:
CREATE TABLE User1 (
UserID INTEGER PRIMARY KEY NOT NULL
);
CREATE TABLE Thing (
ThingID INTEGER PRIMARY KEY NOT NULL,
created_by INTEGER NOT NULL,
FOREIGN KEY (created_by) REFERENCES User1(UserID)
);
CREATE TABLE Subthing (
consists_of INTEGER REFERENCES Thing(ThingID),
SubthingID INTEGER NOT NULL,
PRIMARY KEY (SubthingID, consists_of)
);
CREATE TABLE Metal (
Mstuff VARCHAR(40) NOT NULL,
SubthingID INTEGER NOT NULL REFERENCES Subthing(SubthingID),
consists_of INTEGER NOT NULL REFERENCES Subthing(consists_of),
PRIMARY KEY (SubthingID, consists_of)
);
CREATE TABLE Wood (
Wstuff VARCHAR(40) NOT NULL,
SubthingID INTEGER NOT NULL REFERENCES Subthing(SubthingID),
consists_of INTEGER NOT NULL REFERENCES Subthing(consists_of),
PRIMARY KEY (SubthingID, consists_of)
);
However, when I try to run it with pgadmin3, I'm getting the following error:
ERROR: there is no unique constraint matching given keys for referenced table "subthing"
********** Error **********
ERROR: there is no unique constraint matching given keys for referenced table "subthing"
Now I'm really not sure what to do. Any help would be appreciated.
You must reference the complete PK constraint, you can't just reference a single column - not even if you do that for two columns individually.
You need to create a single FK constraint referencing both columns:
CREATE TABLE Metal (
Mstuff VARCHAR(40) NOT NULL,
SubthingID INTEGER NOT NULL,
consists_of INTEGER NOT NULL,
foreign key (SubthingID, consists_of)
references subthing (SubthingID, consists_of) --<< ONE constraint with TWO columns
PRIMARY KEY (SubthingID, consists_of)
);

PostgreSQL tables creation with wrong order

I have an .sql file, that creates lots of tables, that are related to each other.
I made other file for testing, that holds only two statements:
CREATE TABLE "USER" (
"id" bigint NOT NULL,
"name" varchar(50),
PRIMARY KEY ("id"));
CREATE TABLE "PERSON" (
"id" bigint NOT NULL,
"name" varchar(50),
"user" bigint,
PRIMARY KEY ("id"),
CONSTRAINT "fk_user" FOREIGN KEY ("user") REFERENCES "USER" ("id"));
This works fine if i'm trying to execute such file, but if i have other order - where table "PERSON" is created first - i'm getting ERROR: relation "USER" does not exist.
Is it possible to make some changes (or use some additional options when running 'psql' command), leaving the order as it is, to make it work?
EDIT: I understand why this error happens in given case, but i was thinking about some solution, where i don't need to change the order of my CREATE statements (Imagine you have hundreds of tables)... In MySQL you can simply use SET FOREIGN_KEY_CHECKS=0; and this will work. Do i have similar possibilities in PostgreSQL?
If you want table a to reference table b, you must either create table b before table a, or add the foreign key reference after creation:
ALTER TABLE a ADD FOREIGN KEY (a_col) REFERENCES b(b_col);
That works to create two tables that reference each other, too, but you won't be able to create rows unless you make one of them DEFERRABLE INITIALLY DEFERRED.
You are getting the error because at the point you are creating the foreign key on the the PERSON table it references the USER table which does not exist yet.
You can work round this issue by separating out FOREIGN KEY CONSTRAINT into it's own statement and applying this after you have created both tables:
CREATE TABLE "PERSON" (
"id" bigint NOT NULL,
"name" varchar(50),
"user" bigint,
PRIMARY KEY ("id"));
CREATE TABLE "USER" (
"id" bigint NOT NULL,
"name" varchar(50),
PRIMARY KEY ("id"));
ALTER TABLE "PERSON"
ADD CONSTRAINT fk_user
FOREIGN KEY ("user")
REFERENCES "USER" (id);

postgresql unique constraint not unique enough

I am creating tables to handle the security question/selected question/given answer section of our database and getting this error:
there is no unique constraint matching given keys for referenced table "m_security_questions"
Not sure how I fix this?
(Since schema won't build b/c of error, I couldn't add SQL Fiddle)
CREATE TABLE security_question --defines questions
(
id SERIAL PRIMARY KEY NOT NULL,
question character varying(1024) NOT NULL,
is_custom boolean DEFAULT FALSE NOT NULL
);
INSERT INTO security_question
(question,is_custom)
VALUES
('do you know the answer?',FALSE),
('Write your own question',TRUE);
CREATE TABLE m_security_questions
( --defines question a member chooses & allows free form question
-- id SERIAL NOT NULL,
-- I know adding id like this and making keeping same pk solves it
-- but isn't storing the extra sequence not needed?
member integer --REFERENCES member(m_no)
-- commented out reference for so only
NOT NULL,
question integer REFERENCES security_question(id) NOT NULL,
m_note text,
PRIMARY KEY(member,question)
);
-- here I add unique constraint but doesn't the primary already mean I have a unique index?
ALTER TABLE m_security_questions ADD CONSTRAINT m_security_questions_unique_member_question UNIQUE (member,question);
INSERT INTO m_security_questions
(member,question,m_note)
VALUES
(2,1,NULL),
(2,2,'How many marbles in this jar?');
CREATE TABLE m_security_answer --defines members given answer
( -- I want member & question here to line up w/ same from m_security_questions
member integer REFERENCES m_security_questions(member),
question integer REFERENCES m_security_questions(question) NOT NULL,
answer character varying(255) NOT NULL,
PRIMARY KEY (member,question)
);
-- here is where I get the error:
-- there is no unique constraint matching given keys for referenced table "m_security_questions"
INSERT INTO m_security_answer
(member,question,answer)
VALUES
(2,1,'yes'),
(2,2,'431');
The primary key definitely defines a unique constraint. But the unique constraint is on (member,question). Your have two FOREIGN KEY constraints that references just (member) and (question) separately.
I'm pretty sure what you want is:
CREATE TABLE m_security_answer --defines members given answer
(
member integer,
question integer NOT NULL,
answer character varying(255) NOT NULL,
PRIMARY KEY (member,question),
FOREIGN KEY (member, question) REFERENCES m_security_questions(member, question)
);