Query other fields in tables referenced by FK in junction table - sql

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.

Related

What's the best way to store an enum type in postgres being used in a join table?

I have have the following three tables:
Movie Types
CREATE TYPE category AS ENUM ('comedy', 'drama', 'action', 'thriller');
CREATE TABLE movie_types (
id BIGSERIAL PRIMARY KEY,
category category NOT NULL
);
Column | Type | Modifiers
--------+--------+----------------------------------------------------
id | bigint | not null
category | category | not null
Movie to Movie types joins table
CREATE TABLE movie_categories (
id BIGSERIAL PRIMARY KEY,
movie_type_id FOREIGN KEY REFERENCES movie_type(id)
movie_id FOREIGN KEY REFERENCES movie(id)
);
Column | Type | Modifiers
--------+--------+----------------------------------------------------
id | bigint | not null
movie_type_id | category | not null
movie_id | category | not null
Movies
CREATE TABLE movies (
id BIGSERIAL PRIMARY KEY,
name varchar
);
Column | Type | Modifiers
--------+--------+----------------------------------------------------
id | bigint | not null
name | string | not null
The Movie types is limited list of categories stored as an enum. A movie can have several different categories associated with it.
What's the best practice when storing something in a similar data model? I using the enum type here good practice or is it better to just use varchar for category in movie_types?
An ENUM sets up a predetermined set of values that a column can take. A look up table provides the same restriction on a column. The problem you face is you are attempting to implement both. A solution is to choose and implement one of them. I tend to lean toward minimizing maintenance where possible, so the following implements a look table approach.
First step: Drop the ENUM.
Second alter category table category to text.
Insert the prior enum values into category table.
Adjust other tables as needed.
The result becomes a simple M:M with Movie:Movie_Types with the intersection table movie_categories.
create table movie_types (
id bigint generated always as identity primary key
, category text not null unique
);
insert into movie_types(category)
values ('comedy'), ('drama'), ('action'), ('thriller');
create table movies (
id bigint generated always as identity primary key
, name varchar
);
create table movie_categories (
movie_type_id bigint references movie_types(id)
, movie_id bigint references movies(id)
, constraint movie_categories_pk
primary key (movie_id,movie_type_id)
);

Foreign key constraint : alternative for one to one relation with split tables

For tables like this:
Book
| id | type
+----+--------
| 1 | audio
| 2 | paper
PK id SEQUENCE
AudioBook
| book_id | type
+---------+-----------
| 1 | audio
FK book_id references Book
FK constraint book_id + type references Book
PaperBook
| book_id | type
+---------+--------
| 2 | paper
FK book_id references Book
FK constraint book_id + type references Book
Is there other way to ensure that PaperBook and AudioBook will not have same book_id without type column on them (remove FK constraint book_id + type references Book and type on childs?
Example without fk constraint where AudioBook and PaperBook can have book_id=1 at the same time:
Book
| id | type
+----+--------
| 1 | audio
| 2 | paper
PK id SEQUENCE
AudioBook
| book_id
+---------
| 1
FK book_id references Book
PaperBook
| book_id
+---------
| 2
FK book_id references Book
approach one: foreign key constraints ensure data integrity between the parent table and children tables. a foreign key constraint prevents the child table from inputting a value for a key not found in the parent table.
without type, you will want to use a guid to keep the key unique on the foreign constraint. I don't like guids because you lose readibility.
approach two: in general a foreign key id is a unique number referenced in the parent table. if you are sharing between multiple tables and a single parent id than consider creating a many to many table to join the tables using key id and type, a cross reference table for lookup.
you can populate and remove items from the cross reference table using a trigger

Foreign key with distinguisher

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'));

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

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.