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

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.

Related

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'

How to count values for one id in table and put data to other table sql

I have problem with summing values... Tables are presenting like this:
create table clients
(
id_c INTEGER not null,
name VARCHAR2(20),
age INTEGER,
address VARCHAR2(20),
price number,
Primary key (id_c)
);
create table PRODUCTS
(
id_p NUMBER not null,
name_product VARCHAR2(30),
price NUMBER,
duration NUMBER,
primary key (id_p)
);
create table TRANSACTIONS
(
id_t NUMBER not null,
id_c NUMBER not null,
id_p NUMBER not null,
td_c NUMBER,
primary key (ID_t),
foreign key (ID_c) references CLIENTS (ID_c)
);
now i want to sum up all costs of products (translations.td_c) for each client (translations.id_c), and put that value to table clients into price row. The point is to take the same ID of client (ID_C) and sum up every row that have that ID_C in transactions table, sum up for that client every value of td_c, and put it into table clients to price column... I don't know how to do that, every time when i trying to make some UPDATE code, there are errors...
Try it with a subquery:
UPDATE clients
SET price = (SELECT sum(transactions.td_c)
FROM transactions
WHERE transactions.id_c = clients.id_c);
But your design is probably problematic. clients.price entirely depends on transactions and therefore shouldn't exist materialized like that.
I'll immediately go out of sync, if there are any changes in transactions. E.g., if you insert a new row into transactions for some customer "John" with an td_c of +100, the price for "John" in the clients table will be a 100 short until you update it again.
Consider using a view to have the price and the client data joined, always selecting the current price from transactions. E.g.:
CREATE VIEW clients_with_price
AS
SELECT c.id_c,
c.name,
c.age,
c.address,
s.price
FROM clients c
LEFT JOIN (SELECT sum(t.td_c) price,
t.id_c
FROM transactions t
GROUP BY t.id_c) s
ON s.id_c = c_id.c;
Then drop the price column in clients.

Normalizing a table with duplicate rows and many-to-many relationships

I am designing the database for an accounting system, currently working on the Expenses table.
According to IRS rules, whenever you update a row in any accounting table, you need to cancel out the existing row by negating its values, and create a new row with the modified information, like so:
Set the row's Status flag to "Modified"
Create an identical copy of this row, with all Money fields negated, so that the sum of the two rows is 0
Create a 3rd row, identical to the first one, with the modified data
Each expense has an identity field called ID for internal identification purposes, and an ExpenseID field, which identifies the transaction to the users. The two cannot be the same, because
ExpenseID can be repeated twice if the transaction was modified and its row was duplicated.
ExpenseIDs MUST be consecutive and NEVER have gaps, while identity fields can skip numbers if a transaction is rolled back and the identity is not reseeded.
In general, I believe the primary key should have no business meaning whatsoever.
My problem is that there are other tables used to link these expenses Many-To-Many to other objects in our system. E.g.: each expense can be linked to documents, folders, users, etc.
So it looks something like this:
create table Expenses (
ID int not null identity(1,1),
ExpenseID int not null,
Amount Money not null,
Status tinyint not null,
[...]
)
create table Expenses_Users (
ExpenseID int not null,
UserID int not null
)
alter table Expenses_Users add constraint FK_Expenses_Users_Expenses
foreign key (ExpenseID) references Expenses (ID)
alter table Expenses_Users add constraint FK_Expenses_Users_Users
foreign key (UserID) references Users (ID)
Now, because of the IRS guidelines, I have to duplicate not only rows in the Expenses table, but also in Expenses_Users, and any other table that links Expenses to other tables.
I have two ideas on how to solve this:
Option One: Normalize Expenses like this:
create table Expenses (
ID int not null identity(1,1),
ExpenseID int not null,
Status tinyint not null,
[...]
)
create table ExpensesNormalized (
ExpenseID int not null,
Amount Money not null
)
alter table ExpensesNormalized add constraint FK_ExpensesNormalized_Expenses
foreign key (ExpenseID) references Expenses(ExpenseID)
This means I'll only have to link external tables to Expenses, not ExpensesNormalized. Also, when updating an expense, I'll only duplicate and negate the data in ExpensesNormalized, which means I'll have far less redundant data in the Expenses table.
However, I'll have to use a JOIN clause every single time I SELECT from Expenses. I fear a performance hit because of this.
Option Two: Use the same tables I use now, but have the field Expenses_Users.ExpenseID point to the field Expenses.ExpenseID. This means that I won't have to duplicate any external objects because they'll point to ExpenseID, which may occur several times.
However, this will not be a real foreign key because SQL Server does not allow foreign keys to non-unique fields, so I'll have to implement foreign key logic in a trigger.
I'm having a hard time deciding between these two options. Any feedback would be appreciated.

Table design for efficiency [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I have a requirement to develop a system. Part of the system is that, every day, a payment for a person is calculated (A person gets given $x.xx per day, based on some rules), and stored in a Transaction table, which at the moment, consists of the personId, the amount and the date.
CREATE TABLE DailyTransaction
(
DailyTransaction INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
PersonId INT NOT NULL,
TransactionDate DATE NOT NULL
)
There are 8,000 people in the system right now, so every day, 8,000 rows are written via a process to the table.
I think, storing the date, is repeating too much. And there may be queries on that date later. So, I'd like to create a 'DailyRun' table, which contains a date, and and id (and more columns later, if needed). So, when I do a daily payment insert run to populate the 8,000 rows, I first create a DailyRun record with the date, and then assign that ID for that row, to the Transaction table.
CREATE TABLE DailyRun
(
DailyRunId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
RunDate DATE NOT NULL
)
CREATE TABLE DailyTransaction
(
DailyTransaction INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
PersonId INT NOT NULL,
DailyRunId INT NOT NULL
)
So, if I wanted all the rows for a specific date, I find the date in the DailyRun table, and then get all the record associated to it, via a foreign key, to the Transaction Table.
That seems OK so far (Unless someone sees an issue?)
BUT, now the issue.
Another requirement is that during the day, an ad-hoc payment can be added to the transaction table. So, an extra one-off payment. But, it doesn't get created as a Run. So, no row in the DailyRun table. An idea is a nullable DailyRun ID in the Transaction table, but ... when I need to get all the transactions for a specific date - well, there's no date field... and now RunID set.. so it won't be found.
What I can do is allow for Null RunID, and also have a nullable 'AdHocPaymentDate' field. So, if it's an Adhoc payment, I can leave RunID null, and populate AdHocPaymentDate?
And if it's a Scheduled payment run, I populate the RunID, and leave the AdHocPaymentDate as null?
But, aren't nullable fields slow, or not recommended for some reason?
Is there a better way to handle this? Maybe a separate 'Ad Hock Payment' table, which holds the ad hoc payments, in addition to the table that holds the regular daily payments?
It seems that there is a business requirement to distinguish the ad hoc payments from the daily runs. I guess you could work with this kind of solution. Using 'DailyRun' and 'AdHoc' as transaction types and then making the business intelligence insert the transaction properly from daily runs (service) and adhocs (user interaction)
CREATE TABLE [TransactionType]
(
[Id] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Name] NVARCHAR(40)
)
CREATE TABLE [Transaction]
(
[Id] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[TransactionTypeId] INT NOT NULL REFERENCES TransationType
[PersonId] INT NOT NULL REFERENCES person
[Date] DATE NOT NULL
)
I'm not sure how to approach your concern about repeating values in rows. The best I can offer is that there's no principle of normalization that says, "Hunt for values that appear in more than one row. If you find them, replace then with integers and another table." Anyway, if you do that, you'll have row after row of repeating integers.
Also, since both "run" and ad hoc payments have a date, there's no logical reason to remove the date from the table of transactions.
How I'd start . . .
By your description, this isn't a table of transactions. It's a table of payments.
create table payments (
payment_id integer not null,
payment_date date not null,
payment_amount decimal(12, 2) not null
check (payment_amount > 0),
-- For identifying a(d hoc) and r(un) payments
payment_type char(1) not null
check (payment_type in ('a', 'r')),
person_id integer not null, -- references a table not shown
primary key (payment_id),
unique (payment_id, payment_type),
unique (payment_date, payment_type, person_id)
);
The unique constraint on {payment_date, payment_type, person_id} is important. Your description seems to require no more than one "run" payment per date per person, and no more than one ad hoc payment per date per person. In any case, you need a unique constraint on the real data in addition to a primary key constraint on the surrogate ID number.
The unique constraint on {payment_id, payment_type} is also important. If you need to store more details about either run payments or about ad hoc payments, you can build a table like this.
create table payments_ad_hoc (
payment_id integer not null,
payment_type char(1) not null
default 'a'
check (payment_type = 'a'),
other_columns_go_here char(1) not null
default 'x',
primary key (payment_id),
foreign key (payment_id, payment_type)
references payments (payment_id, payment_type)
);
The combination of the check constraint and the foreign key constraint guarantees that rows in this table will reference an ad hoc payment, not just any payment. Structure for "run" payments would be similar.

No unique constraint matching given keys

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