How to add a unique constraint using a column from another table? - sql

I have 3 tables in SQL Server 2008 R2 that look like these:
A COMPANY may have many LSPs. An LSP may have many SERVICEs.
And I need to make sure that SERVICE_CODE uniquely identifies a SERVICE record within a COMPANY. In other words, COMPANY_ID + SERVICE_CODE should uniquely identify a SERVICE record in the entire system.
For example: COMPANY-A may NOT have 2 services (with 2 different SERVICE_IDs) with the same SERVICE_CODE. But COMPANY-A and COMPANY-B may both have 2 separate SERVICES (again, with different SERVICE_IDs) with SERVICE_CODE = "PREMIUM".
I need something like this:
alter table "SERVICE"
add constraint "SERVICE_Index01"
unique ("COMPANY_ID", "SERVICE_CODE")
But (obviously) this fails because the COMPANY_ID column is not in the SERVICE table.
Thanks in advance for any help.

You could use an indexed view as an external constraint:
CREATE VIEW dbo.CompanyServices
WITH SCHEMABINDING
AS
SELECT
c.COMPANY_ID,
s.SERVICE_CODE
FROM dbo.COMPANY c
INNER JOIN dbo.LSP l ON c.COMPANY_ID = l.COMPANY_ID
INNER JOIN dbo.SERVICE s ON l.LSP_ID = s.LSP_ID
GO
CREATE UNIQUE CLUSTERED INDEX UQ_CompanyServices
ON dbo.CompanyServices (COMPANY_ID, SERVICE_CODE);
The index will make sure there's no duplicates of (COMPANY_ID, SERVICE_CODE) in your data.

Is each company limited to a single LSP? Is Service_Code unique (or could there be two service codes "PREMIUM" with different Service_IDs)?
CREATE TABLE dbo.Company
(
CompanyID INT PRIMARY KEY
-- , ...
);
CREATE TABLE dbo.LSP
(
LSPID INT PRIMARY KEY,
CompanyID INT FOREIGN KEY REFERENCES dbo.Company(CompanyID) -- UNIQUE?
-- , ...
);
CREATE TABLE dbo.Service
(
ServiceID INT PRIMARY KEY
-- , ...
);
CREATE TABLE dbo.LSP_Service
(
LSPID INT FOREIGN KEY REFERENCES dbo.LSP(LSPID),
ServiceID INT FOREIGN KEY REFERENCES dbo.Service(ServiceID),
PRIMARY KEY (LSPID, ServiceID)
);

Add COMPANY_ID to service table.
If you need rows in Service table to be unique by this id it makes sense to keep a foreign key reference in this table.

Related

How to create an SQL column that can have multiple values?

I created a table in SQL using PostgreSQL called "tenants". Below is the code for the tenants:
create table tentants (
id bigserial not null primary key,
tenant_name varchar(1000) not null,
offices int not null,
number int not null,
email varchar(1000)
I want to include the ability to add multiple values to "office" in case a tenant rents more than one office. I don't want to use JSON for this. I tried creating a related table called "offices" but That could only allow me to add one office per tenant.
What is the best approach for this?
You can use text, that works for me with ids separated by commas like this
4,3,67,2
Anyway the proper approach would be another table and name it tenant_offices
tenant_offices
columns >
tenant_id
office_id (well ofcourse you should have atleast an office table)
You can create an "tenant_offices" table (like you did before), having as structure :
id, tenant_id, office_id,... where id is the primary key of the "tenant_offices" table and tenant_id and office_id are foreign keys.
tenant_id which refers to your tenants table and office_id which refers to your offices table.
Here, the tenant can therefore rent several offices.
Hoping to have enlightened you, or helped !
I assume that the relationship is one-to-many tenant to office (that is, office can be rented only by one tenant)
Then you have to create table offices with a foreign key that points to the tenant:
CREATE TABLE offices (
id bigserial not null primary key,
tenant_id bigserial foreign key references tenants(id))
additional columns if needed
note that in this version you will not retain history of rents (you will have to run update on offices to change tenant_id)
EDIT: In case of a many-to-many relationship (that will also allow us to retain history of rents) we need to create a relationship table:
CREATE TABLE TenantsOffices (
id bigserial not null primary key
tenant_id bigserial foreign key references tenants(id),
office_id bigserial foreign key references offices(id),
start_date datetime,
end_date datetime)
Useful information: https://www.sqlshack.com/learn-sql-types-of-relations/

Database - Am i doing this right?

I'm trying to make a simple database for a personal project, and I'm not sure whether i'm using Primary Keys properly.
Basically, the database contains users who have votes yes/no on many different items.
Example :
User "JOHN" voted YES on item_1 and item_2, but voted FALSE on item_3.
User "BOB" voted YES on item_1 and item_6.
User "PAUL" votes NO on item_55 and item_76 and item_45.
I want to use the following 3 tables (PK means Primary Key) :
1) table_users, which contains the columns "PK_userID" and "name"
2) table_items, which contains the columns "PK_itemID" and "item_name"
3) table_votes, which contains the columns "PK_userID", "PK_itemID", and "vote"
and the columns with the same name will be linked
Does it look like a proper way to use primary keys ? (so the table_votes will have two Primary Keys, being linked to the two other tables)
Thanks :)
Since user can vote for multiple items and multiple users can vote for a single item, you should not create following two primary keys in third table table_votes. Just create them as fields otherwise it will restrict you add only a userId or itemId only once. Yep, you should make them NOT NULL
"PK_userID", "PK_itemID",
No, that's not correct.
There can only be one primary key per table. You can have other columns with unique indexes that could have been candidate keys, but that's not the primary.
I think you'd have three tables:
create table PERSON (
PERSON_ID int not null identity,
primary key(PERSON_ID)
);
create table ITEM (
ITEM_ID int not null identity,
primary key(ITEM_ID)
);
create table VOTE (
PERSON_ID int,
ITEM_ID int,
primary key(PERSON_ID, ITEM_ID),
foreign key(PERSON_ID) references PERSON(PERSON_ID),
foreign key(ITEM_ID) references ITEM(ITEM_ID)
);
It's a matter of cardinality. A person can vote on many items; an item can be voted on by many persons.
select p.PERSON_ID, i.ITEM_ID, COUNT(*) as vote_count
from PERSON as p
join VOTE as v
on p.PERSON_ID = v.PERSON_ID
join ITEM as i
on i.ITEM_ID = v.PERSON_ID
group by p.PERSON_ID, i.ITEM_ID
This looks reasonable. However, I would not advise you to name your primary keys with a "PK_" prefix. This can be confusing, especially because I advise giving foreign keys and primary keys the same name (the relationship is then obvious). Instead, just name it after the table with Id as a suffix. I would recommend a table structure such as this:
create table Users (
UserId int not null auto_increment primary key,
Name varchar(255) -- Note: you probably want this to be unique
);
create table Items (
ItemId int not null auto_increment primary key,
ItemName varchar(255) -- Note: you probably want this to be unique
);
create table Votes (
UserId int not null references Users(UserId),
ItemId int not null references Items(ItemId),
Votes int,
constraint pk_UserId_ItemId primary key (UserId, ItemId)
);
Actually, I would be inclined to have an auto-incremented primary key in Votes, with UserId, ItemId declared as unique. However, there are good arguments for doing this either way, so that is more a matter of preference.

How do I create a composite foreign key going backwards down a relationship?

Sorry about the title, couldn't think of a better way to write it.
Here's my problem...
I have 2 tables in my database [Drawings] and [Revisions];
[Drawings] 1-----* [Revisions]
ProjectId(pk) ProjectId(pk)(fk)
DrawingNo(pk) DrawingNo(pk)(fk)
RevisionNo(pk)
LatestRevision
There is a foreign key in [revisions] referencing [drawings] on [ProjectId] and [DrawingNo].
I need to implement a way of enforcing that the drawings latest revision number equals a corresponding revision number in the revisions table:
... WHERE [Drawings].[LatestRevision] NOT IN (
SELECT [RevisionNo]
FROM [Revisions]
WHERE [Drawings].[ProjectId] = [Revisions].[ProjectId]
AND [Drawings].[DrawingNo] = [Revisions].[DrawingNo])
How would I put something like this into a foreign key?
I need this to work on sql server 2008 express onwards.
Thanks in advance for any help!
Schema:
TABLE Drawings
( ProjectId varchar,
DrawingNo varchar,
LatestRevisions varchar,
...other columns
PRIMARY KEY(ProjectId, DrawingNo)
)
TABLE Revisions
( ProjectId varchar,
DrawingNo varchar,
RevisionNo varchar,
...other columns
PRIMARY KEY(ProjectId, DrawingNo, RevisionNo)
FOREIGN KEY(ProjectId, DrawingNo) REFERENCES (Drawings(ProjectId, DrawingNo))
)
Drawing 'A' can have revision '1', and Drawing 'B' can have a different revision '1',
Revision number by itself is not unique
I will take the schema as follows:
TABLE drawings
( projectid integer,
drawingno integer,
latestRevision integer,
primary key (projectid, drawingno)
)
TABLE revisions
( revisionno integer primary key,
projectid integer,
drawingno integer,
foreign key (projectid, drawingno)
references (drawings (projectid, drawingno))
)
In this case, I would issue:
ALTER TABLE drawings
ADD FOREIGN KEY (latestRevision)
REFERENCES (revisions(revisionNo))
This would mean that every revisions.revisionNo is unique and the column drawings.latestRevision is a foreign key that references the primary key of revisions table, that is, revisionNo.
Please let me know if there are any changes to the schema you have.
Also, the foreign key is enforced only if it is referencing a primary key of another table. If revisions.revisionno is not a primary key or if the primary key constraint is disabled on this column, then the ALTER TABLE .. ADD FOREIGN KEY statement is bound to return an error.
The following structures replaces your tables with views and looks similar to what you describe, except it's instead maintained behind the scenes rather than being an explicit foreign key. I don't know what operations you'd want to support on Revisions, at the moment I only support INSERT:
create table dbo._Drawings (
ProjectId int not null,
DrawingId int not null,
constraint PK_Drawings PRIMARY KEY (ProjectID,DrawingID)
)
go
create table dbo._Revisions (
ProjectID int not null,
DrawingID int not null,
RevisionNo int not null,
_PreviousRevision as CASE WHEN RevisionNo > 1 THEN RevisionNo - 1 END persisted,
_NextRevision int null,
constraint PK_Revisions PRIMARY KEY (ProjectID,DrawingID,RevisionNo),
constraint FK_Revisions_Drawings FOREIGN KEY (ProjectID,DrawingID)
references _Drawings (ProjectID,DrawingID),
constraint CK_RevisionNos CHECK (RevisionNo >= 1),
constraint UK_Revisions_Previous UNIQUE (ProjectID,DrawingID,_PreviousRevision),
constraint UK_Revisions_Next UNIQUE (ProjectID,DrawingID,_NextRevision),
constraint FK_Revisions_Previous FOREIGN KEY (ProjectID,DrawingID,_PreviousRevision)
references _Revisions (ProjectID,DrawingID,RevisionNo),
constraint FK_Revisions_Next FOREIGN KEY (ProjectID,DrawingID,_NextRevision)
references _Revisions (ProjectID,DrawingID,RevisionNo)
)
The above two tables are the "backing store" for the data. The _Revisions table ensures that the revision sequences are strictly monotonically increasing from 1. Each row maintains a foreign key to its immediate preceding and succeeding revisions, except the first and last, for which NULLs substitute (but the unique constraints ensure only one of each exists for each ProjectID,DrawingID combination.
create view dbo.Drawings
with schemabinding
as
select
d.ProjectID,
d.DrawingID,
r.RevisionNo as LatestRevision
from
dbo._Drawings d
left join
dbo._Revisions r
on
d.ProjectId = r.ProjectID and
d.DrawingId = r.DrawingID and
r._NextRevision is null
The above view mimics your asked for Drawings table and would be used for any actual data access. If you wanted to enforce an invariant that each drawing must have at least one revision, you could switch the left join to an inner join and make this an indexed view. You'd need to add a trigger to support INSERTs, in much the same way as the below does for Revisions, which then populates both tables.
create view dbo.Revisions
with schemabinding
as
select
ProjectID,
DrawingID,
RevisionNo
from
dbo._Revisions
This view creates the impression that Revisions is as simple as in your query
create trigger T_Revisions_I
on dbo.Revisions
instead of insert
as
;with SplitData as (
select ProjectID,DrawingID,RevisionNo,RevisionNo-1 as Prev, Seq
from inserted cross join (select 1 union all select 2) t(Seq)
)
merge into dbo._Revisions r
using SplitData s
on
r.ProjectID = s.ProjectID and
r.DrawingID = s.DrawingID and
(
(s.Seq = 1 and r.RevisionNo = s.Prev) or
(s.Seq = 2 and r.RevisionNo = s.RevisionNo)
)
when matched and s.Seq = 1
then update set _NextRevision = s.RevisionNo
when not matched and s.Seq = 2
then insert (ProjectID,DrawingID,RevisionNo) values (s.ProjectID,s.DrawingID,s.RevisionNo)
;
And finally, this trigger is responsible for maintaining the _Revisions structure in the way that the constraints I created above require. The trick is that we use a MERGE statement so that at the same time as we insert the new row, we also update the previous row so that it's _NextRevision column is no longer null and references the row that we're inserting.
More triggers can be added to support more advanced usage.

valid schema for company and suppliers and clients?

I am having a problem in modeling relation between a company and suppliers and clients. Basically in my system the supplier and the client are companies too so I made this schema:
table.company:
id
name
//other fields
table.company_suppliers:
company_id FK table.company.id
supplier_id FK table.company.id
table.company_clients:
company_id FK table.company.id
client_id FK table.company.id
Is this ok?
I would use only one table which will contains all the company and a bit field (called by instance Supplier )which will tell you which are are suppliers too.
Company
Id
Name
IsSupplier (bit)
Fk_IdSupplier --it will relate this supplier to a company on the same table
Or you can create a junction table (many to many)
Company
Id
Name
IsSupplier (bit)
CompanySupplier
fk_IdCompany
fk_IdSupplier
Your basic insight is right--you don't want unrelated tables of clients and suppliers. But you have too many ID numbers.
create table companies (
company_id integer primary key,
company_name varchar(35) not null
);
create table suppliers (
supplier_id integer primary key references companies (company_id)
-- Other supplier columns go here.
);
create table clients (
client_id integer primary key references companies (company_id)
-- Other client columns go here.
);
If you're using MySQL, you'll need to adjust the syntax a little. MySQL doesn't support all the standard SQL syntax for declaring primary keys and foreign keys.

MS SQL Bridge Table Constraints

Greetings -
I have a table of Articles and a table of Categories.
An Article can be used in many Categories, so I have created a table of ArticleCategories like this:
BridgeID int (PK)
ArticleID int
CategoryID int
Now, I want to create constraints/relationships such that the ArticleID-CategoryID combinations are unique AND that the IDs must exist in the respective primary key tables (Articles and Categories).
I have tried using both VS2008 Server Explorer and Enterprise Manager (SQL-2005) to create the FK relationships, but the results always prevent Duplicate ArticleIDs in the bridge table, even though the CategoryID is different.
I am pretty sure I am doing something obviously wrong, but I appear to have a mental block at this point.
Can anyone tell me please how should this be done?
Greaty appreciated!
Don't use a BridgeId column.
Make a composite primary key (aka compound key) from your ArticleId and CateogryId, that will ensure each combination is unique.
Make each column a foreign key to the corresponding table, and that completes your set of constraints.
Ok first you do a unique index on ArticleID, CategoryID.
Then you set up a foreign key constraint on articleID linking it back to the Article table. and then do the same for CategoryID and Catgory table.
Your description sounds like you are creating the PK on the Bridge table and the FK on the other table which is why it wouldn't work.
Expanding on HLGEM's solution you would have something like:
Create Table ArticleCategories
(
Id int not null Primary Key Clustered
, ArticleId int not null
, CategoryId int not null
, Constraint UK_ArticleCategories_Unique ( ArticleId, CategoryId )
, Constraint FK_ArticleCategories_Articles
Foreign Key ( ArticleId )
References Articles( Id )
, Constraint FK_ArticleCategories_Categories
Foreign Key ( CategoryId )
References Categories( Id )
)