Unique constraint over multiple tables - sql

Let's say we have these tables:
CREATE TABLE A (
id SERIAL NOT NULL PRIMARY KEY
);
CREATE TABLE B (
id SERIAL NOT NULL PRIMARY KEY
);
CREATE TABLE Parent (
id SERIAL NOT NULL PRIMARY KEY,
aId INTEGER NOT NULL REFERENCES A (id),
bId INTEGER NOT NULL REFERENCES B (id),
UNIQUE(aId, bId)
);
CREATE TABLE Child (
parentId INTEGER NOT NULL REFERENCES Parent (id),
createdOn TIMESTAMP NOT NULL
);
Is it possible to create a unique constraint on Child such that for all rows in Child at most one references a Parent having some value of aId? Stated another way can I created a unique constraint so that the join of the above tables will have no duplicate aId? I'm thinking not--the grammars of every database I could find seem tied to one table per constraint--but that might be a lack of imagination on my part. (De-normalizing to include aId on Child is one solution, of course.)

You could try the following. You have to create a redundant UNIQUE constraint on (id, aId) in Parent (SQL is pretty dumb isn't it?!).
CREATE TABLE Child
(parentId INTEGER NOT NULL,
aId INTEGER NOT NULL UNIQUE,
FOREIGN KEY (parentId,aId) REFERENCES Parent (id,aId),
createdOn TIMESTAMP NOT NULL);
Possibly a much better solution would be to drop parentId from the Child table altogether, add bId instead and just reference the Parent table based on (aId, bId):
CREATE TABLE Child
(aId INTEGER NOT NULL UNIQUE,
bId INTEGER NOT NULL,
FOREIGN KEY (aId,bId) REFERENCES Parent (aId,bId),
createdOn TIMESTAMP NOT NULL);
Is there any reason why you can't do that?

The proper way to do this would be to do away with the Child table altogether and put the createdOn column in the Parent table, without the NOT NULL constraint. All you are saying is that one Parent entry can have zero or one (but not more) createdOn values. You don't need a separate table for that. The fact that it is not easy or obvious to do otherwise partially proves my point. ;-) SQL usually works out that way.

Related

relational model query for simple person-car example

I'd like some insight on a relational model I have created for PostgreSQL. It has to do with person and car relationships.
CREATE TABLE "person" (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(300) NOT NULL,
"car_id" integer REFERENCES car (id));
CREATE TABLE "car" (
"id" serial NOT NULL PRIMARY KEY,
"type" varchar(50) NOT NULL);
CREATE TABLE "car_person_relations" (
"id" serial NOT NULL PRIMARY KEY,
"car_type" varchar(50) NOT NULL REFERENCES "car" ("type"),
"person_id" integer NOT NULL REFERENCES "person" ("id"));
Ultimately, I'd like to get the most popular car type based on how many people have it,
i.e. how many "person"s it is associated with. What query can I use to achieve this? And is this relational table (car_person_relations) sufficient to achieve it?
Any insight would be much appreciated
If I understand correctly, this is a simple aggregation with some limiting:
select car_type, count(*) as num_persons
from car_person_relations cpr
group by car_type
order by count(*) desc
limit 1;
First off take a look at your data model, it has a couple fundamental problems.
In CAR_PERSONS_RELATIONS table you are attempting to create a FK on car_type. However, to do so car_type would have to be
unique in the car table. From the documentation section 5.4.5. Foreign Keys .
A foreign key constraint specifies that the values in a column (or a group of columns) must match the values
appearing in some row of another table. We say this maintains the
referential integrity between two related tables. ... A foreign key
must reference columns that either are a primary key or form a unique
constraint.
But this would there could only be 1 car for a given type in the
table.
Now look at the PERSON table. This table contains a FK to car_id, thus establishing a M:1 to CAR. Basically this relationship
says "A person can own only 1 car, but a car can be owned by many persons". Your description and table name indicate
this is NOT the relationship you are trying to define.
Correcting requires the following changes:
Add car_id to CAR_PERSON_RELATIONS.
Remove car_id from PERSON.
Remove car_type from CAR_PERSON_RELATIONS.
Redefine CAR_PERSON_RELATIONS PK to car_id, person_id. Alternately
leave the surrogate PK and create UK on car_id,person_id).
Create the FKs from CAR_PERSON_RELATIONS to CAR and PERSON.
Revised Model
create table person (
id serial
,name varchar(300) not null
,constraint person_pk primary key (id)
);
create table car (
id serial
,type varchar(50) not null
,constraint car_pk primary key (id)
);
create table car_person_relations (
car_id integer
,person_id integer
,constraint car_person_relations primary key (car_id, person_id)
,constraint car_person_relations_2_car_fk
foreign key (car_id)
references (car.id)
,constraint car_person_relations_2_person_fk
foreign key (person_id)
references (person.id)
):
And then the necessary query becomes:
select cpr.car_type, count(*) as num_persons
from car_person_relations cpr
join car c
on cpr.car_id = c.id
group by cpr.car_type
order by count(*) desc
limit 1;

Create three tables with the same auto increment primary key and name

Cannot find a good answer for this online. I need to create three tables as an example below, a parent with an auto increment ID that will then link to the two child tables (Subject and Comment) with the same exact ID and cascade back if that parent ID is deleted.
Any ideas on how to solve?
I have googled and am extremely confused as to how to solve this one. I have a decent amount of experience with SQL, but not with creating tables and relationships.
CREATE TABLE Parent
(
ParentID INT NOT NULL IDENTITY PRIMARY KEY,
Email VARCHAR(50) NOT NULL,...
)
CREATE TABLE Subject
(
ParentID INT NOT NULL PRIMARY KEY,
Subject
)
CREATE TABLE Comment
(
ParentID INT NOT NULL PRIMARY KEY,
Comment VARCHAR(100)
)
Use a 1 to 1 relationship with on delete cascade:
CREATE TABLE Parent(
ParentID INT NOT NULL IDENTITY PRIMARY KEY,
Email VARCHAR(50) NOT NULL,...
)
CREATE TABLE Subject(
ParentID INT NOT NULL PRIMARY KEY,
Subject,
CONSTRAINT fk_SubjectParentId FOREIGN KEY (ParentID)
REFERENCES Parent (ParentID) ON DELETE CASCADE
)
CREATE TABLE Comment(
ParentID INT NOT NULL PRIMARY KEY,
Comment VARCHAR(100),
CONSTRAINT fk_CommentParentId FOREIGN KEY (ParentID)
REFERENCES Parent (ParentID) ON DELETE CASCADE
)
This is known as a 1 to 1 relationship, since both ends of the foreign key are unique within their table.
Though I have to agree with Mitch Wheat comment, cascading deletes is something to use with caution. by specifying cascade delete, you are telling the database engine to delete the related records whenever a parent record is deleted. Not having that cascade delete option will simply throw an error if you attempt to delete a record that is referenced by another table. This forces you, as a developer, to think about the side effects of deleting rows from the parent table and basically acts as a "Are you sure you want to delete?" guard against unwanted deletes.

SQL Server Foreign Key

I'm trying to create this database with the following relations in SQL Server and I get this error:
Msg 1776, Level 16, State 0, Line 11
There are no primary or candidate keys in the referenced table 'Consumable' that match the referencing column list in the foreign key 'FK_Recipe_Ingredie__59FA5E80'.
Msg 1750, Level 16, State 0, Line 11
Could not create constraint. See previous errors.
What am I doing wrong?
Here's my code;
CREATE TABLE Consumable
(
c_ID int NOT NULL,
Name varchar(32) NOT NULL,
Amount int,
Unit varchar(8) NOT NULL CHECK (unit IN ('ml', 'g', 'pieces', 'unknown')),
CONSTRAINT PK_Consumable PRIMARY KEY (c_ID, Name)
)
CREATE TABLE Recipe
(
Name varchar(64) PRIMARY KEY NOT NULL,
Type varchar(32),
Description varchar(512),
IngredientsID int NOT NULL FOREIGN KEY REFERENCES Consumable(c_ID) ON DELETE CASCADE
)
CREATE TABLE Kitchen
(
K_ID int PRIMARY KEY NOT NULL IDENTITY,
IngredientsID int FOREIGN KEY REFERENCES Consumable(c_ID) ON DELETE CASCADE
)
Read the error message!
It's pretty clear: the foreign key you're trying to set up from Recipe.IngredientsID doesn't reference the primary key of your target table (Consumable - primary key is (c_ID, Name) - not just c_ID ....)
To fix this: you must reference the whole compound PK on your target table (e.g. you must have both columns of PK_Consumable in your child table in order to reference it
CREATE TABLE Recipe
(
Name varchar(64) PRIMARY KEY NOT NULL,
Type varchar(32),
Description varchar(512),
IngredientsID int NOT NULL,
IngredientsName varchar(32) NOT NULL,
CONSTRAINT FK_Recipe_Consumable
FOREIGN KEY (IngredientsID, IngredientsName)
REFERENCES Consumable(c_ID, Name) ON DELETE CASCADE
)
Any foreign key can only ever reference the WHOLE primary key of a parent table - or a unique constraint (again: all columns involved in that unique constraint). You cannot just simply refer to an arbitrary column (or set of columns) in your parent table.
Your Pk is a composite PK and so c_id is not necessarily unique and thus cannot be used in an FK relationship.
You have several choices depending that you had. If you will not have multiple of c_id in the consumables table than you can create a unique index on it and you should be able to create the Fk. BUt in that case really why are you using a compositer OK? The other choice you have in this situation is to make just the c_id the PK and then put a unique index on Name. Never use a composite PK if you don't have to.
If the C_id will into be unique in the Consumable table, then the only choice you have is to add the name column to the Other tables. You would of course have to give it a different name in Recipe since it has a different name.
Ingredients_id doesn't make sense in the recipe table as you are going to have more than one ingredient in a recipe. You should have a child table that contains recipe ingredients.
And On Delete Cascade is a poor choice as well. Very bad thing to do to a nice innocent database. Do you really want to delete the recipes you are out of an ingredient? I don't think so.

Check if data exists in another table on insert?

Table A
(
Table_A_ID int
)
Table B
(
Table_B_ID int
Value int
)
Say I want to insert data into Table B, where 'Value' would be the same as a Table_A_ID.
How would I make a constraint or check that the data actually exists in the table on insertion?
You probably need to enforce data integrity not only on INSERT into Table B, but also on UPDATE and DELETE in both tables.
Anyway options are:
FOREIGN KEY CONSTRAINT on Table B
TRIGGERs on both tables
As a last resort if for some reason 1 and 2 is not an option STORED PROCEDUREs for all insert, delete update operations for both tables
The preferred way to go in most cases is FOREIGN KEY CONSTRAINT.
Yap, I agree with #peterm.
Cause, if your both Table_A_ID and Table_B_Id are primary keys for both tables, then you don't even need two tables to store the value. Since, your two tables are seems to be on 'one-to-one' relationship. It's one of the database integrity issues.
I think you didn't do proper normalisation for this database.
Just suggesting a good idea!
I found this example which demonstrates how to setup a foreign key constraint.
Create employee table
CREATE TABLE employee (
id smallint(5) unsigned NOT NULL,
firstname varchar(30),
lastname varchar(30),
birthdate date,
PRIMARY KEY (id),
KEY idx_lastname (lastname)
) ENGINE=InnoDB;
Create borrowed table
CREATE TABLE borrowed (
ref int(10) unsigned NOT NULL auto_increment,
employeeid smallint(5) unsigned NOT NULL,
book varchar(50),
PRIMARY KEY (ref)
) ENGINE=InnoDB;
Add a constraint to borrowed table
ALTER TABLE borrowed
ADD CONSTRAINT FK_borrowed
FOREIGN KEY (employeeid) REFERENCES employee(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
NOTE: This tells MySQL that we want to alter the borrowed table by adding a constraint called ‘FK_borrowed’. The employeeid column will reference the id column in the employee table – in other words, an employee must exist before they can borrow a book.
The final two lines are perhaps the most interesting. They state that if an employee ID is updated or an employee is deleted, the changes should be applied to the borrowed table.
NOTE: See the above URL for more details, this is just an excerpt from that article!
Create a foreign key constraint on the column 'Value' on table B that references the 'Table_A_ID' column.
Doing this will only allow values that exist in table A to be added into the 'Value' field of table B.
To accomplish this you first need to make Table_A_ID column the primary key for table A, or it at least has to have some sort of unique constraint applied to it to be a foreign key candidate.
BEGIN TRANSACTION -- REMOVE TRANSACTION AND ROLLBACK AFTER DONE TESTING
--PUT A PRIMARY KEY ON TABLE A
CREATE TABLE A
( Table_A_ID int CONSTRAINT PK_A_Table_A_ID PRIMARY KEY)
--ON VALUE ADD A FOREIGN KEY CONSTRAINT THAT REFERENCEs TABLE A
CREATE TABLE B
( Table_B_ID int,
[Value] int CONSTRAINT FK_B_Value_A REFERENCES A(Table_A_ID)
)
-- TEST VALID INSERT
INSERT A (Table_A_ID) VALUES (1)
INSERT B (Table_B_ID, [Value]) VALUES (1,1)
--NOT ALLOW TO INSERT A VALUE THAT DOES NOT EXIST IN A
--THIS WILL THROW A FOREIGN KEY CONSTRAINT ERROR
INSERT B (Table_B_ID, [Value]) VALUES (1,2) -- 2 DNE in table A
ROLLBACK
Note: there is no magic to 'FK_B_Value_A' or 'PK_A_Table_A_ID' it simply a naming convention and be called anything. The syntax on the foreign key and primary key lines work like this:
column-definition CONSTRAINT give-the-constraint-a-name REFERENCES table-name ( table-column )
column-definition CONSTRAINT give-the-constraint-a-name PRIMARY KEY

Is it possible to reference a different column in the same table?

If a blog has a 'categories' table such as the following:
CREATE TABLE categories
(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
parent_id INTEGER NOT NULL,
name VARCHAR(30) NOT NULL,
description TEXT,
count INTEGER NOT NULL DEFAULT 0
);
And if the parent_id field is intended to refer to the 'id' field of the categories table, then how could I add a constraint that would ensure that values inserted into parent_id references the id field?
I simply want to make sure that only category id values that exist can be used as a parent of a newly inserted category.
Yes, you can reference a column in the same table.
But that column should be nullable otherwise you can't insert the first record.
CREATE TABLE categories
(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
parent_id INTEGER NULL,
name VARCHAR(30) NOT NULL,
description TEXT,
count INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
Note that after the REFERENCES keyword the table name is not optional, so you must specify it even if you are referencing a column in the same table. From the documentation:
reference_definition:
REFERENCES tbl_name (index_col_name,...)
[MATCH FULL | MATCH PARTIAL | MATCH SIMPLE]
[ON DELETE reference_option]
[ON UPDATE reference_option]
See it working online: sqlfiddle
Just use a normal foreign key:
ALTER TABLE categories ADD CONSTRAINT FK_categories_Parent_ID
REFERENCES categories (ID)
However Parent_ID should be nullable as you'll never be able to insert a record
You can use below link. It has how you can do it Oracle database.
http://www.adp-gmbh.ch/ora/data_samples/hierarchic_yahoo.html
Thanks