I'm designing SQL Server tables with Natural Keys instead of a Surrogate Keys for the Primary Key. The problem I've run into is that it doesn't work well with the Audit table format that I've used with Surrogate Key tables in the past. Typically I'll create an audit table that has the same columns as the table being audited. A trigger on the table being audited writes a new row to the audit table that matches the state of the row before update or delete. To enforce integrity I use both the Surrogate Key and modified date columns as a composite PK for the table. If I don't use a Surrogate Key then I cannot track changes if one of the columns that make up the composite key changes.
Log table example with Surrogate Key:
LogId (PK)
LogType
Data
ModifedDate
ModifedBy
LogAudit Table for Log table with Surrogate Key:
LogId (PK)
LogType
Data
ModifedDate (PK)
ModifedBy
Log table example with Natural Key:
LogType (PK)
Data
ModifedDate (PK)
ModifedBy
LogAudit Table for Log table with Natural Key:
LogType
Data
ModifedDate
ModifedBy
How do you track changes for Natural Key Log table record in the Audit table if LogType or ModifiedDate change?
I think you misunderstood the basic concept here.
If it is true that {LogType, ModifedDate} is a natural key in the Log table (case 2), then it is also true that it is an alternate key (AK) (unique) in the first example.
So the Log table in the first example would look like this.
LogId (PK)
LogType (AK1.1)
Data
ModifedDate (AK1.2)
ModifedBy
So, for a given {LogID} the {LogType, ModifedDate} can not change, and vice versa.
But, this is not what you are trying to accomplish. The answer is probably that your Log table does not have "natural key" in the first place.
EDIT as per comments
As a rule-of-thumb, for creating a row-based audit table:
Copy the original table structure (columns).
Add ChangedAt (datetime) and ChangedBy columns to the table.
Add ChangedAt column to the original PK.
Add copy of PK columns to the table. This is to capture any PK changes.
Add FKs with ON UPDATE CASCADE, ON DELETE NO ACTION. This prevents hard-deletes, so use soft deletes.
Populate from an AFTER INSERT, UPDATE trigger on the original table.
Example 1
Table: {ID, SomeData} ; key: {ID}
audit table: {ID, SomeData, ChangedAt, ChangedBy, ID_CPY} ; key: {ID, ChangedAt}
Example 2
Table: {UserID, UserOrderNo, SomeData} ; key: {UserID, UserOrderNo}
audit table: {UserID, UserOrderNo, SomeData, ChangedAt, ChangedBy, UserID_CPY, UserOrderNo_CPY} ; key: {UserID, UserOrderNo, ChangedAt}
Example 3 (as per your description)
Table: {LogType, ModifedDate, SomeData} ; key: {LogType, ModifedDate}
audit table: {LogType, ModifedDate, SomeData, ChangedAt, ChangedBy, LogType_CPY, ModifedDate_CPY} ; key: {LogType, ModifedDate, ChangedAt}
Hmmmm, that natural key in particular is odd.
There is no restriction for you to have both sets of keys, natural and surrogate.
I would mix both tables i'd use LogID for PK and create a unique index for LogType+ModifiedDate, so that way you have a single field PK for joining tables (which i think is a good thing) and you have your Business/Natural Key (still unique just not Primary) for whatever you need it.
Related
I'm trying to create a foreign key between two tables. Problem is one of those tables has a composite primary key..
My tables are products (one row per product) and product_price_history (many rows per product).
I have a composite key in product_price_history, which is product id and start date of a specific price for that product.
Here's my code :
CREATE TABLE products (
product_id INT IDENTITY(1,1) PRIMARY KEY,
product_name VARCHAR(50) NOT NULL,
product_desc VARCHAR(255) NULL,
product_group_id INT
)
CREATE TABLE product_price_history (
product_id INT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NULL,
price NUMERIC (6,2) NOT NULL
)
ALTER TABLE product_price_history
ADD CONSTRAINT pk_product_id_start_dt
PRIMARY KEY (product_id,start_date)
Now I'm trying to create a foreign key between the products table and the product_price_history table but I can't because its a composite key.
Also it doesn't make sense to add the start date (the other part of the foreign key) to the products table.
What's the best way to deal with this? Can I create a foreign key between these tables? Do I even NEED a foreign key?
My intentions here are
to enforce uniqueness of the product price information. A product can only have one price at any time.
to link these two tables so there's a logical join between them, and I can show this in a database diagram
The foreign key on the product_price_history table should only include product_id. Your target is to ensure that any entry product_price_history already has "parent" entry in products. That has nothing to do with start_date.
The way I see this situation, in theory, fully normalized version of the tables would have to have current_price as unique value in products table. And the product_price_history is simply a log table.
It's not necessary to do it this way, with a physical field, but thinking from this perspective helps to see where your tables model is slightly de-normalized.
Also, if you make product_price_history table anything but simple log table, how do you ensure that new start_date is newer than previous end_date? You can't even express that as a primary key. What if you edit start_date later? I would even think to create different compaund key for product_price_history table. Perhaps product_id+insert_date or only auto-increment id, while still keeping foreign key relationship to the products.product_id.
I have a database in which i have two tables:
CREATE TABLE Transactions (
ID BIGINT IDENTITY(1,1) NOT NULL,
AccountID BIGINT NOT NULL,
Amount BIGINT NOT NULL,
CONSTRAINT PK_Transactions PRIMARY KEY CLUSTERED (ID ASC,AccountID ASC),
CONSTRAINT FK_Transaction_Account FOREIGN KEY (AccountID) REFERENCES Accounts(ID)
);
CREATE TABLE Accounts (
ID BIGINT IDENTITY(1,11) NOT NULL,
Balance BIGINT NOT NULL,
CONSTRAINT PK_Accounts PRIMARY KEY (ID)
);
Transactions are inserted to their table by a stored procedure i wrote, so that two rows are generated when Account 1 transfers 25 "coins" to Account 21:
ID | AccountID | Amount
-------------------------
1 | 1 | -25
-------------------------
1 | 21 | 25
In the above schema, i want the first row to reference the bottom row based on ID and the AccountID being unequal to the AccountID of the bottom row.
And vica versa.
What i want to do would look something like this:
CONSTRAINT FK_Transaction_Counterpart FOREIGN KEY (ID) REFERENCES Transactions(ID) WHERE thisRow.AccountID != referencedRow.AccountID
I haven't found this possibility in the documentation on the table constraints.
So both out of curiosity and intent to use this i ask, is this possible? And if yes, how?
Edit:
Answers reflect that this is not possible, and i should adjust my design or intentions.
I think i will settle with assigning the two transaction rows to each other in the functional code.
A traditional foreign key can't be conditional (i.e. no WHERE clause attached). In your case, I'd probably just make sure that the inserts are atomic (in the same transaction) so that there'd be no possibility of only one of them inserting.
If the data model you are trying to implement is:
One transaction (ID) has two and only two entries in table Transactions
For the two rows of a given Transaction ID, the AccountIDs cannot be the same
Then one perhaps overly-complex way you could enforce this business rule within the database table structures would be as follows:
Table Accounts, as you have defined
Table Transactions, as you have defined
New table TransactionPair with:
Columns (all are NOT NULL)
ID
LowAccountID
HighAccountID
Constraints
Primary key on ID (only one entry per Transaction ID)
Foreign key on (ID, LowAccountID) into Transactions
Foreign key on (ID, HighAccountID) into Transactions
Check constraint on the row such that LowAccountID < HighAccountID
Process:
Add pair of rows to Transactions table
Add single row to TransactionPair referencing the rows just added
If that row cannot be added, something failed, roll everything back
Seems neat and tidy, but quite possibly overly complex. Your mileage may vary.
I am creating a relationship between 2 tables:
The relationship I like to form is between the Inventory an InventoryExtended tables.
The primary key for the Inventory table is InvID (Inventory ID).
The reason why I created the InventoryExtended is becauses only 1% of the inventory items in the Inventory table will need additional or extended fields, the rest will not.
Instead of adding these additional fields in the Inventory table where 99% will be blank for 50 additional fields that I need I decided to create an InventoryExtended table and store the 50 fields there.
The relationship between the Inventory an InventoryExtended table will be 1 to 1.
Meaning, for the 1% of the records in the Inventory table , the InvId will be the same as the InvId in the InventoryExtended table.
My question is that should the InvID in the InventoryExtended table be a FK (Foreign Key) or should it be a PK and a FK?
I am thinking it should be a PK and a FK as there the InvID will be unique in the InventoryExtended table.
Thanks in advance.
You are correct.
The InvID should be a PRIMARY KEY and a FOREIGN KEY as it will be unique in the InventoryExtended table.
This type of relationship is indeed 1:1 or (more accurately) 1::0..1, as only some of the rows in the Inventory table will have a related row in InventoryExtended.
Also note that the InventoryExtended (InvID) should not have the IDENTITY property, even if the Inventory (InvID) has it.
The InvID will be both a PK and FK for the extended table.
If you know in advance (meaning when you attempt to fetch data) whether or not a particular inventory type will have the extended data, for such records you can even skip the original table altogether and simply use two disjoint tables smallInventory and bigInventory such that no records of one are present in the other.
I have a table named Books which contains 3 columns.
TableName: Books
Columns: BookId (PK), BookName, Book_Publisher_XRef_Id (FK), IsInternal
I have two tables that contains publisher information. Both these tables have different set of columns.
TableName: InternalPublishers
Columns: PublisherId (PK), PublisherName, ....
TableName: ExternalPublishers
Columns: PublisherId (PK), PublisherName, ....
I have a link table that contains information about which book belongs to which publisher. One book can have multple publishers.
TableName: Books_Publishers_XRef
Columns: Book_Publisher_XRef_Id (PK), PublisherId
If I want to create a Foreign Key constraint on PublisherId, I need to create sort of Composite Foreign Key constraint which I am not sure can be created.
So in this scenario, what is the best way to achieve FK on PublisherId in Books_Publishers_XRef table?
Break Books_Publishers_XRef table in 2 tables i.e. one for Internal Publishers and another one for External Publishers and have 2 columns in Books table for Books_Internal_Publishers_XRef and Books_External_Publishesr_XRef tables?
Don't create FK on Publisher_Id column and leave the design as it is?
Create composite FK by adding Publisher_Type_Id column in Books table and Books_Publishers_XRef table where if Publisher_Type_Id = 1, it belongs to Internal_Publishers table and Publisher_Type_Id = 2, it belongs to External_Publishers table ? (Not sure if this is possible)
Some other schema design?
Please advise.
Don't divide your data amongst two tables: InternalPublishers, ExternalPublishers. Create one table and have a bit field to determiner whether they are internal or external. Something like this:
create table Publisher
(
PublisherId int not null primary key clustered,
PublisherName varchar(100) not null,
IsInternal bit not null
)
go
That way you can easily create your foreign key reference. After all, you seem to have this same design for Books, keep that going to publishers.
Keep all common columns in the Publisher table.
Subtype tables have only columns specific to each one.
I have an oracle db which has no Foreign Keys, all table relations are handled by the software. For example, table Customer with columns Customer.customercode, Customer.Name where customercode is the primary key and table Order with columns Order.ordercode (PK), Order.customercode where customercode has no foreign key constraint. So far the application handles all transactions and takes care of all the table relations so that the data are consistent. I need to change this to a proper relational DB implementation, so I need to modify Order.customercode to be a FK from table Customer. Any sqlplus statement to do this without losing my data?
In Oracle, creating a foreign key would never lose any data, but it will fail if the data doesn't correspond to the new constraint. Assuming your data is OK, you can use an alter table statement:
ALTER TABLE order
ADD CONSTRAINT order_customer_fk FOREIGN KEY (customercode)
REFERENCES customer(customercode)