For all records in a table how to guarantee at least one record on a foreign key related table? - sql

Assume that there are two tables in a database. First one is X, second one is Y. Y table has a foreignkey on X table. So, if there is a record on Y table, it's foreignkey related column value must exist on X table. This is the default behavior.
X Y
------- -------
ID ID
XID <--- Foreignkey to X table.
Now, for all records in X table i want to guarantee that there must be at least one record on Y table. If there isn't, it should add one automatically. How can i do this?

There are two different issues here.
Managing declarative constraints.
Managing inserts, updates, and deletes.
The "normal" way to guarantee a row exists in y for every row in x is to include foreign keys in both tables. Each table references the other.
create table x (
x_id integer primary key,
y_id integer not null
);
create table y (
y_id integer primary key,
x_id integer not null references x (x_id)
);
alter table x
add constraint one_to_one
foreign key (y_id)
references y (y_id) deferrable initially deferred;
begin transaction;
insert into x values (1, 100);
insert into y values (100, 1);
commit;
Although this guarantees that every "x" has a "y", so to speak, it doesn't guarantee that every "x" has a different "y", or that every "x" has its own "y". I'm not sure whether that's your requirement.
This isn't quite what you're looking for, because you have a 1:N relationship between your two tables. You could make it work, but I think you'd have to write a trigger to manage changes to table "y". For example, if the user deletes from "y" the row that "x" holds a foreign key reference to, you'd need to update "x" to reference a different row in "y".
But this leads to the second issue: managing inserts, updates, and deletes. There are several ways to do that.
The client is responsible for providing all the statements necessary to keep the database consistent. (With the possible exception of that foreign key problem above, which might better be handled in a trigger on "y".)
Clients have no direct access to base tables. All changes are through stored procedures.
Clients have no direct access to base tables. All changes are through updatable views.
Stored procedures and updatable views can give you some degree of automatically adding a row in "y", but you have to write procedural code to do it.

To insert missed records into Y for existing data you can run something like
INSERT INTO Y(xid)
SELECT x.id
FROM x
WHERE NOT EXISTS(SELECT NULL FROM Y a WHERE a.xid = x.id);
To prevent such things in the future, write a procedure which inserts data in 2 tables, and prevent everyone from inserting data into tables directly (only through this procedure)

Related

Storing arbitrary attributes on tables

I have 3 tables, x, y, and z. I want to be able to attach arbitrary
attributes to each row in each table. x, y, and z have nothing in
common other than the fact that they all have an integer primary key called
id and should be able to have arbitrary attributes attached to them.
Is it better to make a single attributes table, like
create table attributes (
table enum('x', 'y', 'z'),
xyz_id integer,
name varchar(50),
value text,
primary key (table, xyz_id, name)
);
Or is it best to make separate tables, like
create table x_attributes (
x_id integer,
name varchar(50),
value text,
primary key (x_id, name),
foreign key (x_id) references x (id)
);
create table y_attributes (...);
create table z_attributes (...);
The second option (separate tables) seems to be cleaner, but requires a lot
more boilerplate on both the database side and the application side.
I'm also open to suggestions other than those two.
Note: I've considered the possibility of using a document store like MongoDB, but
the data I'm working with is fundamentally relational.
Go with one table with an enum column, it will make grabbing all of the attributes for each row easier in the long run.

ON UPDATE CASCADE with two columns in a single table in SQL Server [duplicate]

I have a database table called Lesson:
columns: [LessonID, LessonNumber, Description] ...plus some other columns
I have another table called Lesson_ScoreBasedSelection:
columns: [LessonID,NextLessonID_1,NextLessonID_2,NextLessonID_3]
When a lesson is completed, its LessonID is looked up in the Lesson_ScoreBasedSelection table to get the three possible next lessons, each of which are associated with a particular range of scores. If the score was 0-33, the LessonID stored in NextLessonID_1 would be used. If the score was 34-66, the LessonID stored in NextLessonID_2 would be used, and so on.
I want to constrain all the columns in the Lesson_ScoreBasedSelection table with foreign keys referencing the LessonID column in the lesson table, since every value in the Lesson_ScoreBasedSelection table must have an entry in the LessonID column of the Lesson table. I also want cascade updates turned on, so that if a LessonID changes in the Lesson table, all references to it in the Lesson_ScoreBasedSelection table get updated.
This particular cascade update seems like a very straightforward, one-way update, but when I try to apply a foreign key constraint to each field in the Lesson_ScoreBasedSelection table referencing the LessonID field in the Lesson table, I get the error:
Introducing FOREIGN KEY constraint 'c_name' on table 'Lesson_ScoreBasedSelection' may cause cycles or multiple cascade paths.
Can anyone explain why I'm getting this error or how I can achieve the constraints and cascading updating I described?
You can't have more than one cascading RI link to a single table in any given linked table. Microsoft explains this:
You receive this error message because
in SQL Server, a table cannot appear
more than one time in a list of all
the cascading referential actions that
are started by either a DELETE or an
UPDATE statement. For example, the
tree of cascading referential actions
must only have one path to a
particular table on the cascading
referential actions tree.
Given the SQL Server constraint on this, why don't you solve this problem by creating a table with SelectionID (PK), LessonID, Next_LessonID, QualifyingScore as the columns. Use a constraint to ensure LessonID and QualifyingScore are unique.
In the QualifyingScore column, I'd use a tinyint, and make it 0, 1, or 2. That, or you could do a QualifyingMinScore and QualifyingMaxScore column so you could say,
SELECT * FROM NextLesson
WHERE LessonID = #MyLesson
AND QualifyingMinScore <= #MyScore
AND #MyScore <= QualifyingMaxScore
Cheers,
Eric

Is there a smart way to append a number to an PK identity column in a Relational database w/o total catastrophe?

It's far from the ideal situation, but I need to fix a database by appending the number "1" to the PK Identiy column which has FK relations to four other tables. I'm basically making a four digit number a five digit number. I need to maintain the relations. I could store the number in a var, do a Set query and append the 1, and do that for each table...
Is there a better way of doing this?
You say you are using an identity data type for your primary key so before you update the numbers you will have to SET IDENTITY_INSERT ON (documentation here) and then turn it off again after the update.
As long as you have cascading updates set for your relations the other tables should be updated automatically.
EDIT: As it's not possible to change an identity value I guess you have to export the data, set the new identity values (+10000) and then import your data again.
Anyone have a better suggestion...
Consider adding another field to the PK instead of extending the length of the PK field. Your new field will have to cascade to the related tables, like a field length increase would, but you get to retain your original PK values.
My suggestion is:
Stop writing to the tables.
Copy the tables to new tables with the new PK.
Rename the old tables to backup names.
Rename the new tables to the original table name.
Count the rows in all the tables and double check your work.
Continue using the tables.
Changing a PK after the fact is not fun.
If the column in question has an identity property on it, it gets complicated. This is more-or-less how I'd do it:
Back up your database.
Put it in single user mode. You don't need anybody mucking around whilst you do the surgery.
Execute the ALTER TABLE statements necessary to
disable the primary key constraint on the table in question
disable all triggers on the table in question
disable all foreign key constraints referencing the table in question.
Clone your table, giving it a new name and a column-for-column identical definitions. Don't bother with any triggers, indices, foreign keys or other constraints. Omit the identity property from the table's definition.
Create a new 'map' table that will map your old id values to the new value:
create table dbo.pk_map
(
old_id int not null primary key clustered ,
new_id int not null unique nonclustered ,
)
Populate the map table:
insert dbo.pk_map
select old_id = old.id ,
new_id = f( old.id ) // f(x) is the desired transform
from dbo.tableInQuestion old
Populate your new table, giving the primary key column the new value:
insert dbo.tableInQuestion_NEW
select id = map.id ,
...
from dbo.tableInQuestion old
join dbo.pk_map map on map.old_id = old.id
Truncate the original table: TRUNCATE dbo.tableInQuestion. This should work—safely—since you've disabled all the triggers and foreign key constraints.
Execute SET IDENTITY_INSERT dbo.tableInQuestion ON.
Reload the original table:
insert dbo.tableInQuestion
select *
from dbo.tableInQuestion_NEW
Execute SET IDENTITY_INSERT dbo.tableInQuestion OFF
Execute drop table dbo.tableInQuestion_NEW. We're all done with it.
Execute DBCC CHECKIDENT( dbo.tableInQuestion , reseed ) to get the identity counter back in sync with the data in the table.
Now, use the map table to propagate the changed primary key column down the line. Depending on your E-R model, this can get complicated as foreign keys referencing the updated column may themselves be part of a composite primary key.
When you're all done, start re-enabling the constraints and triggers you disabled. Make sure you do this using the WITH CHECK option. Fix any problems thus uncovered.
Finally, drop the map table, and clear the single user flag and bring your system(s) back online.
Piece of cake! (or something.)
Consider this approach:
Reset the identity seed to the 10000 + the current seed.
Set identity insert on
Insert into the table from the values in the table and add 10000 to the identity column on the way.
EX:
Set identity insert on
Insert Table(identity, column1, eolumn2)
select identity + 10000, column1, column2
From Table
Where identity < origional max identity value
After the insert you know the identity is exactly 10000 more than the origional.
Update the foreign keys by addding 10000.

A constraint that only allows one of two tables to reference a base table

I have 3 tables. A base table, call it Table A, and two tables that reference Table A, Call them Table X and Table Y. Both X and Y have a foreign key contraint that references Table A. The Foreign Key of X and Y is also their own Primary Key.
I'd like to know if it is possible to add a constraint that will only allow one of these tables to contain a recrod that references Table A. So if X has a record that references A then Y can't have one and if Y has a record that references A then X can't have one.
Is this possible?
Thanks,
CHECK constraints with UDFs (which is Oded's answer) don't scale well and have poor concurrency. See these:
Scalar UDFs wrapped in CHECK constraints are very slow and may fail for multirow updates
Tony Rogerson
So:
create a new table, say TableA2XY
this has the PK of TableA and a char(1) column with a CHECK to allow ony X or Y. And a unique constraint on the PK of A too.
tableX and tableY have new char(1) column with a check to allow only X or Y respectively
tableX and tableY have their FK to TableA2XY on both columns
This is the superkey or subtype approach
all DRI based
no triggers
no udfs with table access in CHECK constraints.
Yes, this is possible using CHECK constraints.
Apart from the normal foreign key constraint, you will need to add a CHECK constraint on both referencing tables to ensure that a foreign key is not used in the other referencing table.

Database table id-key Null value and referential integrity

I'm learning databases, using SQLce. Got some problems, with this error:
A foreign key value cannot be inserted because a corresponding primary key value does not exist.
How does the integrity and acceptance of data work when attempting to save a data row that does not have specified one foreign key. Isn't it possible to set it to NULL in some way, meaning it will not reference the other table? In case, how would I do that? (For an integer key field)
Also, what if you save a row with a valid foreign key that corresponds to an existing primary key in other table. But then decide to delete that entry in this other table. So the foreign key will no longer be valid. Will I be allowed to delete? How does it work? I would think it should then be simply reset to a null value.. But maybe it's not that simple?
What you need to do is insert your data starting from the parent down.
So if you have an orders table and an items table that refers to orders, you have to create the new order first before adding all the children to the list.
Many of the data access libraries that you can get (in C# there is Linq to SQL) which will try and abstract this problem.
If you need to delete data you actually have to go the other way, delete the items before you delete the parent order record.
Of course, this assumes you are enforcing the foreign key, it is possible to not enforce the key, which might be useful during a bulk delete.
This is because of "bad data" you have in the tables. Check if you have all corresponding values in the primary table.
DBMS checks the referential integrity for ensuring the "correctness" of data within database.
For example, if you have a column called some_id in TableA with values 1 through 10 and a column called some_id in TableB with values 1 through 11 then TableA has no corresponding value (11) for that which you have already in TableB.
You can make a foreign key nullable but I don't recommend it. There are too many problems and inconsistencies that can arise. Redesign your tables so that you don't need to populate the foreign key for values that don't exist. Usually you can do that by moving the column to a new table for example.