在SQL Server中修改用户定义类型

60

我在数据库中创建了几个用户定义的类型,如下所示

CREATE TYPE [dbo].[StringID] FROM [nvarchar](20) NOT NULL

我已经将它们分配给各个表。我的数据库中的表位于不同的模式中(不仅限于 dbo

但是我意识到我需要更大的字段,需要进行更改,例如从 [nvarchar](20) 增加到 [nvarchar](50),但是没有 ALTER TYPE 语句。

我需要一个脚本,使用临时表/光标等保存使用我的类型的所有表和字段。然后将现有字段更改为基本类型 - 例如从 CustID [StringID] 更改为 CustID [nvarchar(20)]。 删除用户自定义类型并使用新类型重新创建它 - 例如 nvarchar(50), 最后将字段设置回用户自定义类型。

我没有定义类型的规则,因此不必删除规则并重新添加它们。

任何帮助都将不胜感激。

10个回答

38

这是我通常使用的方法,虽然有点手动:

/* Add a 'temporary' UDDT with the new definition */ 
exec sp_addtype t_myudt_tmp, 'numeric(18,5)', NULL 


/* Build a command to alter all the existing columns - cut and 
** paste the output, then run it */ 
select 'alter table dbo.' + TABLE_NAME + 
       ' alter column ' + COLUMN_NAME + ' t_myudt_tmp' 
from INFORMATION_SCHEMA.COLUMNS 
where DOMAIN_NAME = 't_myudt' 

/* Remove the old UDDT */ 
exec sp_droptype t_mydut


/* Rename the 'temporary' UDDT to the correct name */ 
exec sp_rename 't_myudt_tmp', 't_myudt', 'USERDATATYPE' 

4
很遗憾,SQL Server没有“ALTER TYPE”命令(为什么没有呢?),这可能是唯一可行的方法。 - marc_s
11
您可以在 https://connect.microsoft.com/SQLServer/feedback/details/319134/msft-mso-support-alter-type 上投票支持"ALTER TYPE"命令。 - Václav Dajbych
6
来自Google的朋友请注意:sp_addtypesp_droptype自SQL Server 2005起已被弃用。 (注:该提示提醒用户sp_addtypesp_droptype这两个SQL Server操作已经过时,不再建议使用) - Jon Seigel
2
在重命名新的 UDDT 之后,任何先前存在的存储过程都将无法工作。运行 "sp_refreshsqlmodule PROC_NAME" 来修复它们。 - Skrealin
4
由于我有将用户定义数据类型作为参数的函数和存储过程,所以DROP语句对我不起作用。执行该语句会导致错误信息:“无法删除类型'dbo.MyUDT',因为它被对象'usp_do_whatever'引用。可能还有其他引用此类型的对象。” - Rory
显示剩余4条评论

6
我们正在使用以下过程,它允许我们从头开始重新创建一个类型,这是“开始”。它重命名现有类型,创建类型,重新编译存储过程,然后删除旧类型。这解决了仅仅删除旧类型定义由于对该类型的引用而失败的情况。
使用示例:
exec RECREATE_TYPE @schema='dbo', @typ_nme='typ_foo', @sql='AS TABLE([bar] varchar(10) NOT NULL)'

代码:

CREATE PROCEDURE [dbo].[RECREATE_TYPE]
    @schema     VARCHAR(100),       -- the schema name for the existing type
    @typ_nme    VARCHAR(128),       -- the type-name (without schema name)
    @sql        VARCHAR(MAX)        -- the SQL to create a type WITHOUT the "CREATE TYPE schema.typename" part
AS DECLARE
    @scid       BIGINT,
    @typ_id     BIGINT,
    @temp_nme   VARCHAR(1000),
    @msg        VARCHAR(200)
BEGIN
    -- find the existing type by schema and name
    SELECT @scid = [SCHEMA_ID] FROM sys.schemas WHERE UPPER(name) = UPPER(@schema);
    IF (@scid IS NULL) BEGIN
        SET @msg = 'Schema ''' + @schema + ''' not found.';
        RAISERROR (@msg, 1, 0);
    END;
    SELECT @typ_id = system_type_id FROM sys.types WHERE UPPER(name) = UPPER(@typ_nme);
    SET @temp_nme = @typ_nme + '_rcrt'; -- temporary name for the existing type

    -- if the type-to-be-recreated actually exists, then rename it (give it a temporary name)
    -- if it doesn't exist, then that's OK, too.
    IF (@typ_id IS NOT NULL) BEGIN
        exec sp_rename @objname=@typ_nme, @newname= @temp_nme, @objtype='USERDATATYPE'
    END;    

    -- now create the new type
    SET @sql = 'CREATE TYPE ' + @schema + '.' + @typ_nme + ' ' + @sql;
    exec sp_sqlexec @sql;

    -- if we are RE-creating a type (as opposed to just creating a brand-spanking-new type)...
    IF (@typ_id IS NOT NULL) BEGIN
        exec recompile_prog;    -- then recompile all stored procs (that may have used the type)
        exec sp_droptype @typename=@temp_nme;   -- and drop the temporary type which is now no longer referenced
    END;    
END

GO


CREATE PROCEDURE [dbo].[recompile_prog]
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @v TABLE (RecID INT IDENTITY(1,1), spname sysname)
    -- retrieve the list of stored procedures
    INSERT INTO 
        @v(spname) 
    SELECT 
        '[' + s.[name] + '].[' + items.name + ']'     
    FROM 
        (SELECT sp.name, sp.schema_id, sp.is_ms_shipped FROM sys.procedures sp UNION SELECT so.name, so.SCHEMA_ID, so.is_ms_shipped FROM sys.objects so WHERE so.type_desc LIKE '%FUNCTION%') items
        INNER JOIN sys.schemas s ON s.schema_id = items.schema_id    
        WHERE is_ms_shipped = 0;

    -- counter variables
    DECLARE @cnt INT, @Tot INT;
    SELECT @cnt = 1;
    SELECT @Tot = COUNT(*) FROM @v;
    DECLARE @spname sysname
    -- start the loop
    WHILE @Cnt <= @Tot BEGIN    
        SELECT @spname = spname        
        FROM @v        
        WHERE RecID = @Cnt;
        --PRINT 'refreshing...' + @spname    
        BEGIN TRY        -- refresh the stored procedure        
            EXEC sp_refreshsqlmodule @spname    
        END TRY    
        BEGIN CATCH        
            PRINT 'Validation failed for : ' + @spname + ', Error:' + ERROR_MESSAGE();
        END CATCH    
        SET @Cnt = @cnt + 1;
    END;

END

感谢这些存储过程,它们为我节省了大量时间。 - pateketu
正如Rory所指出的那样,这对我也不起作用,因为我有使用数据类型作为参数的过程,因此DROP语句会导致“无法删除类型'dbo.MyUDT',因为它被对象'usp_do_whatever'引用”。 - Alejandro Barone

5

这里有一个更全面的脚本示例

值得注意的是,如果您有任何视图,此脚本将包括这些视图。我运行了它,而不是执行内联生成的脚本,而是生成了一个脚本作为输出,然后进行了调整和运行。

另外,如果您有使用用户定义类型的函数/存储过程,则需要在运行脚本之前删除它们。

教训:将来不要费心使用UDT,它们带来的麻烦比它们的价值还大。

SET NOCOUNT ON

DECLARE @udt VARCHAR(150)
DECLARE @udtschema VARCHAR(150)
DECLARE @newudtschema VARCHAR(150)
DECLARE @newudtDataType VARCHAR(150)
DECLARE @newudtDataSize smallint
DECLARE @OtherParameter VARCHAR(50)

SET @udt = 'Name' -- Existing UDDT
SET @udtschema = 'dbo' -- Schema of the UDDT
SET @newudtDataType = 'varchar' -- Data type for te new UDDT
SET @newudtDataSize = 500 -- Lenght of the new UDDT
SET @newudtschema = 'dbo' -- Schema of the new UDDT
SET @OtherParameter = ' NULL' -- Other parameters like NULL , NOT NULL
DECLARE @Datatype VARCHAR(50),
    @Datasize SMALLINT

DECLARE @varcharDataType VARCHAR(50)

DECLARE @Schemaname VARCHAR(50),
    @TableName VARCHAR(50),
    @FiledName VARCHAR(50)

CREATE TABLE #udtflds
    (
      Schemaname VARCHAR(50),
      TableName VARCHAR(50),
      FiledName VARCHAR(50)
    )

SELECT TOP 1
        @Datatype = Data_type,
        @Datasize = character_maximum_length
FROM    INFORMATION_SCHEMA.COLUMNS
WHERE   Domain_name = @udt
        AND Domain_schema = @udtschema

SET @varcharDataType = @Datatype
IF @DataType Like '%char%'
    AND @Datasize IS NOT NULL
    AND ( @newudtDataType <> 'varchar(max)'
          OR @newudtDataType <> 'nvarchar(max)'
        )
    BEGIN
        SET @varcharDataType = @varcharDataType + '('
            + CAST(@Datasize AS VARCHAR(50)) + ')'
    END

INSERT  INTO #udtflds
        SELECT  TABLE_SCHEMA,
                TABLE_NAME,
                Column_Name
        FROM    INFORMATION_SCHEMA.COLUMNS
        WHERE   Domain_name = @udt
                AND Domain_schema = @udtschema

DECLARE @exec VARCHAR(500)

DECLARE alter_cursor CURSOR
    FOR SELECT  Schemaname,
                TableName,
                FiledName
        FROM    #udtflds

OPEN alter_cursor
FETCH NEXT FROM alter_cursor INTO @Schemaname, @TableName, @FiledName

WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @exec = 'Alter Table ' + @Schemaname + '.' + @TableName
            + '  ALTER COLUMN ' + @FiledName + ' ' + @varcharDataType
        EXECUTE ( @exec
               )
        FETCH NEXT FROM alter_cursor INTO @Schemaname, @TableName, @FiledName

    END

CLOSE alter_cursor

SET @exec = 'DROP TYPE [' + @udtschema + '].[' + @udt + ']'
EXEC ( @exec
    )

SET @varcharDataType = @newudtDataType

IF @newudtDataType Like '%char%'
    AND @newudtDataSize IS NOT NULL
    AND ( @newudtDataType <> 'varchar(max)'
          OR @newudtDataType <> 'nvarchar(max)'
        )
    BEGIN
        SET @varcharDataType = @varcharDataType + '('
            + CAST(@newudtDataSize AS VARCHAR(50)) + ')'
    END

SET @exec = 'CREATE TYPE [' + @newudtschema + '].[' + @udt + '] FROM '
    + @varcharDataType + ' ' + @OtherParameter
EXEC ( @exec
    )

OPEN alter_cursor
FETCH NEXT FROM alter_cursor INTO @Schemaname, @TableName, @FiledName

WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @exec = 'Alter Table ' + @Schemaname + '.' + @TableName
            + '  ALTER COLUMN ' + @FiledName + ' ' + '[' + @newudtschema
            + '].[' + @udt + ']'
        EXECUTE ( @exec
               )
        FETCH NEXT FROM alter_cursor INTO @Schemaname, @TableName, @FiledName
    END

CLOSE alter_cursor
DEALLOCATE alter_cursor
SELECT  *
FROM    #udtflds

DROP TABLE #udtflds

1http://www.sql-server-performance.com/2008/how-to-alter-a-uddt/已取代http://www.sql-server-performance.com/faq/How_to_alter_a%20_UDDT_p1.aspx


太棒了!我刚刚使用它来更改我的dbo.percentage UDT的基础数据类型,从decimal(9, 2)更改为decimal(9, 4)。 - bloparod
链接现在已经失效。 - Rory
1
我已经更新了@Robin答案中的错误链接,将其更改为该答案的当前地址。 - outis nihil

5
通过Visual Studio的对象浏览器是最简单的方法,这也适用于社区版。一旦你建立了与SQL服务器的连接,浏览到类型,右键单击并选择查看代码,对用户定义类型的模式进行更改,然后单击更新。Visual Studio应该会显示该对象的所有依赖项,并生成脚本以更新类型并重新编译依赖项。

4

正如devio所说,如果UDT正在使用中,则无法简单地编辑它。

通过SMS的解决方法对我有用,这种方式是生成一个创建脚本并进行适当更改;重新命名现有的UDT;运行创建脚本;重新编译相关存储过程并删除已重新命名的版本。


你如何重命名现有的UDT? - Muflix

3
我在存储过程的自定义类型中遇到了问题,并通过以下脚本解决了这个问题。我并没有完全理解上面的脚本,所以我遵循了“如果你不知道它是什么,请不要使用”的规则。
简而言之,我重命名了旧类型,并创建了一个原始类型名称的新类型。然后,我告诉SQL Server刷新其有关每个使用自定义类型的存储过程的详细信息。您必须这样做,因为即使更名,所有内容仍然与旧类型参考“已编译”。在这种情况下,我需要更改的类型是“PrizeType”。希望这可以帮助您。我也在寻求反馈,以便我学习 :)
请注意,您可能需要转到程序性>类型>[适当的用户类型]并删除该对象。我发现DROP TYPE在使用该语句后似乎并不总是删除类型。
/* Rename the UDDT you want to replace to another name */ 
exec sp_rename 'PrizeType', 'PrizeTypeOld', 'USERDATATYPE';

/* Add the updated UDDT with the new definition */ 
CREATE TYPE [dbo].[PrizeType] AS TABLE(
    [Type] [nvarchar](50) NOT NULL,
    [Description] [nvarchar](max) NOT NULL,
    [ImageUrl] [varchar](max) NULL
);

/* We need to force stored procedures to refresh with the new type... let's take care of that. */
/* Get a cursor over a list of all the stored procedures that may use this and refresh them */
declare sprocs cursor
  local static read_only forward_only
for
    select specific_name from information_schema.routines where routine_type = 'PROCEDURE'

declare @sprocName varchar(max)

open sprocs
fetch next from sprocs into @sprocName
while @@fetch_status = 0
begin
    print 'Updating ' + @sprocName;
    exec sp_refreshsqlmodule @sprocName
    fetch next from sprocs into @sprocName
end
close sprocs
deallocate sprocs

/* Drop the old type, now that everything's been re-assigned; must do this last */
drop type PrizeTypeOld;

3
这里提供的解决方案仅适用于仅在表定义中使用用户定义类型,并且如果UDT列未被索引。
一些开发人员还使用具有UDT参数的SP和函数,这也没有涵盖。(请参见Robin's linkConnect entry中的评论)
2007年的Connect条目终于在3年后关闭了:

感谢您提交此建议,但考虑到其优先级相对于我们队列中的许多其他项目,我们不太可能真正完成它。因此,我们将此建议关闭为“不修复”。

我尝试解决类似问题ALTERing XML SCHEMA COLLECTIONS,这些步骤似乎大多适用于ALTER TYPE。
要删除UDT,需要执行以下步骤:
  • 如果表列引用了UDT,则必须将其转换为基础类型
  • 如果表列具有默认约束,则删除默认约束
  • 如果存储过程或函数具有UDT参数,则必须删除存储过程或函数
  • 如果UDT列上存在索引,则必须删除该索引
  • 如果索引是主键,则必须删除所有外键
  • 如果基于UDT列的计算列存在,则必须删除这些计算列
  • 如果这些计算列上存在索引,则必须删除这些索引
  • 如果基于包含UDT列的表的模式绑定视图、函数或存储过程存在,则必须删除这些对象

2

对一个老问题的新回答:

Visual Studio 的数据库项目在部署更改时会处理删除和重建过程。它将删除使用 UDDT 的存储过程,然后在删除和重建数据类型后重新创建它们。


我不确定为什么会被踩。如果这不是一个合法的答案,请告诉我原因。 - mcfea
如果删除该过程,则会删除附加到它的任何权限。这真的是项目要做的吗?我问这个问题是因为我不知道。使用CREATE OR ALTER会更好。 - Adam R. Turner
1
@AdamR.Turner,是的,它将删除项目中未包含的权限。我通过管理组来解决这个问题。 - mcfea
感谢对这个老问题的回复,我会研究一下群组。 - Adam R. Turner

1

1. 重命名旧的UDT,
2. 执行查询,
3. 删除旧的UDT。


-3

先简单执行 DROP TYPE,然后进行更正/修改后再次执行 CREATE TYPE

有一个简单的测试可以查看它是否已被定义,就像表格、存储过程或函数一样 - 如果我不在工作中,我会查找这是什么?

(我只是粗略地浏览了上面的内容...如果我读错了,提前道歉!;)


16
如果类型正在使用,你就不能删除它。 - Jersey Dude

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接