Default constraints in SQL are used to assign a default value to a column when no value is provided during data insertion. However, managing and auditing these default values in large databases can be challenging. The SQL query provided in this article is a robust way to identify tables and columns where default constraints are defined and evaluate if they align with the expected default values based on their data type.
;WITH CTE AS
(
SELECT
t.name AS TableName,
c.name AS ColumnName,
TYPE_NAME(c.user_type_id) AS DataType,
dc.name AS OldConstraint,
'DF_' + t.name + '_' + c.name AS NameWillBe,
REPLACE(REPLACE(dc.definition, '(', ''), ')', '') AS OldDefaultValue,
CASE
WHEN dc.name = 'DF_' + t.name + '_' + c.name THEN 1
ELSE 0
END AS IsConstraintMatching,
CASE
WHEN TYPE_NAME(c.user_type_id) IN ('smallint', 'int', 'tinyint', 'bit', 'bigint', 'decimal', 'float') THEN '0'
WHEN TYPE_NAME(c.user_type_id) IN ('varchar', 'nvarchar', 'char', 'nchar', 'text') THEN ''''''
WHEN TYPE_NAME(c.user_type_id) IN ('datetime', 'date', 'smalldatetime', 'datetime2', 'datetimeoffset') THEN NULL
ELSE 'N/A'
END AS DefaultValueWillBe,
CASE
WHEN REPLACE(REPLACE(dc.definition, '(', ''), ')', '') =
CASE
WHEN TYPE_NAME(c.user_type_id) IN ('smallint', 'int', 'tinyint', 'bit', 'bigint', 'decimal', 'float') THEN '0'
WHEN TYPE_NAME(c.user_type_id) IN ('varchar', 'nvarchar', 'char', 'nchar', 'text') THEN ''''''
WHEN TYPE_NAME(c.user_type_id) IN ('datetime', 'date', 'smalldatetime', 'datetime2', 'datetimeoffset') THEN NULL
ELSE 'N/A'
END
THEN 1
ELSE 0
END AS IsDefaultMatching
FROM sys.tables t
INNER JOIN sys.columns c
ON t.object_id = c.object_id
LEFT JOIN sys.default_constraints dc
ON c.default_object_id = dc.object_id
WHERE
c.column_id NOT IN
(
SELECT
ic.column_id
FROM
sys.index_columns ic
INNER JOIN
sys.indexes i
ON ic.object_id = i.object_id
AND ic.index_id = i.index_id
WHERE
i.is_primary_key = 1
AND ic.object_id = t.object_id
)
)
SELECT
CTE.TableName,
CTE.ColumnName,
CTE.OldDefaultValue,
CTE.DefaultValueWillBe,
CTE.IsDefaultMatching,
CTE.OldConstraint,
CTE.NameWillBe,
CTE.IsConstraintMatching,
'( ''' + TableName + '''' + ', ' + QUOTENAME(ColumnName, '''') + ', ' + QUOTENAME(IsDefaultMatching, '''') + ')'
FROM CTE
WHERE
IsConstraintMatching = 0
-- OR IsDefaultMatching = 0
ORDER BY
TableName,
ColumnName;

Automating Default Constraint Management in SQL Server
Managing default constraints across multiple tables and columns in a SQL Server database can be a tedious and error-prone task, especially when working with large datasets. This blog introduces a robust SQL script to automate the process of adding, updating, or removing default constraints for specified columns based on desired values.
Problem Statement
In scenarios where default values for columns need to be updated or standardized across tables (e.g., fact tables in a data warehouse), the manual process can become cumbersome:
- Identifying columns without default constraints.
- Dropping and recreating constraints when needed.
- Avoiding errors due to name conflicts or invalid data types.
Solution Overview
The provided SQL script automates this process by:
- Storing Required Metadata: Using a table variable (
@TableList) to specify the desired columns, their default values, and corresponding tables. - Generating the Final List: Creating a final list of columns from the database schema (
sys.tablesandsys.columns), checking for existing constraints, and identifying mismatches. - Dynamic Default Constraint Handling:
- Dropping existing constraints if the names or values don't match the desired ones.
- Adding new constraints with standardized names and default values.
- Iterative Execution: Using a
WHILEloop to process each row in the final list and execute the required SQL statements dynamically.
Step-by-Step Explanation of the Script
1. Define Metadata for Constraints
The first step is to store the desired constraints for specific columns in a table variable:
DECLARE @TableList TABLE
(
ID INT IDENTITY,
TableName VARCHAR(MAX),
ColName VARCHAR(MAX),
DefaultValue VARCHAR(MAX)
);
INSERT INTO @TableList (TableName, ColName, DefaultValue)
VALUES
('DimAccount', 'CreateDate', 'getdate');
This specifies that the CreateDate column in the DimAccount table should have a default value of GETDATE.
2. Identify Target Columns
A Common Table Expression (CTE) collects metadata from the database schema, including:
- Table names.
- Column names.
- Data types.
- Existing default constraints and their definitions.
;WITH CTE AS
(
SELECT
t.name AS TableName,
c.name AS ColumnName,
ISNULL(Temp.DefaultValue, CASE
WHEN TYPE_NAME(c.user_type_id) IN ('smallint','int',...) THEN '0'
ELSE 'N/A'
END) AS DefaultValueWillBe
FROM sys.tables t
INNER JOIN sys.columns c ON t.object_id = c.object_id
LEFT JOIN @TableList Temp ON Temp.TableName = t.name AND Temp.ColName = c.name
WHERE t.name LIKE 'Fact%'
)
This CTE also includes logic to suggest default values based on data types when none are explicitly provided.
3. Dynamic SQL Execution
The core logic processes each column iteratively to ensure default constraints are updated:
- Check Existing Constraints: Using
sys.default_constraintsto fetch the current default constraint name and value. - Drop Constraints (If Needed): Constraints are dropped if:
- The name doesn't match the desired format.
- The default value is different.
- Create New Constraints: New constraints are added dynamically using
ALTER TABLEstatements.
SET @SQL = 'ALTER TABLE . ADD CONSTRAINT DEFAULT ';
IF ISNUMERIC(@DefaultValue) = 1
SET @SQL = @SQL + @DefaultValue;
ELSE IF @DefaultValue = 'GETDATE'
SET @SQL = @SQL + '' + @DefaultValue + '()';
4. Error Handling
The script includes a TRY...CATCH block to gracefully handle errors, log the issue, and terminate the process if necessary.
BEGIN TRY
-- Execute dynamic SQL
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE(), GETDATE();
GOTO exit_handler;
END CATCH;
Benefits of the Script
- Automation: Eliminates the need for manual updates to constraints.
- Consistency: Ensures standardized naming conventions for constraints.
- Efficiency: Handles large datasets with minimal effort.
- Error Resilience: Robust error handling prevents disruptions during execution.
Use Cases
- Data Warehousing: Applying default values to fact and dimension tables.
- Schema Standardization: Enforcing consistent default values across databases.
- Data Quality Assurance: Ensuring columns are never left uninitialized.
Conclusion
This script simplifies the complex process of managing default constraints in SQL Server, saving time and effort while ensuring consistency and reliability. With minor modifications, it can be adapted to meet various database management needs.
Feel free to adapt and expand this script to suit your specific requirements!
Here's a consolidated version of your query with proper comments for clarity:
-- Declare a table variable to store metadata about tables, columns, and their desired default values
DECLARE @TableList TABLE
(
ID INT IDENTITY,
TableName VARCHAR(MAX),
ColName VARCHAR(MAX),
DefaultValue VARCHAR(MAX)
);
-- Insert desired default values for specific columns
INSERT INTO @TableList (TableName, ColName, DefaultValue)
VALUES ('DimAccount', 'CreateDate', 'getdate');
-- Declare another table variable to store the final list of tables, columns, and resolved default values
DECLARE @FinalTable TABLE
(
ID INT IDENTITY,
TableName VARCHAR(MAX),
ColumnName VARCHAR(MAX),
DefaultValue VARCHAR(MAX)
);
-- Use a CTE to gather column metadata, including resolving default values based on data type
;WITH CTE AS
(
SELECT
t.name AS TableName,
c.name AS ColumnName,
TYPE_NAME(c.user_type_id) AS DataType,
dc.name AS ConstraintName,
ISNULL(Temp.DefaultValue, CASE
WHEN TYPE_NAME(c.user_type_id) IN ('smallint','int','tinyint','bit','bigint','decimal','float') THEN '0'
WHEN TYPE_NAME(c.user_type_id) IN ('varchar', 'nvarchar', 'char', 'nchar', 'text') THEN ''''''
WHEN TYPE_NAME(c.user_type_id) IN ('datetime', 'date', 'smalldatetime', 'datetime2', 'datetimeoffset') THEN 'NULL'
ELSE 'N/A'
END) AS DefaultValueWillBe
FROM sys.tables t
INNER JOIN sys.columns c ON t.object_id = c.object_id
LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
LEFT JOIN @TableList Temp ON Temp.TableName = t.name AND Temp.ColName = c.name
WHERE t.name LIKE 'Fact%' -- Filter for tables of interest
AND c.column_id NOT IN ( -- Exclude primary key columns
SELECT ic.column_id
FROM sys.index_columns ic
INNER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
WHERE i.is_primary_key = 1 AND ic.object_id = t.object_id
)
)
-- Insert results into @FinalTable
INSERT INTO @FinalTable (TableName, ColumnName, DefaultValue)
SELECT TableName, ColumnName, DefaultValueWillBe
FROM CTE
ORDER BY TableName, ColumnName;
-- Declare variables for processing each column in @FinalTable
DECLARE @DC_Name NVARCHAR(255);
DECLARE @DC_Default NVARCHAR(255);
DECLARE @tableSchema NVARCHAR(255) = N'dbo';
DECLARE @TableName NVARCHAR(255);
DECLARE @ColumnName NVARCHAR(255);
DECLARE @DefaultValue VARCHAR(100);
DECLARE @ConstraintName NVARCHAR(255);
DECLARE @Total INT;
DECLARE @I INT = 1;
DECLARE @SQL NVARCHAR(MAX) = '';
-- Get the total number of rows in @FinalTable
SELECT @Total = COUNT(*) FROM @FinalTable;
-- Loop through each row in @FinalTable to process constraints
WHILE @I <= @Total
BEGIN
BEGIN TRY
-- Fetch the current table name, column name, and default value
SELECT @TableName = TableName, @ColumnName = ColumnName, @DefaultValue = DefaultValue
FROM @FinalTable WHERE ID = @I;
-- Define the desired constraint name
SET @ConstraintName = 'DF_' + @TableName + '_' + @ColumnName;
-- Check for an existing default constraint on the column
SET @DC_Name = '';
SET @DC_Default = '';
SELECT @DC_Name = dc.name,
@DC_Default = REPLACE(REPLACE(dc.definition, ')', ''), '(', '')
FROM sys.default_constraints dc
JOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id
WHERE dc.parent_object_id = OBJECT_ID('.')
AND c.name = @ColumnName;
-- Drop the existing constraint if needed
IF (@DC_Name <> @ConstraintName OR LOWER(@DC_Default) <> LOWER(@DefaultValue))
BEGIN
IF @DC_Name <> ''
BEGIN
SET @SQL = 'ALTER TABLE . DROP CONSTRAINT ;';
EXEC sp_executesql @SQL;
END
-- Add a new constraint if it doesn't already exist
IF NOT EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_NAME = @ConstraintName
AND TABLE_NAME = @TableName
AND TABLE_SCHEMA = @tableSchema
)
BEGIN
SET @SQL = 'ALTER TABLE . ADD CONSTRAINT DEFAULT ';
IF ISNUMERIC(@DefaultValue) = 1
SET @SQL = @SQL + @DefaultValue;
ELSE IF @DefaultValue = 'GETDATE'
SET @SQL = @SQL + '' + @DefaultValue + '()';
ELSE
SET @SQL = @SQL + @DefaultValue;
SET @SQL = @SQL + ' FOR ;';
EXEC sp_executesql @SQL;
END
END
-- Increment the loop counter
SET @I = @I + 1;
END TRY
BEGIN CATCH
-- Handle errors gracefully
SELECT @TableName, @ColumnName, ERROR_NUMBER(), ERROR_MESSAGE(), GETDATE();
GOTO exit_handler;
END CATCH
END;
exit_handler:
-- End of script
GO