No unique constraint matching given keys - sql

I have two tables in in PostgreSQL -- customer and creditcard. A customer can have multiple credit cards, and different customers can have the same credit card associated with their account. However, a customer should not be able to have the same credit card associated with their account multiple times. Thus, in the creditcard table, the combination of the cardnumber and custid represents a unique key, which I want to use for the primary key.
However, I receive the following error: ERROR: there is no unique constraint matching given keys for referenced table "creditcard". Adding UNIQUE to the end of the cardnumber declaration in the creditcard table resolves this issue, but I want the same card number to be able to be associated with multiple accounts, thus this is not an acceptable solution. Most of my searches on this topic turn up discussions regarding foreign keys, but I don't see any issue with my custid foreign key. Could someone help me understand what I am doing wrong?
Note that this is not for a real product and is just for me to learn about SQL -- I am not going to store any real CC numbers with this setup.
CREATE SEQUENCE customeridseq INCREMENT BY 1 MINVALUE 100;
CREATE TABLE customer (
id INT NOT NULL PRIMARY KEY default nextval('customeridseq') --- this is a surrogate key (multiple customers can potentially have the same name...)
,username VARCHAR(20) NOT NULL UNIQUE
,fname VARCHAR(20) NOT NULL
,lname VARCHAR(20) NOT NULL
,emailaddress VARCHAR(20) NOT NULL UNIQUE
,birthdate INTERVAL YEAR TO MONTH NOT NULL
,passwordhash VARCHAR(64) NOT NULL -- assuming SHA-512 hash producing 64 bytes of data
);
CREATE type cardtype AS enum (
'Discover'
,'Visa'
,'MasterCard'
,'AmericanExpress'
);
CREATE TABLE creditcard (
cardnumber INT NOT NULL
,custid INT NOT NULL REFERENCES customer(id)
,cardtype CARDTYPE NOT NULL
,expirationdate INT NOT NULL
,billingaddress VARCHAR(40) NOT NULL
,PRIMARY KEY (custid, cardnumber)
);

I don't have a database handy, but this is how I would layout the tables to allow a customer to have more than one cc, and different people having the same cc, but not allow the same person to have the same cc more than once. The solution is normalization. There are different levels of it and typically you want to go to at least 3 sometimes higher depending on the needs. There's for sure some art and science to doing it right based on the situation.
CREATE TABLE customer
(
customer_id PRIMARY KEY
);
CREATE TABLE creditcard
(
creditcard_id PRIMARY KEY,
cardnumber --UNIQUE
);
create table customer_creditcard
(
customer_creditcard_id PRIMARY KEY,
customer_id,
creditcard_id --UNIQUE (customer_id, creditcard_id)
);

Related

What is the best way to construct database tables for many to many relationship (with additional condition)?

Business problem: Suppose that we have a few medical centers and doctors, who work in these centers. Obviously, many doctors can work in one center. But also one doctor can work in many centers at the same time. And we have to store information about who is the head doctor of each medical center (each medical center can have only one head doctor and one doctor can be the head doctor in multiple centers).
Question: What is the best way to construct database tables to serve these business requirements?
I see two variants (described below) but if you see more, please, let me know.
Variant 1
In this variant, we store information about the head doctor in the join table jobs. I see two disadvantages here:
the column jobs.is_head will contain false in most cases and it looks strange (and looks like we store unnecessary information).
we need somehow to restrict adding two head doctors into one center.
create table doctors
(
id bigint not null
constraint doctors_pk
primary key,
name varchar not null
);
create table medical_centers
(
id bigint not null
constraint medical_centers_pk
primary key,
address varchar not null
);
create table jobs
(
medical_center_id bigint not null
constraint centers_fk
references medical_centers,
doctor_id bigint not null
constraint doctors_fk
references doctors,
is_head boolean not null,
constraint jobs_pk
primary key (doctor_id, medical_center_id)
);
Variant 2
In this variant, we store information about the head doctor in medical_centers table. Two disadvantages again:
we have two types of relationships between tables now: many to many and one to many (because one doctor can be the head doctor in multiple centers), which is a bit complicated, especially considering that I want to use this schema through ORM framework (JPA implementation).
we have to somehow restrict setting doctor as a head doctor if this doctor is not working in this center.
create table doctors
(
id bigint not null
constraint doctors_pk
primary key,
name varchar not null
);
create table medical_centers
(
id bigint not null
constraint medical_centers_pk
primary key,
address varchar not null,
head_doctor_id bigint
head_doctor_id_fk
references doctors
);
create table jobs
(
medical_center_id bigint not null
constraint centers_fk
references medical_centers,
doctor_id bigint not null
constraint doctors_fk
references doctors,
constraint jobs_pk
primary key (doctor_id, medical_center_id)
);
It is a trade - off, but operational complexity or some(?) storage. I think variation 1 looks good. If you want to change a little bit you can add another table called "med_cen_heads" with unique constraint on med_center_id column. Thus, we prevent adding a second doctor to same medical center. The hard part is checking if the head doctor works in the medical center or not before insert.
INSERT INTO med_cen_heads
SELECT medical_center_id, doctor_id
FROM jobs
WHERE EXISTS (SELECT 1 FROM jobs WHERE medical_center_id = 'medical_center_id_to_insert' and doctor_id 'doctor_id_to_insert');
Also, you can create "before insert trigger" to check if values exist in jobs table.
It could look like this:
create table doctors
(
id bigint not null
constraint doctors_pk
primary key,
name varchar not null
);
create table medical_centers
(
id bigint not null
constraint medical_centers_pk
primary key,
address varchar not null
);
create table jobs
(
job_id serial,
medical_center_id bigint not null
constraint centers_fk
references medical_centers,
doctor_id bigint not null
constraint doctors_fk
references doctors
);
create table med_cen_heads
(
medical_center_id bigint unique not null,
doctor_id bigint not null
);
This will save you from unnecessary storage but, BOOLEAN data type is just 1 byte. Let' s assume you have 1 billion med_center - doctor pairs(I don' t think you will have). In this way you only store 0.93132 GB more for your extra column. Of course, becuse there is only one head doctor in a medical center this column will be skewed. Yet when you query normal doctors this column will not be your concern, you should use "doctor_id" or any other columns.
In short, from my point of view stick with variation 1 with this small change:
create unique index unique_row on jobs(medical_center_id) where is_head;
Check you cannot add a second head doctor to a medical center.

SQL/ORACLE- FOREIGN KEY using two columns from other tables

I'm trying to create an table in SQL*Plus that consults two columns from another table. For example,
If table A looks something like this:
CREATE TABLE Customers
(Customer_ID int NOT NULL PRIMARY KEY,
NAME Varchar(30) NOT NULL,
PHONE Varchar(12) NOT NULL,
OUTSTANDING_FEES Varchar(10) NULL);
And if my table B looks something like this:
CREATE TABLE Customer_Fees
(Fee_ID int NOT NULL PRIMARY KEY,
FEE_TYPE Varchar(20) NOT NULL,
AMOUNT Varchar(10) NOT NULL,
CUSTOMER_ID int NOT NULL);
I want to populate the OUTSTANDING_FEES in table A with the AMOUNT in table B, where the CUSTOMER_ID matches among the tables. For my purposes I can assume that any single Customer_ID in table B will only appear once in the table.
I've tried creating both tables, with the table A OUTSTANDING_FEES field being null and then making it a FOREIGN KEY that references table B's AMOUNT field, but it's not working since I need to make sure it also cross references the CUSTOMER_ID fields in both tables.
Thanks if you can help!
You can not create foreign key on a non-primary column from another table(s).
You'll have to create FK on FEE_ID.
CREATE TABLE Customers (
Customer_ID int NOT NULL PRIMARY KEY,
NAME Varchar(30) NOT NULL,
PHONE Varchar(12) NOT NULL,
OUTSTANDING_FEES Varchar(10) FOREIGN KEY REFERENCES Customer_Fees(FEE_ID)
);
You can use AMOUNT field in your select clause.
Select A.Customer_ID,B.AMOUNT from Customers A Join Customer_Fees B
on A.OUTSTANDING_FEES = B.FEE_ID
There are several things wrong with the data model posed in the question.
Defining OUTSTANDING_FEES and AMOUNT as varchar2columns is bad data modelling as both are surely intended to be numeric (monetary) values. Good practice is always to use the most appropriate datatype for the attribute we're modelling.
Building a foreign key between OUTSTANDING_FEES and AMOUNT is wrong because they are not unique identifiers. The amount of money owed by one customer can be the same as the amount of money owed by any other - even all - customers (at the start of term all students owe the same amount of tuition fees). So, a foreign which "references the CUSTOMER_ID fields in both tables" is all that is needed.
The data model doesn't provide any attribute which allows us to distinguish between fees which have been paid and fees which haven't.
The questioner states that "I can assume that any single Customer_ID in table B will only appear once in the table" but in real life we would expect Customers to have multiple fee records, unpaid and paid. Why not model that? Otherwise if there is truly a 1:1 relationship between Customer and Fee then there is no need for two tables.
So, here is an improved model. It uses proper datatype for monetary values; it enforces the foreign key between the two tables using CUSTOMER_ID; consequently it supports a one-to-many relationship between Customer and Fee; finally it tracks paid and unpaid fees.
create table customers
( customer_id integer not null constraint cust_pk primary key
, name varchar2(30) not null
, phone varchar2(12) not null
)
/
create table customer_fees
( fee_id integer not null constraint fees_pk primary key
, fee_type varchar2(20) not null
, amount number not null
, invoice_date date not null
, paid_date date null
, customer_id integer not null constraint fees_cust_fk references customers
)
/
Ah, but what about OUTSTANDING_FEES? Well, that information is derivable from the data in the two tables. There are many ways of writing this query, this approach is just a choice:
select cust.customer_id
, cust.name
, cust.phone
, fees.outstanding_fees
from customers cust
left outer join
( select fees.customer_id
, sum(case when fees.paid_date is null then fees.amount
else 0 end) as outstanding_fees
from customer_fees fees
group by fees.customer_id ) fees on fees.customer_id = cust.customer_id
/
Generally it is better to calculate aggregated values on demand rather than re-calculate them in every transaction. It scales better, certainly with OLTP volumes of data; the physics of a data warehouse is different, but I don't think that's what we're dealing with in this case.

Using a single attribute as both a primary key and a foreign key in a subtype table

I have a supertype table (from ERD) called Card and this table has two subtypes: Credit Card and Debit Card. I have a Primary Key in my table Card named as CardID.
Will it be correct if I assign the Primary Key of Card as both the foreign key and primary key of Credit Card and Debit Card?
How do import the primary key of a supertype table to its subtypes (the code)? Do I simply do this?
CREATE TABLE [Credit Card] (
CreditCardNumber varchar(50) NOT NULL Primary Key,
CreditCardNumber varchar(50) NOT NULL Foreign Key REFERENCES Card(CardID)
)
See Doc\SQL\T-SQL
CREATE TABLE [Credit Card] (
CreditCardNumber varchar(50) NOT NULL Primary Key,
Foreign Key (CreditCardNumber) REFERENCES Card(CardID)
)
If the credit card table is considerably different in structure than the debit card one, I suppose it makes sense to do the keys the way you are describing. You have to balance for yourself whether this solution is better than to just have a card table with all fields of either credit or debit card.
The way you have written your query is like saying you have two different column with the same name, as GuidoG stated. You want something like this:
CREATE TABLE [Credit Card] (
CreditCardNumber varchar(50) NOT NULL Primary Key,
....(other cols)....,
CONSTRAINT FK_CreditCard_Card Foreign Key (CreditCardNumber) REFERENCES Card(CardID)
)
I'd like to add that the nature of your wondering about the best solution may be closely related to "polymorphic associations", check a bit around for it.
In response to your comment: The only thing not correct from the things you are saying is the "but" word. It seems to have gotten into your mind that it is "strange" if a column is both PK and FK; it is not. They are two totally irrelevant things, and SQL server couldn't care less. The PK makes sure each row has a different value in this column. The FK makes sure the value must exist in the possible values of a column from another table. No conflicts at all; except a possible philosophical/design conflict, which of course only happens in YOUR mind (not the sql server's structure), and which my response (and many others') covered already.
I guess you want something like this
create table dbo.[Credit Card] (
CreditCardNumber varchar(50) not null,
other columns ...
constraint PK_CreditCardNumber primary key (CreditCardNumber),
constraint FK_CreditCard_Card foreign key (CreditCardNumber) references Card(CardID)
)
This will only work if the column CardID in table Card has the same definition as the column CreditCardNumber in table Credit Card
Just from looking at the names this might not be the case ?
Most of the times it is better to use a discrimator field and just one table.
For example
create table dbo.Card (
CardNumber varchar(50) not null,
CardType char(1) not null check (CardType in ('C', 'D')),
CreditCardColumn1 varchar(50) null,
CreditCardColumn2 varchar(50) null,
DebitCardColumn1 varchar(50) null,
constraint PK_CardNumber primary key (CardNumber),
)
The check constraint will make sure only C or D can be stored in this colum.
Both columns for Credit Card and Debet Card have to be put into this table.
The query that you write should be different for each type, the table will always be the same
Now when you need all creditcards just do
select c.CardNumber,
c.CardType,
c.CreditCardColumn1,
c.CreditCardColumn2
from dbo.Card c
where CardType = 'C'
and for all debitcards just do
select c.CardNumber,
c.CardType,
c.DebitCardColumn1
from dbo.Card c
where CardType = 'D'

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.

Can I use SQL to model my data?

I am trying to develop a bidding system, where an item is listed, and bidders can place a bid, which includes a bid amount and a message. An item may have an arbitrary number of bids on it. Bidders should also be able to see all the bids they have made across different items.
I am unfamiliar with SQL, so am a little unsure how to model this scenario. I was thinking the following:
A User table, which stores information about bidders, such as name, ID number, etc.
A Bid table, which contains all the bids in the system, which stores the bidder's user ID, the bid amount, the bid description.
A Job table, which contains the User ID of the poster, an item description, and then references to the various bids.
The problem I am seeing is how can I store these references to the Bid table entries in the Job table entries?
Is this the right way to go about approaching this problem? Should I be considering a document-oriented database, such as Mongo, instead?
You're describing a many-to-many relationship. In very simplified form, your tables would look something like this:
user:
id int primary key
job:
id int primary key
bids:
user_id int
job_id int
primary key(userid, job_id)
foreign key (user_id) references user (id)
foreign key (job_id) references job (id)
basically, the bids table would contain fields to represent both the user and the job, along with whatever other fields you'd need, such as bid amount, date/time stamp, etc...
Now, I've made the user_id/job_id fields a primary key in the bids table, which would limit each user to 1 bid per job. Simply remove the primary key and put in two regular indexes on each field to remove the limit.
SQL will work fine like you have it set up... I would do:
create table usertable (
userID integer unsigned not null auto_increment primary key,
userName varchar(64) );
create table jobtable (
jobID integer unsigned not null auto_increment primary key,
jobDesc text,
posterUserRef integer not null );
create table bidtable (
bidID integer unsigned not null auto_increment primary key,
bidAmount integer,
bidDesc text,
bidTime datetime,
bidderUserRef integer not null references usertable(userID),
biddingOnJobRef integer not null reference jobtable(jobID) );
Now you can figure out whatever you want with various joins (maximum bid per user, all bids for job, all bids by user, highest bidder for job, etc).