When you define a foreign key in SQL Server, you can choose what happens to the child rows when the parent row is deleted or updated. One option is SET DEFAULT. With this setting, SQL Server updates the foreign key column in the child table to its default value whenever the parent key is deleted or updated.
It’s not the most common option, but it can be useful if you want to preserve child records while moving them to a “default” category, user, or state.
How SQL Server Stores This Information
The rules for foreign keys are stored in the sys.foreign_keys catalog view. The following columns define what happens:
delete_referential_actionand its descriptive counterpartdelete_referential_action_descupdate_referential_actionand its descriptive counterpartupdate_referential_action_desc
The numeric values for the non-_desc columns map like this:
0 = NO ACTION1 = CASCADE2 = SET NULL3 = SET DEFAULT
The _desc versions are more human-readable (e.g. SET_DEFAULT) and are usually easier to work with in queries.
Here’s a query that finds all foreign keys using SET DEFAULT for either deletes or updates:
SELECT
fk.name AS ForeignKeyName,
tp.name AS ParentTable,
tr.name AS ReferencedTable,
fk.delete_referential_action,
fk.delete_referential_action_desc,
fk.update_referential_action,
fk.update_referential_action_desc
FROM sys.foreign_keys fk
JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
WHERE fk.delete_referential_action = 3
OR fk.update_referential_action = 3;
Example
Let’s create a new example schema that contains employees and the departments they belong to. If a department gets deleted or its ID changes, we don’t want to lose the employees. Instead, we’ll assign them to a default department.
CREATE TABLE Departments (
DepartmentId INT PRIMARY KEY,
DepartmentName NVARCHAR(100) NOT NULL
);
-- Insert a default "General" department
INSERT INTO Departments (DepartmentId, DepartmentName) VALUES
(0, 'General'),
(1, 'Engineering'),
(2, 'Marketing'),
(3, 'Finance');
CREATE TABLE Employees (
EmployeeId INT PRIMARY KEY,
EmployeeName NVARCHAR(100) NOT NULL,
DepartmentId INT NOT NULL DEFAULT 0,
CONSTRAINT FK_Employees_Departments FOREIGN KEY (DepartmentId)
REFERENCES Departments(DepartmentId)
ON DELETE SET DEFAULT
ON UPDATE SET DEFAULT
);
INSERT INTO Employees (EmployeeId, EmployeeName, DepartmentId) VALUES
(201, 'Wanida', 1),
(202, 'Claude', 1),
(203, 'Lance', 2),
(204, 'Zara', 3);
SELECT
e.DepartmentId,
d.DepartmentId AS ParentDepartmentId,
d.DepartmentName,
e.EmployeeId,
e.EmployeeName
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentId = d.DepartmentId
ORDER BY e.EmployeeId;
Output:
DepartmentId ParentDepartmentId DepartmentName EmployeeId EmployeeName
------------ ------------------ -------------- ---------- ------------
1 1 Engineering 201 Wanida
1 1 Engineering 202 Claude
2 2 Marketing 203 Lance
3 3 Finance 204 Zara
The query at the end allows us to see the departments along with their respective employees.
Here, Employees.DepartmentId has a default of 0, which maps to the “General” department. When we delete or update a department, all employees that belong to that department will be set to this default value (due to us using SET DEFAULT).
We can use the query from earlier to verify the SET DEFAULT setting:
SELECT
fk.name AS ForeignKeyName,
tp.name AS ParentTable,
tr.name AS ReferencedTable,
fk.delete_referential_action,
fk.delete_referential_action_desc,
fk.update_referential_action,
fk.update_referential_action_desc
FROM sys.foreign_keys fk
JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
WHERE fk.delete_referential_action = 3
OR fk.update_referential_action = 3;
Result:
ForeignKeyName ParentTable ReferencedTable delete_referential_action delete_referential_action_desc update_referential_action update_referential_action_desc
------------------------ ----------- --------------- ------------------------- ------------------------------ ------------------------- ------------------------------
FK_Employees_Departments Employees Departments 3 SET_DEFAULT 3 SET_DEFAULT
We can see that it has SET_DEFAULT on both delete and updates. That means, whenever there’s a delete or update to the parent, this foreign key column will be set to NULL.
Test Deleting a Department
We can test this by deleting a department:
DELETE FROM Departments WHERE DepartmentId = 1;
And now let’s check the data again:
SELECT
e.DepartmentId,
d.DepartmentId AS ParentDepartmentId,
d.DepartmentName,
e.EmployeeId,
e.EmployeeName
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentId = d.DepartmentId
ORDER BY e.EmployeeId;
Result:
DepartmentId ParentDepartmentId DepartmentName EmployeeId EmployeeName
------------ ------------------ -------------- ---------- ------------
0 0 General 201 Wanida
0 0 General 202 Claude
2 2 Marketing 203 Lance
3 3 Finance 204 Zara
Now the employees that belonged to department 1 remain, but they now belong to the “General” department (i.e., their DepartmentId is now 0).
Test Updating a Department ID
Updating a department should also result in the same outcome:
UPDATE Departments SET DepartmentId = 5 WHERE DepartmentId = 2;
Employee Lance will now point to the default department (0), since his old department ID no longer exists.
Now let’s check it again:
SELECT
e.DepartmentId,
d.DepartmentId AS ParentDepartmentId,
d.DepartmentName,
e.EmployeeId,
e.EmployeeName
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentId = d.DepartmentId
ORDER BY e.EmployeeId;
Result:
DepartmentId ParentDepartmentId DepartmentName EmployeeId EmployeeName
------------ ------------------ -------------- ---------- ------------
0 0 General 201 Wanida
0 0 General 202 Claude
0 0 General 203 Lance
3 3 Finance 204 Zara
As expected, we can see that Lance has also been moved to the “General” department (DepartmentId = 0).
Conclusion
SET DEFAULT gives you more control over how orphaned child rows are handled. Instead of breaking referential integrity or leaving NULL values, you can redirect those rows to a known default.
The query against sys.foreign_keys (with both numeric and descriptive action columns) is handy when auditing a database to confirm where SET DEFAULT is in play. If you inherit a schema or troubleshoot strange reassignments, this check can save time and reveal exactly which relationships rely on defaults.