Conditional PostgreSQL foreign key - postgresql

Is it possible in PostgreSQL to conditionally add a foreign key?
Something like:ALTER TABLE table1 ADD FOREIGN KEY (some_id) REFERENCES other_table WHERE some_id NOT IN (0,-1) AND some_id IS NOT NULL;
Specifically, my reference table has all positive integers (1+) but the table I need to add the foreign key to can contain zero (0), null and negative one (-1) instead, all meaning something different.
Notes:
I am fully aware that this is poor table design, but it was a clever trick built 10+ years ago when the features and resources we have available at this point did not exist. This system is running hundreds of retail stores so going back and changing the method at this point could take months which we don't have.
I can not use a trigger, this MUST be done with a foreign key.

The short answer is no, Postgres does not have conditional foreign keys. Some options you might consider are:
Just not have a FK constraint. Move this logic into the data access layer and live without the referential integrity.
Allow NULL in the column, which is perfectly valid even with a FK constraint. Then, use another column to store whatever the meaning of 0 and -1 is.
Add a dummy row in the referenced table for 0 and -1. Even if it just had bogus data, it would satisfy the FK constraint.
Hope this helps!

You can add another "shadow" column to table1 which holds the cleaned values (i.e. everything but 0 and -1). Use this column for the referential integrity checks. This shadow column is updated/filled by a simple trigger on table1 which writes all values but 0 and -1 into the shadow column. Both 0 and -1 could be mapped to null.
Then you have reference integrity and your unchanged original column. The downside: You have also a little trigger and some redundant data. But alas, this is the fate of a legacy schema!

Your requirement is equivalent to this check constraint:
create table t (a float check (a >= -1 and a = floor(a) or a is null));

You can implement this with a check constraint and a foreign key.
CREATE TABLE table1 (some_id INT, some_id_fkey INT REFERENCES other_table(other_id), CHECK (some_id IN (0,-1) OR some_id IS NOT DISTINCT FROM some_id_fkey));
(not tested)

Here's another possibility. Use PG Inheritance to enforce a partition of the table into has +1 in the flag column and otherwise. (Usual rules/triggers for maintaining this.) Then have the FK relationship between only the Has_PLUS_ONE child table and the referenced table.

Related

PostgreSQL oximoron

Hi all,
Can any understand what's going on here?
The case is:
There are 2 tables, called "matricula" and "pagament" with a 1:1 relationship cardinality.
Table matricula primary key composed by 3 fields "edicio","curs" and "estudiant".
Table pagament primary key, the same as above. Furthermore, it references matricula.
As shown, trying to insert a row in pagament table is rejected because it does not exists a row in table matricula. However, asking for this row returns one result.
What am I missing?
Thanks you all
Carles
The problem is that the order of the fields in both tables is not the same, and, moreover, the restriction of the foreign key in table pagament, said that
foreign key (estudiant,curs,edicio) references matricula
without specifying the matricula fields.
It's been solved by setting this restriction as
foreign key (estudiant,curs,edicio) references matricula(estudiant,curs,edicio)

Is there a way to reserve a range in a postgres sequence?

I'm writing a program that generates large numbers of rows to be inserted into a PostgreSQL database. Due to the presence of multiple indices, this process is getting slower over time. That's why I want to move to using COPY which seems to be significantly faster. The problem is that one of the tables has a foreign key into another, and I do not have the IDs for the foreign key column.
I was thinking that maybe if I could reserve a range in the sequence used for the primary key of the first table, I could do the ID assignment manually but I don't think Postgres natively supports such an operation. Is there a way to achieve this another way?
First off from your source data identify the business key for the parent and child tables. Create those tables and a unique constraint business key. This will not be the surrogate - auto generated - PK. Now create a staging table with all the columns necessary (except the FK). Since you will most likely be using across sessions this is a permanent table, but the intent is single time usage. With this insert into the parent table generating the pk from the sequence. Then insert into the child selecting the FK by referencing the business key from the parent.
insert into parent( <columns> )
select column_list
from stage
on conflict (business key ) do nothing;
insert into child ( <columns>, )
select s.<columns>, a.id
from stage s
join parent a on s.business key = a.business key
on conflict (a.parent_id, child_bk) do nothing;
Since the above is rather obscure in the abstract see a concrete example here. There is no need attempting to "reserve a range", just let the pk/fk develop naturally.

PostgreSQL multiple on conflicts in one upsert statement

I have two unique constraints on the same table, and I want to do an upsert statement on that table.
Is it possible to specify the two conflicts in the upsert? I saw this: How to upsert in Postgres on conflict on one of 2 columns?
but my issue is slightly more involved because one of the unique constraints is a subset of the other unique constraint. I.e.
unique_constraint_1 = (col_1)
unqiue_constraint_2 = (col_1, col_2)
INSERT INTO table (col_1, col_2, col_3)
VALUES (val_1, val_2, val_3)
ON CONFLICT (what do I put here to account for both constraints?)
DO NOTHING;
thanks!
According to documentation, ON CONFLICT covers all unique constraints by default.
when omitted, conflicts with all usable constraints (and unique indexes) are handled
In your case there is no need for two constraints, as Grzegorz Grabek pointed out already. That is because the stricter single-column constraint already covers the looser two-column constraint.

PostgreSQL Foreign Key with a condition

Using postgres sql, is there a way to set up a condition on foreign key where it's limited to to another table like a normal foreign key constraint, but also allows the value of 0 to exist without it being in the other table. For example:
table_a:
id
table_b:
id
foreign_key_on_table_a_id
table_a would have a list of things, and table_b relates to table_a, but has the foreign key constraint. I would also like it to allow for a value of 0 even though there is no id of 0 in table_a.
Is this the right constraint to use? Is there another/better way of doing this without adding the value into table_a?
I'd change foreign_key_on_table_a_id to allow NULL values. Then use an FK as usual and put NULLs in there instead of zero. You can have a NULL in a column that references another table.
Alternatively, you could write a function that returns true if a value is in the other table and false otherwise and then add a CHECK constraint:
CHECK (your_column = 0 or the_function(your_column))
You won't get any of the usual cascade behavior for FKs though and this CHECK is a massive kludge.

DB associative entities and indexing

This is kind of a general DB design question. If one has an associative entity table, i.e. a cross-reference, containing records that basically just consist of two FK references, should it be indexed in some way? Is it necessary to explicitly index that table, since the PKs in the associated tables are already indexed by definition? If one should index it, should it be a combination index, consisting of the two FK fields together?
Indexes on the referenced pk columns in the other tables do not cover it.
By defining the two fk columns as composite primary key of the "associative entity" table (as you should in most cases - provided that associations are unique), you implicitly create a multi-column index.
That covers all queries involving both or the first columns optimally.
It also covers queries on the second column, but in a less effective way.
If you have important queries involving just the second column, create an additional index on that one, too.
Read all the details about the topic at this related question on dba.SE.
Or this question on SO, also covering this topic.
Suppose your associative table has a schema such as:
CREATE TABLE Association
(
ReferenceA INTEGER NOT NULL REFERENCES TableA CONSTRAINT FK1_Association,
ReferenceB INTEGER NOT NULL REFERENCES TableB CONSTRAINT FK2_Association,
PRIMARY KEY(ReferenceA, ReferenceB) CONSTRAINT PK_Association
);
The chances are that your DBMS will automatically create some indexes.
Some DBMS will create an index for each of the two foreign keys and also a unique index for the primary key. This is slightly wasteful since the PK index could be used for accessing ReferenceA too.
Ideally, there will be just two indexes: the PK (unique) index and the (duplicates allowed) FK index for ReferenceB, assuming that the PK index has ReferenceA as the first column.
If a DBMS does not automatically create indexes to enforce the referential integrity constraints, you'll want to create the RI or FK duplicates-allowed index. If it doesn't automatically create an index to enforce the PK constraint, you'll want to create that unique index too. The upside is that you'll only create the indexes for the ideal case.
Depending on your DBMS, you might find it more effective to create the table without the constraints, then to add the indexes, and then to add the constraints (which will then use the indexes you created). Things like fragmentation schemes can also factor into this; I ignored them above.
The concept remains simple — you want two indexes in total, one to enforce uniqueness on both columns and provide fast access on the leading column, and a non-unique or duplicates-allowed index on the trailing column.