I have two tables in my SQL Server database, Foo and Bar. Table Foo is like so:
+-------+
| Foo |
+-------+
| Id |
| Type |
| Value |
+-------+
The table has values like:
+----+--------+-----------+
| Id | Type | Value |
+----+--------+-----------+
| 1 | Status | New |
| 2 | Status | Old |
| 3 | Type | Car |
| 4 | State | Inventory |
| 5 | State | Sold |
+----+--------+-----------+
The table Bar is like so:
+----------+
| Bar |
+----------+
| Id |
| TypeId |
| StatusId |
| StateId |
+----------+
Where TypeId, StatusId and StateId are all foreign key'ed to the Foo table.
But I want to put a condition on each foreign key where they can only key to the Foo
ids related to it's type. For example, the TypeId column can ONLY foreign key to id
3 on the Foo table. Or the StatusId column can ONLY foreign key to ids 1 or 2.
I know there is a check function in SQL Server but I'm unsure on how to use it correctly. I
tried to do something like this:
CREATE TABLE TEST.dbo.Bar
(
Id int PRIMARY KEY NOT NULL IDENTITY,
TypeId int NOT NULL CHECK (Type='Type'),
CONSTRAINT FK_Bar_Foo_Type FOREIGN KEY (TypeId) REFERENCES Foo (Id, Type)
)
CREATE UNIQUE INDEX Bar_Id_uindex ON TEST.dbo.Bar (Id)
But this didn't work. What am I doing wrong?
The check constraints you are referring to are only used to limit the type of information stored in a key or non key column. So, if you don't want a key column to have a negative value (lets say its a price column, and there is never a negative price) you will use Check constraint.
To better understand the concept of primary and foreign keys:
Primary key uniquely identifies each record in a table.
Foreign key is a value in some table which is a unique identifier (and can also be a primary key) in another table. This means that Foreign key can repeat many times in the table in which it is a foreign key in, and it will definitely be unique in the table that it is created from ( in the table that gives meaning to it).
Now coming to your question, you probably need to use the concept of composite keys. A composite key is basically a group of two or more values that uniquely identify a record, because you cannot enforce limitations on foreign keys in the way you are intending to do, because that defeats the very purpose of a key. Handle some issues with type of data stored in your keys at the application layer instead of database layer.
Looking at the problem in this manner will conceptually resolve some design flaws with your tables as as well.
Related
Let's say I have a Persons and Books table that were associated.
PERSONS TABLE BOOKS TABLE
uid | userCode | name id| name | owner
------------------------- --------------------------
1 | 0xc! | john 1 | book foo | 0xc!
2 | li5$ | doe 2 | foo book | li5$
3 | 1y&t | temp 3 | ddia | 0xc!
Currently persons.usercode serves as the primary key and hence the foreign key on associated tables. I would like to change the primary key of the persons table to persons.uid. So now I want the books table to look like
PERSONS TABLE BOOKS TABLE
uid | usercode | name id| name | owner
------------------------- --------------------------
1 | 0xc! | john 1 | book foo | 1
2 | li5$ | doe 2 | foo book | 2
3 | 1y&t | temp 3 | ddia | 1
Dropping and adding the new primary key constraint shouldn't be a problem. However, how do I go about updating the entire books.owner column with the new primary key if I have over 10,000 rows in the books table
You need to drop/disable the current foreign key & re-add it. You may also need to find out the name of that primary/foreign key constraint before dropping.
ALTER TABLE "PERSONS"
DROP CONSTRAINT "primary_fkey"
UPDATE BOOKS bk SET owner=(SELECT uid FROM PERSONS WHERE userCode = bk.owner);
ALTER TABLE "PERSONS"
ADD CONSTRAINT "primary_fkey"
FOREIGN KEY ("uid")
REFERENCES BOOKS("owner")
ON UPDATE CASCADE;
I'm looking for a Database solution for dealing with scenario like this:
A lot tables: TableA, TableB, TableC ... share some attributes(fields), I want to store these shared attributes in a table, let's call it Shared Table.
like this:
TableA:
| KeyA | Shared Attr.1 | ... | Other Attr. |
| A_1 | A_SValueA1 | ... | A_OValueA1 |
TableB:
| KeyB | Shared Attr.1 | ... | Other Attr. |
| B_1 | B_SValueA1 | ... | B_OValueA1 |
Shared Table:
| KeyShare | EntityType | Shared Attr.1 | ... |
| A_1 | A | A_SValueA1 | ... |
| B_1 | B | B_SValueA1 | ... |
Of course I'll create one table to store specific attributes for each table.
But I need to solve this problem, Table C, which I also want to store its shared attributes in SharedTable, has composite keys, like this:
TableC:
| KeyC1 | KeyC2 | Shared Attr.1 | ... | Other Attr. |
| C1_1 | C1_2 | C_SValueA1 | ... | C_OValueA1 |
So I can't deal with it just like TableA and TableB.
Is there any good design to deal with Composite Keys in table like C?
I'm sure this is not some new questions, a lot people may have faced and solved it, but I searched a lot and failed to find existing questions.
In fact, this scenario is designed as an alternative of E-A-V antipattern, because TableA/B/C are of the same class, so they have shared attributes, and they act as subclass, so they have special attributes.
In class level, they are same things, they can be compared, sorted, calculated, so they are designed to be placed in one table.
If you really want this (and I concur with the other answer that this is a bad[tm] idea) - turn the solution upside-down:
Don't store the key to the particular entity in the shared table, store a reference to a new "Shared Entity" in your particular table.
For example:
CREATE TABLE TableA (
id INT PRIMARY KEY,
my_special_attr VARCHAR,
shared_attr_set INT REFERENCES Shared(attr_set_id)
);
CREATE TABLE TableC (
id1 INT,
id2 VARCHAR(2),
my_special_attr VARCHAR,
shared_attr_set INT REFERENCES Shared(attr_set_id),
PRIMARY KEY (id1, id2)
);
CREATE TABLE Shared (
id INT PRIMARY KEY,
attr_set_id INT,
shared_attr_1 INT,
shared_attr_2 VARCHAR
);
Of course this means you'll have to cleanup after yourself if you remove instances of particular classes - you'll either need to scan Shared for "orphaned" sets or delete them together with the instance.
Let me end with a word of warning: I've been there. I messed up. This is a recipe for a lot of headaches. Consider making Shared a full entity and use composition over inheritance.
understanding it's by-design to be an alternative of E-A-V anti pattern, if you really want to make it work, perhaps you can use single primary key for your table C: Adding a new auto-increment column that set as primary key and leave your composite key columns as is, but adding a unique-constraint on them.
this would resolve the issue on the shared table. though query might be a headache later...
another approach I've seen before is that using a parent table that holds the keys and common columns, while children extend the table to have specific columns. sample:
TableParent:
| Key | Type | Shared Attr.1 | Shared Attr.2 |... | Other common Attr. |
| P_1 | A | Value A1 | Value A2 |... | |
| P_2 | B | Value B1 | Value B2 |... | |
| P_3 | C | Value C1 | Value C2 |... | |
Key column is auto-incremental primary key column
TableChildA:
| Key | Col1 | Col2 | ... |
| P_1 | Val 1| Val 2 | ... |
The Key column is not auto-incremental, but foreign-keyed back to TableParent Key column
TableChildB:
| Key | Col3 | Col4 | ... |
| P_2 | Val 3| Val 4 | ... |
The Key column is not auto-incremental, but foreign-keyed back to TableParent Key column
TableChildC:
| Key | Composite Key 1 | Composite Key 2 |Col5 | Col6 | ... |
| P_3 | key 1 | Key 2 |Val 5| Val 6 | ... |
The Key column is not auto-incremental, but foreign-keyed back to TableParent Key column
Also there is a unique constraint on Composite Key 1 + Composite Key 2
literally in your code, you can have parent class of TableParent while having separate children class for TableA/B/C...
I completely agree with #Damien_The_Unbeliever here. This sounds really much like OTLT.
Aggregating data from TableA and TableB can be achieved through inheritance:
CREATE TableA INHERITS SharedTabele;
CREATE TableB INHERITS SharedTabele;
or use of a view:
CREATE VIEW SharedTable AS
SELECT * FROM TableA
UNION ALL
SELECT * FROM TableB
But when you explained you are trying to push a radically different TableC into the same shared table I realized something is terribly wrong. This is hard to do exactly because it does not make much sense.
Please, consider changing your approach. OTLT will not solve anything and it will definitely make things more complicated.
For example, I have these tables and data:
TABLE: logo_user
+----+---------+
| id | logo_id |
+----+---------+
| 1 | 1 |
| 2 | 2 |
+----+---------+
TABLE: logo
+----+
| id |
+----+
| 1 |
| 2 |
+----+
What I want is to delete every rows in logo table, and nullify the values that refers to it. Example:
TABLE: logo_user
+----+---------+
| id | logo_id |
+----+---------+
| 1 | NULL |
| 2 | NULL |
+----+---------+
TABLE: logo (now emptied)
I tried using TRUNCATE ... CASCADE but it also deleted every rows in the logo_user table.
I also consider altering the foreign key constraint of the logo_user to cascade on delete, but it's too much work. In reality I have many tables referring to logo table.
you can use the below solution for your problem.
ON DELETE SET NULL
on a foreign key in your main table will solve the problem in one step. What it basically does is :
if a record in the parent table is deleted, then the corresponding records in the child table will have the foreign key fields set to null (the columns that you would be mentioning in the key). The records in the child table will not be deleted, the corresponding values would be updated to null as per your requirement. For example you can refer to the below syntax :
CREATE TABLE table_name
(
column1 datatype null/not null,
column2 datatype null/not null,
...
CONSTRAINT fk_col
FOREIGN KEY (column1, column2, ... column_n)
REFERENCES parent_table (column1, column2, ... column_n)
ON DELETE SET NULL
);
I don't know why it's "too much work" to modify the foreign keys to ON DELETE SET NULL - it's not just the easiest way, you will have to do it since the default NO ACTION will prevent you from deleting without a cascade.
I am wondering if I can add a foreign key to an already existing table with records in it.
The tables structure and the data in it looks like that at the moment:
Books table:
ID | BookName | BookCode | BookEdition | AuthorID
1 | Name1 | Code1 | 1 | 1
2 | Name1 | Code1 | 2 | 2
Authors table:
ID | AuthorName
1 | Name1
2 | Name2
Basically, I want to add link the two tables based on the AuthorID, the problem is that the column already has data in it, as shown in the example above, but these ids are the correct ones and they also exist in the Authors table.
Is it possible to do that without re-creating the tables?
You can do this using alter table:
alter table books add constraint fk_books_authorid
foreign key (AUthorId) references Authors(ID);
I have a table in MySQL that has a primary key:
mysql> desc gifts;
+---------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| giftID | int(11) | NO | PRI | NULL | |
| name | varchar(80) | YES | | NULL | |
| filename | varchar(80) | YES | | NULL | |
| effectiveTime | datetime | YES | | NULL | |
+---------------+-------------+------+-----+---------+-------+
but I wanted to make it auto_increment.
The following statement failed. How can it be modified so that it can work? thanks
mysql> alter table gifts modify giftID int primary key auto_increment;
ERROR 1068 (42000): Multiple primary key defined
Leave off the primary key attribute:
ALTER TABLE gifts MODIFY giftID INT AUTO_INCREMENT;
Certain column attributes, such as PRIMARY KEY, aren't exactly properties of the column so much as shortcuts for other things. A column marked PRIMARY KEY, for example, is placed in the PRIMARY index. Futhermore, all columns in the PRIMARY index are given the NOT NULL attribute. (Aside: to have a multi-column primary key, you must use a separate constraint clause rather than multiple PRIMARY KEY column attributes.) Since the column is already in the PRIMARY index, you don't need to specify it again when you modify the column. Try SHOW CREATE TABLE gifts; to see the affects of using the PRIMARY KEY attribute.