Enforcing unique rows in a many-to-many junction table - sql

I have a junction table for a many-to-many relationship that just links two foreign keys together. However I've found that this will not prevent duplicate identical row entries. What's the proper way of handling that? I thought adding PRIMARY KEY to the two foreign keys would do it, but it seems like I'm not understanding that correctly.
CREATE TABLE ab_link (
a_id bigint REFERENCES a(a_id) PRIMARY KEY,
b_id bigint REFERENCES b(b_id) PRIMARY KEY
);
I found on another question this example:
CREATE TABLE bill_product (
bill_id int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id) -- explicit pk
);
Is that constraint the best way of enforcing uniqueness? I would think there would be some way of doing it without having a third row.

For a compound primary key, you need a separate declaration:
CREATE TABLE ab_link (
a_id bigint REFERENCES a(a_id),
b_id bigint REFERENCES b(b_id),
PRIMARY KEY (a_id, b_id)
);

Related

SQL: Having a primary key also be a foreign key

For this question, I am referring to the specific case where you have table T, it has primary key K, but K is a foreign key. Is this valid? And how would you write it in SQL99?
All the other questions I've seen on here are just asking whether or not a primary key can be a foreign key for another table. That's not what I'm asking about. I'm asking about a table which has a foreign key where that is the primary key of that table.
If I understand you correctly, you want to create a hierarchical table, for instance:
create table hierarchical
(
id number primary key,
parent_id number
);
alter table hierarchical add constraint
fk_parent_id foreign key(parent_id) references hierarchical(id);
This kind of table can contain employees/managers for instance.
A column can be a primary key as well foreign key. For example, refer to the following:
A column can be both a primary key and a foreign key. For example:
create table A
(
id int not null
, constraint PK_A primary key (id)
);
create table B
(
id int not null
,constraint PK_B primary key (id)
,constraint FK_B_ID foreign key (id) references A(id)
);
Though, this requires data to be present in Table B first.

Can a foreign key reference another foreign key

Is it possible to have a foreign key that references another foreign key in a different table, or can it only reference primary and unique keys?
A foreign key can reference any field defined as unique. If that unique field is itself defined as a foreign key, it makes no difference. A foreign key is just to enforce referential integrity. Making a field a foreign key doesn't change the field itself in any way. If it is a unique field, it can also be the target of another FK.
For example:
create table Table1(
PK int identity primary key,
...
);
create table Table2( -- 1-1 relationship with Table1
PKFK int primary key,
...,
constraint FK_Table2_1 foreign key( PKFK ) references Table1( PK )
);
create table Table3( -- relates to Table2
PKFKFK int primary key,
...,
constraint FK_Table3_2 foreign key( PKFKFK ) references Table2( PKFK )
);
I know of no DBMS where this is not the case. And I agree with Horse, there is nothing wrong with the practice.
Is it possible to have a foreign key that references another foreign key in a different table
Yes. In fact contrary to accepted answer, the referenced FK column doesn't even have to be unique! - at least in MySQL. see https://www.db-fiddle.com/f/6RUEP43vYVkyK2sxQQpBfj/0 for a demo of the same.
which brings up the question that if the FK is not unique in the parent table, then who is the parent row? The purpose of FKs is to establish parent-child relationship.

What's the difference between these SQL syntaxes?

I was looking for info on foreign keys.... AGAIN! ... and happened to notice on webschools.com they have different examples of the same thing. for the foreign key example they have
CREATE TABLE Orders
(
O_Id int NOT NULL,
OrderNo int NOT NULL,
P_Id int,
PRIMARY KEY (O_Id),
FOREIGN KEY (P_Id) REFERENCES Persons(P_Id)
)
CREATE TABLE Orders
(
O_Id int NOT NULL PRIMARY KEY,
OrderNo int NOT NULL,
P_Id int FOREIGN KEY REFERENCES Persons(P_Id)
)
CREATE TABLE Orders
(
O_Id int NOT NULL,
OrderNo int NOT NULL,
P_Id int,
PRIMARY KEY (O_Id),
CONSTRAINT fk_PerOrders FOREIGN KEY (P_Id)
REFERENCES Persons(P_Id)
)
now..........
what's the difference?...
How do I know which one I'm suppose to use for my database? I have a feeling this will help resolve a lot of the confusion I'm having with SQL...
No difference in effect: They achieve exactly the same thing.
I prefer the in-line version, because it puts the fk definition as close to the column definition as possible.
There's a 3rd way - a separate alter table statement (which I think is the "official" way):
alter table orders
add contraint fk_PerOrders
foreign key p_id references persons(p_id);
You may find some databases don't support one version or the other.
All of them are doing same thing. Use the one which you feel is easy to understand.
All do same (three ways):
In first, you first defined P_Id as int then defined foreign key constraint.
In second, P_Id int FOREIGN KEY REFERENCES Persons(P_Id). P_Id is defecation and foreign key constraint defecation in same line.
In third, a foreign key constraint name is also give fk_PerOrders. that can be useful later when you wants to drop constraint. e.g.
ALTER TABLE Orders
DROP FOREIGN KEY fk_PerOrders
Its always good practice to give name to a constraint.

Why is this a cyclical foreign key constraint?

I came upon this code, marked "error," in an application I'm to update. Running it on a test database gives a cyclical reference error:
The referential relationship will result in a cyclical reference that is not allowed (Constraint name = descriptions_fk_2)
I named the constraints to see which one caused the problem.
CREATE TABLE items (
id INT NOT NULL UNIQUE IDENTITY,
name NCHAR(100) NOT NULL UNIQUE,
PRIMARY KEY (id)
);
CREATE TABLE sources (
id INT NOT NULL UNIQUE IDENTITY,
item_id INT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (item_id)
REFERENCES items(id) ON UPDATE NO ACTION ON DELETE CASCADE
);
CREATE TABLE descriptions (
id INT NOT NULL UNIQUE IDENTITY,
item_id INT NOT NULL,
source_id INT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT descriptions_fk_1 FOREIGN KEY (item_id)
REFERENCES items(id) ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT descriptions_fk_2 FOREIGN KEY (source_id)
REFERENCES sources(id) ON UPDATE NO ACTION ON DELETE CASCADE
);
Why is this a cyclical reference? The descriptions table is linked to two separate tables, but none of them link back to descriptions.
It's not strictly cyclical - but there are multiple cascade paths. So you could cascade delete a row in items two ways:
1) description -> item
2) description -> source -> item
And, for that reason, it's disallowed.
I believe it's a performance concern, as PostGres will allow cycles like that and will just work it out, but deletes under those circumstances can be quite slow.
For some further reading about why it's disallowed, please see this answer.

How can I get around this foreign key constraint having to have a unique name?

I'm not sure why these have to be unique, but from reading the MySQL forums it appears that they do. However, I think it has something more to do with the INDEX name. I have two tables that have foreign key constraints referencing the same primary key on a third table. If it helps, I'm using MySQL workbench to design the schema.
I usually name my foreign key on each table the same name as the primary key it references. I guess this isn't possible. It will create the first table with the foreign key constraint, but when it tries to create the second table it throws an error. Here is the second table it throws the error on:
CREATE TABLE IF NOT EXISTS `joe`.`products_to_categories` (
`product_to_category_id` INT NOT NULL AUTO_INCREMENT ,
`category_id` INT NOT NULL ,
`product_id` INT NOT NULL ,
PRIMARY KEY (`product_to_category_id`) ,
INDEX `category_id` (`category_id` ASC) ,
INDEX `product_id` (`product_id` ASC) ,
CONSTRAINT `category_id`
FOREIGN KEY (`category_id` )
REFERENCES `joe`.`categories` (`category_id` )
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT `product_id`
FOREIGN KEY (`product_id` )
REFERENCES `joe`.`products` (`product_id` )
ON DELETE CASCADE
ON UPDATE NO ACTION)
ENGINE = InnoDB;
I want the foreign key names to be the same as the primary key in both of the other tables. What should I remove here so that I can use these names. What is the best practice here.
It is not possible because you would have a conflict in the filename for the file that is used for the index IIRC. I probably would name the key < tablename >_< column_name > or something like that.
You are creating an index (constraint) by the name of product_id via:
INDEX product_id
Then you are going and creating another constraint (for the foreign key) with the same name:
CONSTRAINT product_id
What you need to do is allow the server to provide a default, unique constraint name by removing the
CONSTRAINT product_id
See this URL: http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html
"If the CONSTRAINT symbol clause is given, the symbol value must be unique in the database. If the clause is not given, InnoDB creates the name automatically."
In PostgreSQL, the default for naming indexes is to append "_pkey" and "_fkey" to the name of the primary and foreign key, respectively. So your case would look like:
INDEX `product_id_fkey` (`product_id` ASC) ,
UPDATE: I just tried this and it worked. See if that's what you had in mind.
use test;
create table if not exists test.product
(
product_id int not null auto_increment,
name varchar(80) not null,
primary key(product_id)
);
create table if not exists test.category
(
category_id int not null auto_increment,
name varchar(80) not null,
primary key(category_id)
);
create table if not exists test.product_category
(
product_id int,
category_id int,
primary key(product_id, category_id),
constraint product_id_fkey
foreign key(product_id) references product(product_id)
on delete cascade
on update no action,
constraint category_id_fkey
foreign key(category_id) references category(category_id)
on delete cascade
on update no action
);
insert into test.product(name) values('teddy bear');
insert into test.category(name) values('toy');
insert into test.product_category
select p.product_id, c.category_id from product as p, category as c
where p.name = 'teddy bear' and c.name = 'toy';