Alter Column datatype with primary key Alter Column datatype with primary key sql sql

Alter Column datatype with primary key


You need to specify NOT NULL explicitly in an ALTER TABLE ... ALTER COLUMN otherwise it defaults to allowing NULL. This is not permitted in a PK column.

The following works fine.

CREATE TABLE p(ReferenceID VARCHAR(6) NOT NULL PRIMARY KEY)INSERT INTO p VALUES ('AAAAAA')ALTER TABLE p ALTER COLUMN ReferenceID VARCHAR(8) NOT NULL

when the NOT NULL is omitted it gives the following error

Msg 5074, Level 16, State 1, Line 1The object 'PK__p__E1A99A792180FB33' is dependent on column 'ReferenceID'.Msg 4922, Level 16, State 9, Line 1ALTER TABLE ALTER COLUMN ReferenceID failed because one or more objects access this column.

A couple of things to consider in your programmatic approach is that you would need to drop any foreign keys referencing the ReferenceID columns temporarily and also make sure you don't include the NOT NULL for (Non PK) ReferenceID columns that currently are nullable.


EDIT This solution is needed if you have a muddled database with a mixture of varchar(6) and char(6) columns caused by development extending over 10 years (with enough changes of government policy to cause any attempt at "good database design" to collapse eventually.)END EDIT

To those who said I would have to drop and recreate the PK, you were right. Indexes and Foreign Keys also needed dropping and recreating.

Fortunately, there were a manageable number of indexes and FKs so I handled these as 'exceptional' and dropped them all, one at a time, at the beginning of the script, then re-added them, one at a time, at the end of the script (see the two sections in /* */ below).

The main body of the SQL script then tips complete details about the FKs into a temporary table, then loops through each table name, dropping the FK, altering the datatype, re-adding the FK.

The SQL strings that get assembled are PRINTed in the script below. If you intend to reuse this (no warranties provided, etc., blah blah), comment these out to knock up to 50% off the execution time.

SET NOCOUNT ON/* Handle exceptional tables here * Remove indexes and foreign keys * --Lots of "IF EXISTS ... ALTER TABLE <name> DROP CONSTRAINT <constraint name>, etc. */--Declare variablesDECLARE @SQL                    VARCHAR(8000)DECLARE @TableName              VARCHAR(512)DECLARE @ConstraintName         VARCHAR(512)DECLARE @tColumn                VARCHAR(512)DECLARE @Columns                VARCHAR(8000)DECLARE @IsDescending           BIT--Set up temporary tableSELECT    tbl.[schema_id],    tbl.name AS TableName,    i.NAME AS IndexName,    i.type_desc,    c.[column],    c.key_ordinal,    c.is_desc,    i.[object_id],    s.no_recompute,    i.[ignore_dup_key],    i.[allow_row_locks],    i.[allow_page_locks],    i.[fill_factor],    dsi.type,    dsi.name AS DataSpaceNameINTO #PKBackupFROM     sys.tables AS tbl    INNER JOIN sys.indexes AS i        ON (            i.index_id > 0            AND i.is_hypothetical = 0        )        AND ( i.[object_id] = tbl.[object_id] )    INNER JOIN (        SELECT            ic.[object_id] ,            c.[name] [column] ,            ic.is_descending_key [is_desc],            ic.key_ordinal        FROM            sys.index_columns ic            INNER JOIN                sys.indexes i                ON                i.[object_id] = ic.[object_id]                AND                i.index_id = 1                AND                ic.index_id = 1            INNER JOIN                sys.tables t                ON                t.[object_id] = ic.[object_id]            INNER JOIN                sys.columns c                ON                c.[object_id] = t.[object_id]                AND                c.column_id = ic.column_id        ) AS c        ON c.[object_id] = i.[object_id]    LEFT OUTER JOIN        sys.key_constraints AS k        ON        k.parent_object_id = i.[object_id]        AND        k.unique_index_id = i.index_id    LEFT OUTER JOIN        sys.data_spaces AS dsi        ON        dsi.data_space_id = i.data_space_id    LEFT OUTER JOIN        sys.xml_indexes AS xi        ON        xi.[object_id] = i.[object_id]        AND        xi.index_id = i.index_id    LEFT OUTER JOIN        sys.stats AS s        ON        s.stats_id = i.index_id        AND        s.[object_id] = i.[object_id]WHERE    k.TYPE = 'PK'DECLARE TableCursor CURSOR LOCAL READ_ONLY FOR    SELECT t.name AS TableName    FROM sys.columns c        JOIN sys.tables t ON c.object_id = t.object_id    WHERE        c.name = 'ReferenceID'OPEN TableCursor    FETCH NEXT FROM TableCursor     INTO @TableNameWHILE @@FETCH_STATUS = 0BEGIN    PRINT('--Updating ' + @TableName + '...')    SELECT @ConstraintName = PK.CONSTRAINT_NAME    FROM        INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK    WHERE        PK.TABLE_NAME = @TableName        AND        PK.CONSTRAINT_TYPE = 'PRIMARY KEY'--drop the constraint    --Some tables don't have a PK defined, only do the next bit if they do    IF (SELECT COUNT(*) FROM #PKBackup PK WHERE PK.TableName = @TableName) > 0    BEGIN        SET @SQL = 'ALTER TABLE @TableName DROP CONSTRAINT @ConstraintName'        SET @SQL = REPLACE(@SQL, '@TableName', @TableName)        SET @SQL = REPLACE(@SQL, '@ConstraintName', @ConstraintName)        PRINT @SQL        EXEC (@SQL)    END--This is where we actually change the datatype of the column    SET @SQL = 'ALTER TABLE @TableName ALTER COLUMN ReferenceID VARCHAR(8)' + (SELECT CASE WHEN C.Is_Nullable = 'NO' THEN ' NOT NULL' ELSE '' END        FROM INFORMATION_SCHEMA.COLUMNS C        WHERE C.TABLE_NAME = @TableName AND C.COLUMN_NAME = 'ReferenceID')    SET @SQL = REPLACE(@SQL, '@TableName', @TableName)    PRINT(@SQL)    EXEC(@SQL)--Recreate the constraint    --Some tables don't have a PK defined, only do the next bit if they do    IF (SELECT COUNT(*) FROM #PKBackup PK WHERE PK.TableName = @TableName) > 0    BEGIN    --First set up @SQL template    SELECT @SQL =   'ALTER TABLE [' + SCHEMA_NAME(PK.schema_id) + '].[' + PK.TableName                    + '] ADD CONSTRAINT [' + PK.IndexName                    + '] PRIMARY KEY ' + Type_desc + ' ( @Columns ) WITH '                    + '( PAD_INDEX = ' + CASE   WHEN CAST(INDEXPROPERTY(pk.[object_id], PK.IndexName, N'IsPadIndex') AS BIT) = 0 THEN 'OFF'                                                ELSE 'ON'                                            END + ', '                    + 'STATISTICS_NORECOMPUTE = ' + CASE    WHEN pk.no_recompute = 0 THEN 'OFF'                                                            ELSE 'ON'                                                        END                    + ', SORT_IN_TEMPDB = OFF, '                    + 'IGNORE_DUP_KEY = ' + CASE    WHEN pk.[ignore_dup_key] = 0 THEN 'OFF'                                                    ELSE 'ON'                                                END + ', '                    + 'ONLINE = OFF, '                    + 'ALLOW_ROW_LOCKS = ' + CASE   WHEN pk.allow_row_locks = 0 THEN 'OFF'                                                    ELSE 'ON'                                                END + ', '                    + 'ALLOW_PAGE_LOCKS = ' + CASE  WHEN pk.allow_page_locks = 0 THEN 'OFF'                                                    ELSE 'ON'                                                END + ', '                    + 'FILLFACTOR = ' + CASE    WHEN pk.[fill_factor] = 0 THEN '100'                                                ELSE CONVERT(NVARCHAR, pk.[fill_factor])                                            END + ' '                    + ') ON [' + CASE   WHEN 'FG' = pk.[type] THEN pk.DataSpaceName                                        ELSE N''                                    END + ']'    FROM    #PKBackup PK WHERE PK.TableName = @TableName    SET @SQL = REPLACE(@SQL, '@TableName', @TableName)    SET @SQL = REPLACE(@SQL, '@ConstraintName', @ConstraintName)    --Second, build up @Columns    SET @Columns = ' '    DECLARE ColumnCursor CURSOR LOCAL READ_ONLY FOR        SELECT pk.[column], PK.is_desc            FROM #PKBackup PK             WHERE PK.TableName = @TableName            ORDER BY PK.key_ordinal ASC    OPEN ColumnCursor        FETCH NEXT FROM ColumnCursor         INTO @tColumn, @IsDescending    WHILE @@FETCH_STATUS = 0    BEGIN        SET @Columns = @Columns + @tColumn + CASE WHEN @IsDescending = 1 THEN ' DESC, ' ELSE ' ASC, ' END        --Get the next TableName        FETCH NEXT FROM ColumnCursor         INTO @tColumn, @IsDescending    END    --Tidy up    CLOSE ColumnCursor    DEALLOCATE ColumnCursor    --Delete the last comma    SET @Columns = LEFT(@Columns, LEN(@Columns) - 1)    END--Recreate the constraint    SET @SQL = REPLACE(@SQL, '@Columns', @Columns)    PRINT @SQL    EXEC (@SQL)    PRINT('--Done    ')    SET @SQL = ''--Get the next TableName    FETCH NEXT FROM TableCursor     INTO @TableNameEND--Tidy upCLOSE TableCursorDEALLOCATE TableCursorDROP TABLE #PKBackup/* Handle exceptional tables here * Replace indexes and foreign keys that were removed at the start */SET NOCOUNT OFF


You need to execute the ALTER statement as dynamic SQL: Build the statement as a SQL string and pass it to sp_executesql.