Изменить тип данных столбца с помощью первичного ключа
у меня есть столбец ReferenceID varchar(6) в более чем 80 различных таблицах. Мне нужно распространить это на varchar (8) по всей БД после изменения, реализованного правительственной организацией, которая назначает идентификаторы.
Я надеялся объявить курсор, чтобы получить имена таблиц следующим образом:
DECLARE @TableName AS VARCHAR(200)
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 @TableName
а затем отредактируйте тип следующим образом:
ALTER TABLE @TableName ALTER COLUMN ReferenceID VARCHAR(8)
это происходит потому, что столбец является частью первичного ключа в таблицах (и столбцы, включенные в ПК варьируются от таблицы к таблице).
Я действительно не хочу отбрасывать и воссоздавать каждый ПК вручную для каждой таблицы.
в курсоре есть ли способ отключить PK перед изменением типа данных, а затем снова включить его, или удалить и воссоздать PK по обе стороны от изменения типа данных, имея в виду, что PK будет зависеть от того, какую таблицу мы сейчас рассматриваем?
4 ответов
необходимо указать NOT NULL
явно ALTER TABLE ... ALTER COLUMN
в противном случае он по умолчанию позволяя NULL
. Это не разрешено в столбце PK.
далее работает нормально.
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
когда NOT NULL
опущено это дает следующую ошибку
Msg 5074, Level 16, State 1, Line 1
The object 'PK__p__E1A99A792180FB33' is dependent on column 'ReferenceID'.
Msg 4922, Level 16, State 9, Line 1
ALTER TABLE ALTER COLUMN ReferenceID failed because one or more objects access this column.
пара вещей, которые следует учитывать в вашем программном подходе, заключается в том, что вы б нужно удалить все внешние ключи, ссылающиеся на ReferenceID
столбцы временно, а также убедитесь, что вы не включаете NOT NULL
на (не ПК) ReferenceID
столбцы, которые в настоящее время are значение null.
редактировать Это решение необходимо, если у вас есть запутанная база данных со смесью столбцов varchar(6) и char(6), вызванных развитием, продолжающимся более 10 лет (с достаточным количеством изменений государственной политики, чтобы вызвать любую попытку "хорошего дизайна базы данных" в конечном итоге рухнуть.) РЕДАКТИРОВАНИЕ
для тех, кто сказал, что мне придется бросить и воссоздать ПК, вы были правы. Индексы и внешние ключи также необходимо отбросить и воссоздающий.
основной текст сценария SQL затем подсказывает полную информацию о FKs во временную таблицу, затем перебирает каждое имя таблицы, отбрасывая FK, изменяя тип данных, повторно добавляя ФК.
строки SQL, которые собираются, печатаются в сценарии ниже. Если вы собираетесь повторно использовать это (без гарантий и т. д. бла-бла), комментировать их, чтобы сбить до 50% от времени выполнения.
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 variables
DECLARE @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 table
SELECT
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 DataSpaceName
INTO #PKBackup
FROM
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 @TableName
WHILE @@FETCH_STATUS = 0
BEGIN
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 @TableName
END
--Tidy up
CLOSE TableCursor
DEALLOCATE TableCursor
DROP TABLE #PKBackup
/* Handle exceptional tables here
* Replace indexes and foreign keys that were removed at the start
*/
SET NOCOUNT OFF
вам нужно выполнить ALTER
оператор как динамический SQL: создайте оператор как строку SQL и передайте его в sp_executesql
.
из моего опыта работы с базами данных более 30 лет, одна константа-необходимость постоянных изменений структуры любой базы данных, с которой вы работаете, по мере изменения требований к данным. Кроме того, существует множество случаев, когда первичный ключ с автоматическим приращением не является наиболее подходящим, в частности, когда вы хотите убедиться, что данные остаются значимо доступными непосредственно через СУБД (например, SQL Server), когда программа, которая использовала базу данных, больше недоступна. Один из больших недостатков управления базами данных является обычная непроницаемость базы данных, когда программа умерла - то, что полностью противоречит принципу долгосрочного управления данными.
таким образом, невозможность легко изменить размер поля первичного ключа не из-за плохого дизайна базы данных, это потому, что СУБД и SQL инструменты для обработки базы данных являются грубо неадекватными, четко разработаны программистами с теоретической, а не фактической понимание реального мира. Другими примерами таких недостатков программирования являются индексы массива, начинающиеся с 0, а не с 1 (Количество ошибок, возникающих при добавлении или вычитании 1 для переключения с индекса на счет, легион), неспособность числовых переменных изначально обрабатывать нулевые значения и т. д. Я с нетерпением жду того дня, когда изменение структуры базы данных будет рассматриваться как основная необходимость, а не как следствие так называемого плохого дизайна базы данных.