Foreign key with distinguisher - sql

I have one table STATUSES containing statuses for more tables:
TABLE_NAME | STATUS_ID | STATUS_NAME
===========+===========+============
TAB_1 | 1 | New
TAB_1 | 2 | Started
TAB_1 | 3 | Complete
TAB_2 | 1 | Empty
TAB_2 | 2 | Full
Table STATUSES has two column primary key (table_name,status_id).
Then I have the table TAB_1:
ID | STATUS_ID | TAB_VALUE
===+===========+==========
1 | 1 | Value1
When I want to retrieve table values with status I use the following SQL query:
SELECT id,tab_value,status_name
FROM tab_1 t
JOIN statuses s ON (s.status_id=t.status_id AND s.table_name='TAB_1')
Now I'd like to create a foreign key from TAB_1 to STATUSES but STATUS_ID should be distinguished with table name!
I tried sth like this:
ALTER TABLE tab_1
ADD CONSTRAINT tab_1_status_fk FOREIGN KEY ('TAB_1',status_id)
REFERENCES statuses (table_name,status_id)
ENABLE;
Of course, this does not work (otherwise I wouldn't be here).
Is it possible to create such a foreign key with 'distinguisher'?

I think I'd add the table name as a virtual column with a constant value, e.g.
alter table tab_1 add (table_name as ('TAB_1'));
And then create the fk using that
ALTER TABLE tab_1
ADD CONSTRAINT tab_1_status_fk FOREIGN KEY (table_name,status_id)
REFERENCES statuses (table_name,status_id)
ENABLE;
Edit: If you're on 12c or up, you can make the virtual column hidden; I think it'll still work with a FK...
alter table tab_1 add (table_name invisible as ('TAB_1'));

Related

Prevent row insertion if foreign_key is not null in the referenced row

Simple table with primary key as the first column and foreign key referencing the same table as the second column.
1 | null
2 | 1
I want to prevent insertion of a row whose fk would reference a row whose fk is not null. E.g. insertion of the row below should be prevented:
3 | 2
How can it be achieved? I'v tried the following
ALTER TABLE t
ADD CONSTRAINT t_check
CHECK (
fk not in (select t.pk from t1 t where t.pk = fk and t.fk is not null)
);
but got
ERROR: cannot use subquery in check constraint
as expected.

Query other fields in tables referenced by FK in junction table

I have a "junction" table (recipe_ingredients) that I'm using to handle a many-to-many relationship in my database. Each field in that table is a reference to the PK of another table: recipe_id, ingredient_id, quantity_id, and measure_id. These are the PKs of the metadata, ingredients, quantities, and measurements tables. With the exception of the metadata table, all these tables have only two fields: the PK, and a name or number associated with that PK.
I'm trying to query recipe_ingredients for all rows that have a given recipe_id... but instead of displaying the PKs in each row with that recipe_id, I want to display the name/number associated with that PK in that table.
So instead of a query that returns this:
recipe_id | ingredient_id | quantity_id | measure_id
----------+---------------+-------------+-----------
1 | 10 | 2 | 3
----------+---------------+-------------+-----------
1 | 12 | 2 | 3
----------+---------------+-------------+-----------
1 | 13 | 5 | 6
----------+---------------+-------------+-----------
...
I'm looking for a query that returns something like this:
recipe_id | ingredient_name | quantity_num | measure_num
----------+-----------------+--------------+------------
1 | Ground cayenne | 1/2 | tsp
----------+-----------------+--------------+------------
1 | Ground paprika | 1/2 | tsp
----------+-----------------+--------------+------------
1 | Olive oil | 1 | tbsp
----------+-----------------+--------------+------------
...
I just started learning SQL, so I only know how to make simple queries to a single table. I don't even know where to begin with this query, except that I may need a kind of join statement. What query could I write to achieve this?
I'm using sqlite3 on Debian.
The recipe_ingredients "junction" table:
CREATE TABLE recipe_ingredients (
recipe_id int,
ingredient_id int,
quantity_id int,
measure_id int,
primary key (recipe_id, ingredient_id),
foreign key (recipe_id)
references metadata (recipe_id)
on update restrict
on delete restrict,
foreign key (ingredient_id)
references ingredients (ingredient_id)
on update restrict
on delete restrict,
foreign key (quantity_id)
references quantities (quantity_id)
on update restrict
on delete restrict,
foreign key (measure_id)
references measurements (measure_id)
on update restrict
on delete restrict
);
These are the foreign tables referenced by the junction table:
create table if not exists ingredients (
ingredient_id integer primary key,
ingredient_name text not null
);
create table if not exists quantities (
quantity_id integer primary key,
quantity_num int not null
);
create table if not exists measurements (
measure_id integer primary key,
measure_name text not null
);
create table if not exists metadata (
recipe_id integer primary key,
recipe_name text not null,
course_id int,
cuisine_id int,
servings int,
prep_time int,
cook_time int,
total_time int,
foreign key (course_id)
references courses (course_id)
on update restrict
on delete restrict,
foreign key (cuisine_id)
references cuisine (cuisine_id)
on update cascade
on delete cascade
);
You can join the junction table with each of the related tables like so:
select
ri.recipe_id,
i.ingredient_name,
q.quantity_num,
m.measure_num
from recipe_ingredients ri
inner join ingredients i on i.ingredient_id = ri.ingredient_id
inner join quantities q on q.quantity_id = ri.quantity_id
inner noin measurements m on m.measure_id = ri.measure_id
where ri.recipe_id = ?
The question mark (?) stands for the recipe_id that you are looking for.

Postgresql: Unique constraint over Union of 2 columns

I have the following tables:
TRANSACTIONS
id | amount
------------------
1 | 100
2 | -100
3 | 250
4 | -250
TRANSACTION_LINKS
id | send_tx | receive_tx
---------------------------
1 | 2 | 1
2 | 4 | 2
The send_tx and receive_tx columns in the transaction links table use foreign keys pointing to the ID of the transactions table.
This is how I create the transaction links table
CREATE TABLE IF NOT EXISTS transaction_links
(
id BIGSERIAL PRIMARY KEY,
send_id INT NOT NULL UNIQUE REFERENCES transactions(id) ON DELETE
RESTRICT,
receive_id INT NOT NULL UNIQUE REFERENCES transactions(id) ON DELETE
RESTRICT
);
I want to create a unique constraint over both send_tx and receive_tx, meaning that if transaction id 1 is found in the receive_tx column, then
no other transaction link can have the receiving_tx = 1
no other transaction link can have the sending_tx = 1
I know that I can have a unique constraint on each column separately, but that only solves my first problem
EDIT:
essentially, if I insert (1,2) into transaction links, then inserting (1,3) or (3,1) or (4,2) or (2,4) should all be rejected
Also, in my design, the transactions table contains many more columns than what is shown here, I've only included the amount for simplicity's sake.
You can use an exclusion constraint which only requires a single index:
alter table transaction_links
add constraint check_tx
exclude using gist ( (array[send_id, receive_id]) with &&);
The && operator is the "overlaps" operator for arrays - which means "have elements in common, regardless of the order of the elements in the array. In this case the constraint prevents to insert any row where any value of (send_id, receive_id) appears in some other row of the table (regardless of the column).
However, you need the intarray extension for that.
Online example: https://rextester.com/QOYS23482

drop primary key constraint in postgresql by knowing schema and table name only

As far I know the only way of dropping primary key in postgresql is:
ALTER TABLE schema.tableName DROP CONSTRAINT constraint_name;
the constraint name by default is tableName_pkey. However sometimes if table is already renamed I can’t get the original table name to construct right constraint name.
For example, for a table created as A then renamed to B the constraint remains A_pkey but I only have the table name B.
Do you know right way to drop the pkey constraint by knowing only the schema name and table name ?
I am writing program for doing this so I need to use only SQL queries. Solutions like "open pgAdmin and see the constraint name" will not work.
You can use information from the catalog tables like so:
Create a table with id as the primary key
create table test1 (id int primary key, name text);
Create the SQL to drop the key
select concat('alter table public.test1 drop constraint ', constraint_name) as my_query
from information_schema.table_constraints
where table_schema = 'public'
and table_name = 'test1'
and constraint_type = 'PRIMARY KEY';
The result will be:
alter table public.test1 drop constraint test1_pkey
You can create a stored function to extract this query and then execute it.
login to the database using psql, the command line tool.
Then type:
\d <table_name>
for example:
\d claim
Table "public.claim"
Column | Type | Collation | Nullable | Default
--------------------------------+-----------------------------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('claim_id_seq'::regclass)
policy_id | integer | | |
person_id | integer | | |
incident_id | integer | | |
first_notification_of_loss | timestamp without time zone | | |
police_reference | character varying(40) | | |
photos_to_follow | boolean | | |
sketch_to_follow | boolean | | |
description_of_weather | character varying(2000) | | |
description_of_property_damage | character varying(2000) | | |
created_at | timestamp without time zone | | not null | now()
updated_at | timestamp without time zone | | not null |
Indexes:
"primary_key_claim" PRIMARY KEY, btree (id)
Foreign-key constraints:
"foreign_key_claim_incident" FOREIGN KEY (incident_id) REFERENCES incident(id)
"foreign_key_claim_person" FOREIGN KEY (person_id) REFERENCES person(id)
"foreign_key_claim_policy" FOREIGN KEY (policy_id) REFERENCES policy(id)
Referenced by:
TABLE "claimant" CONSTRAINT "foreign_key_claimant_claim" FOREIGN KEY (claim_id) REFERENCES claim(id)
TABLE "damage" CONSTRAINT "foreign_key_damage_claim" FOREIGN KEY (claim_id) REFERENCES claim(id)
TABLE "witness" CONSTRAINT "foreign_key_witness_claim" FOREIGN KEY (claim_id) REFERENCES claim(id)
This shows you the primary key name (as well as other stuff).
If you want to do this programmatically and you are using Java or another language that uses the JDBC interface, you can use the class DatabaseMetaData, method getPrimaryKeys.
Otherwise, the other answer, selecting from the system catalogs, is the way to go.
For those using PGAdmin:
Navigate to the Database>Schemas>{your schema}>Tables>{your table name} right-click>Properties.
Go to the Constraints tab and add/remove at will.
I did this using PGAdmin 4 and PostgreSQL 14.

How to update rows of two tables that have foreign key restrictions

I have two tables: one is foreign reference table lets say table a and other one is the data table lets say table b.
Now, when I need to change the data in table b, but I get restricted by table a.
How can I change "rid" in both tables without getting this message?
"ERROR: insert or update on table "table a" violates foreign key
constraint "fk_boo_kid" SQL state: 23503
Detail: Key (kid)=(110) is not present in table "table b".
Example query to update both tables:
UPDATE table b table a SET rid = 110 WHERE rid =1
table b
+-----+-------+-------+
| rid | ride | qunta |
+-----+-------+-------+
| 1 | car | 1 |
| 2 | bike | 1 |
+-----+-------+-------+
table a
+-----+-----+------------+
| kid | rid | date |
+-----+-----+------------+
| 1 | 1 | 20-12-2015 |
| 2 | 2 | 20-12-2015 |
+-----+-----+------------+
In Postgres you can use a writeable CTE to update both tables in a single statement.
Assuming this table setup:
create table a (rid integer primary key, ride text, qunta integer);
create table b (kid integer primary key, rid integer references a, date date);
The CTE would be:
with new_a as (
update a
set rid = 110
where rid = 1
)
update b
set rid = 110
where rid = 1;
As (non-deferrable) foreign keys are evaluated on statement level and both the primary and foreign key are changed in the same statement, this works.
SQLFiddle: http://sqlfiddle.com/#!15/db6d1/1
you can not update/delete primary key in table B, because the primary key is used in table A.
you can delete primary key in table B, IF >>
you must delete the row in table A which is used primary key table B.
you can delete the row in table B
you have to change both manual
SET session_replication_role = 'replica';
UPDATE table a SET rid=110 WHERE rid=1 ;
UPDATE table b SET rid=110 WHERE rid=1 ;
SET session_replication_role = 'origin';
This is too long for a comment.
You should really explain why you want to change ids into something else. Primary keys really should be considered immutable, so they identify rows both within a table and over time.
If you do need to change them for some reason, then define proper foreign key constraints for the tables in question. Then define the foreign keys to be on update cascade. This will "cascade" changes to all affected changes when a primary key changes.