Can referential integrity be enforced using alter table? - sql-server

Is there a way to enforce referential integrity without foreign keys? Is there a way to achieve what I am trying to do below with alter table statement?
ALTER TABLE no.Man
WITH CHECK ADD CONSTRAINT chk_Son_Weight CHECK
(Son_Weight IN (Select distinct Weight from no.Man))
GO
I got the below error by using the code above
Subqueries are not allowed in this context. Only scalar expressions are allowed.

I'm not sure I understand why you think this is better than foreign keys, but yes, you can implement referential integrity in other (inferior) ways. These will be slower than doing it right and fixing the design.
Check constraint + UDF
CREATE FUNCTION dbo.IsItAValidWeight(#Son_Weight int)
RETURNS bit
WITH SCHEMABINDING
AS
BEGIN
RETURN
(
SELECT CASE WHEN EXISTS
(
SELECT 1
FROM no.Man WHERE Weight = #Son_Weight
) THEN 1 ELSE 0 END
);
END
GO
ALTER TABLE no.Man WITH CHECK
ADD CONSTRAINT chk_Son_Weight
CHECK dbo.IsItAValidWeight(Son_Weight) = 1;
Trigger
Need to know a lot more about the schema I think, but you can research.

Related

All cases when check constraints are executed for each row

When creating a check constraint it will be performed on all rows of a table. Does this happen in another case too?
My check constraint calls a function with SQL inside. This is very slow on large tables.
CREATE FUNCTION CheckUnique(#test NVARCHAR(20))
RETURNS INT
AS
BEGIN
IF EXISTS (SELECT 1 FROM mytable WHERE unique_field = #test)
BEGIN
RETURN 1
END
RETURN 0
END
ALTER TABLE mytable
ADD CONSTRAINT CCheckUnique CHECK([dbo].CheckUnique(unique_field) = 0)
Don't use a function to check if a value is unique, use a UNIQUE INDEX:
CREATE UNIQUE INDEX uq_unique_field ON dbo.mytable(unique_field);
Or, alternatively (it's the end of the day and i totally forget to incldue them) use a UNIQUE CONSTRAINT, and #Marc_s reminded me in the above comment:
ALTER TABLE MyTAble
ADD CONSTRAINT UC_unique_field UNIQUE (unique_field);
Scalar functions, especially those that reference tables, are known to perform very poorly. An inline table-value function should be used when you can; but you cannot implement those for a CHECK CONSTRAINT. Neither are needed here though.

SQL Server : how to enforce Primary Key cannot exist as Primary Key in another table constraint - trigger

I have a table LotTable that has a PK= LotID, Name, rate.
I have another table LotTranslate that has a PK=TranslateLotID and FK=MasterLotID
Before insert into LotTable I need to make sure enforce the PK inserted is NOT already the PK in LotTranslate.
My question is do I do a trigger instead of insert or Delete it after? What is the most clean way, speedy way to check this other table and stop the insert in LotTable if the PK is found there in LotTranslate?
My direction I am not sure if this is the right SQL Server way...
CREATE TRIGGER tr_LotsInsert ON LotTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.LotTable
SELECT *
FROM INSERTED
WHERE INSERTED.LotID not in (select TranslateLotID from LotTranslate)
END
I don't recommend using a trigger to enforce this.
What you are describing is actually inheritance, where different objects share a base type. In this case, you have the base concept of a Lot (called the supertype), and two mutually exclusive subtypes, LotTable and LotTranslate. (And for the record, I think it unfortunate that your database has a table with the name Table in it, unless it actually deals with some kind of tables that aren't database objects).
There is a reasonably well-established database design pattern to deal with subtypes and supertypes: creating a parent table that is used as the "base object" in the inheritance pattern, and making the subtype tables all have an FK relationship to it. To additionally enforce the mutual exclusivity, you can add a Type column to all the tables and involve it in the foreign key.
Then, your base table participates with the two tables in a 1-to-zero-or-one relationship. The most important concept to get here is that the LotID is always the same in all the tables and you do not create separate surrogate keys for any table: the base/supertype table contains the same values that are in the child/subtype tables.
Before I show you how to accomplish this, let me mention that in this case it's possible your two tables should really be combined into one, with a simple Type column indicating which it is which would of course prevent a single Lot from being two types at once. I'm assuming, however, that your two tables have enough columns different between them that it would be a big waste of NULL values to do so (if there are only a few columns different, it may be better to just combine them).
CREATE TABLE dbo.LotBase (
LotID int NOT NULL CONSTRAINT PK_LotBase PRIMARY KEY CLUSTERED,
LotTypeID tinyint NOT NULL
CONSTRAINT FK_LotBase_LotTypeID FOREIGN KEY
REFERENCES dbo.LotType (LotTypeID),
-- A unique constraint needed for FK purposes
CONSTRAINT UQ_LotBase_LotID_LotTypeID
UNIQUE (LotID, LotTypeID)
);
-- Include script here to create a LotType table and populate it with two rows
-- 1 = `Standard Lot` and 2 = `TranslateLot`
INSERT dbo.LotBase (LotID, LotTypeID)
SELECT LotID, 1
FROM dbo.LotTable;
INSERT dbo.LotBase (LotID, LotTypeID)
SELECT TranslateLotID, 2
FROM dbo.LotTranslate;
ALTER TABLE dbo.LotTable ADD LotTypeID tinyint NOT NULL
CONSTRAINT DF_LotTable_LotTypeID DEFAULT (1);
ALTER TABLE dbo.LotTranslate ADD LotTypeID tinyint NOT NULL
CONSTRAINT DF_LotTranslate_LotTypeID DEFAULT (2);
ALTER TABLE dbo.LotTable ADD CONSTRAINT FK_LotTable_LotBase
FOREIGN KEY (LotID, LotTypeID)
REFERENCES dbo.LotBase (LotID, LotTypeID);
ALTER TABLE dbo.LotTable ADD CONSTRAINT FK_LotTable_LotBase
FOREIGN KEY (LotID, LotTypeID)
REFERENCES dbo.LotBase (LotID, LotTypeID);
Note that you might want to do the work to get the new LotTypeID columns in the child tables to be situated immediately after the LotID columns, but it is up to you--just be careful because it will require table recreation and you can harm your database if you are not knowledgeable and careful (take backups first!).
One huge benefit of this pattern to not miss is that anywhere in your database you want an FK to a Lot, you can choose to either use one of the child tables or to use the parent table. This constrains your other tables to allow either both or just one of the subtypes. Another benefit to not miss is that you can put common columns between the two tables into the parent table instead of repeated in the children. Finally, you can create a view for each child that exposes the combined parent + child columns just like the original child table.
Finally, if you persist in going on with the trigger method, you don't have to use an INSTEAD OF trigger. You can just ROLLBACK any transaction that isn't appropriate:
CREATE TRIGGER TR_LotTable_I ON dbo.LotTable FOR INSERT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF EXISTS (
SELECT *
FROM
Inserted I
INNER JOIN dbo.LotTranslate LT
ON I.LotID = LT.TranslateLotID
) ROLLBACK TRAN;
That's a far better way to handle it (for one thing, you won't have to modify it every time you add a column to your LotTable table. Also, I would recommend that you learn to use (and then consistently use) JOIN syntax instead of the IN syntax you showed. While there is some controversy over this recommendation I'm making, in my experience people who use IN instead of JOINs miss some key conceptual learning that goes on in the process of figuring out how to make them into JOINs. There are other practical benefits such as the fact that nested IN queries get abominably hard to understand and maintain, while adding 5 more JOINs doesn't really make a query much harder to understand when formatted well.

Cannot truncate table because it is being referenced by a FOREIGN KEY constraint

I get the following message even when the table that references it is empty: "Cannot truncate table 'dbo.Link' because it is being referenced by a FOREIGN KEY constraint" Doesn't seem to make much sense why this is occurring. Any suggestions?
In SQL Server a table referenced by a FK cannot currently be truncated even if all referencing tables are empty or the foreign keys are disabled.
You need to use DELETE (may require much more logging) or drop the relationship(s) prior to using TRUNCATE and recreate them afterwards or see the workarounds on this connect item for a way of achieving this using ALTER TABLE ... SWITCH
You cannot truncate a table which has an FK constraint on it. As workaround, you could:
1/ Drop the constraints
2/ Trunc the table
3/ Recreate the constraints.
Here it is the associated T-SQL script, supposing you have 2 tables called MyTable and MyReferencedTable:
-- Remove constraint
IF EXISTS(SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_MyReferencedTable_MyTable')
BEGIN
ALTER TABLE dbo.MyReferencedTable
DROP CONSTRAINT FK_MyReferencedTable_MyTable
END
-- Truncate table
TRUNCATE TABLE dbo.MyTable
-- Re-Add constraint
IF NOT EXISTS(SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_MyReferencedTable_MyTable')
BEGIN
ALTER TABLE dbo.MyReferencedTable
WITH CHECK ADD CONSTRAINT [FK_MyReferencedTable_MyTable] FOREIGN KEY(ListingKey)
REFERENCES dbo.MyTable (ListingKey)
END
Execute the following query to search any constraint:
use MyDatabase
select c.name as c_name, t.name as t_name
from sys.key_constraints c
join sys.tables t on t.object_id = c.parent_object_id
If any constraint found on your table, remove it.
If you are receiving this error and you need to truncate the table then alternative solution could be that you can drop and re-create the table along with primary/other_keys/indexes/triggers. Please make sure that you don't need to the data in that table.
This soulution is working like a charm for me and hardly took a minute to finish. I am doing it for masking purpose.
Not for SQL Server but MySQL only.
Instead of deleting or recreating the constraint, I prefer this simpler way.
Disable the constraint validation by executing the following query first :
SET FOREIGN_KEY_CHECKS=0;
Then truncate your tables
And finally, reactivate the constraint validation :
SET FOREIGN_KEY_CHECKS=1;
Thats a common solution when you migrate databases, so you don't have to worry about the order the tables are inserted in.

Change primary key value in Oracle

Is there a way to change the value of a primary key which is referenced by another table as foreign key?
An easier alternative is to insert a new row and delete the old one. (Update any referencing rows in other tables before you do the delete)
There isn't an in-built UPDATE CASCADE if that's what you're after. You'd need to do something like disable any FK constraints; run UPDATE statements; re-enable the constraints.
Note that updating Primary Keys is (usually always) a bad idea.
You will need to disable the foreign key constraints before changing the primary key values, and then re-enable them afterwards.
If you actually want to implement "update cascade" functionality instead then see Tom Kyte's Update Cascade package
It is possible even without disabling constraints, in case if you would like only to swap keys (which is also a change's subset, so it might be still answer to your question). I wrote an example here: https://stackoverflow.com/a/26584576/1900739
update MY_TABLE t1
set t1.MY_KEY = (case t1.MY_KEY = 100 then 101 else 100 end)
where t1.MYKEY in (100, 101)
Yes, there is a way to do the cascading update in Oracle, even within a transaction (which does not hold true for the option of enabling/disabling constraints). However, you'll have to implement it yourself. It can be done via before/after-row-update triggers.
It is possible due to the fact that triggers are executed before any constraints are checked. (Well, at least in Oracle 11.2 it was true. Haven't checked against 12.1, but I honestly believe it hasn't changed.)
Anyway, as said before, updating primary keys is usually a bad idea.
The principe is to disable constrainsts, run your udates based on key, and reenable the constrainst. That for here is a script that run the disable script :
(Assuming all the constraints are enable at start)
Generate the script
SELECT 'alter table ' || uc.table_name|| ' disable constraint '|| uc.constraint_name|| ' ;'
FROM user_constraints uc inner join user_cons_columns ucc on uc.constraint_name = ucc.constraint_name where column_name = 'MYCOLUMN_USED_AS_FOREIGN_KEY' and constraint_type='R'
Copy/paste the generated script and run it
alter table MYTABLE1 disable constraint FK_MYTABLE1 ;
alter table MYTABLE2 disable constraint MYTABLE2 ;
alter table MYTABLE3 disable constraint FK3_MYTABLE3 ;
...
Then update your PK values :
update MYTABLE1 set MYFIELD= 'foo' where MYFIELD='bar';
update MYTABLE2 set MYFIELD= 'foo' where MYFIELD='bar';
update MYTABLE3 set MYFIELD= 'foo' where MYFIELD='bar';
commit;
Generate the enable constraints script :
SELECT 'alter table ' || uc.table_name|| ' enable constraint '|| uc.constraint_name|| ' ;'
FROM user_constraints uc inner join user_cons_columns ucc on uc.constraint_name = ucc.constraint_name where column_name = 'MYCOLUMN_USED_AS_FOREIGN_KEY' and constraint_type='R'
Another way you can do this is by changing the foreign key constraints so that the validation of the constraint is deferred until you commit - i.e. instead of Oracle validating the constraints statement-by-statement, it'll do it transaction-by-transaction.
Note you can't do this via the "alter table" statement, but you can drop and re-create the foreign key constraint to be deferrable, i.e:
alter table <table name> drop constraint <FK constraint name>;
alter table <table name> add constraint <FK constraint name> foreign key .... initially deferrable;
Once you've done that, just update the tables in whatever order you like, and commit - at which point, either:
All your FK constraints are satisfied, and everyone's happy; or
You've violated a FK constraint somewhere - you'll get an error and you'll have to fix up the data and commit, or rollback.
Note this feature is quite safe, as Oracle does not allow dirty reads so they'll only see the effects of your updates once you commit. So from the perspective of every other session, referential integrity appears to be preserved.
Also, this is a once-off change, so you don't need to go executing DDL each time you want to go updating the primary keys.

Enforce constraint checking only when inserting rows in MSSQL?

Is there a way to enforce constraint checking in MSSQL only when inserting new rows? I.e. allow the constraints to be violated when removing/updating rows?
Update: I mean FK constraint.
You could create an INSERT TRIGGER that checks that the conditions are met. That way all updates will go straight through.
CREATE TRIGGER employee_insupd
ON employee
FOR INSERT
AS
/* Get the range of level for this job type from the jobs table. */
DECLARE #min_lvl tinyint,
#max_lvl tinyint,
#emp_lvl tinyint,
#job_id smallint
SELECT #min_lvl = min_lvl,
#max_lvl = max_lvl,
#emp_lvl = i.job_lvl,
#job_id = i.job_id
FROM employee e INNER JOIN inserted i ON e.emp_id = i.emp_id
JOIN jobs j ON j.job_id = i.job_id
IF (#job_id = 1) and (#emp_lvl <> 10)
BEGIN
RAISERROR ('Job id 1 expects the default level of 10.', 16, 1)
ROLLBACK TRANSACTION
END
ELSE
IF NOT (#emp_lvl BETWEEN #min_lvl AND #max_lvl)
BEGIN
RAISERROR ('The level for job_id:%d should be between %d and %d.',
16, 1, #job_id, #min_lvl, #max_lvl)
ROLLBACK TRANSACTION
END
I think your best bet is to remove the explicit constraint and add a cursor for inserts, so you can perform your checking there and raise an error if the constraint is violated.
What sort of constraints? I'm guessing foreign key constraints, since you imply that deleting a row might violate the constraint. If that's the case, it seems like you don't really need a constraint per se, since you're not concerned with referential integrity.
Without knowing more about your specific situation, I would echo the intent of the other posters, which seems to be "enforce the insert requirements in your data access layer". However, I'd quibble with their implementations. A trigger seems like overkill and any competent DBA should sternly rap you on the knuckles with a wooden ruler for trying to use a cursor to perform a simple insert. A stored procedure should suffice.

Resources