【问题标题】:SQL Server - SQL Cascade Change Data Type of a ColumnSQL Server - SQL 级联更改列的数据类型
【发布时间】:2017-10-09 22:32:14
【问题描述】:

这是一个古老的问题,几乎每个人都会遇到它。所以,我正在寻找最常见、最可靠的解决方案。

如果我在一个表中有一个主键列,其他表具有链接到该列的外键,并且我想更改该列的数据类型,那么实现此目的的最佳工具是什么而无需编写即席手动数据库脚本,而不必删除数据? 有这个工具吗?

比如说,我有两张桌子

销售

SaleKey(整数)

总计(十进制)

销售线

SaleLineKey (int)

销售 (int)

数量(整数)

比率(十进制)

比方说,我想将 SaleKey on Sale 列批量更改为唯一标识符。这意味着我必须编写一个数据库脚本来在 Sale 上添加新的唯一标识符列,在 SaleLine 上添加一个类似的列,更新 SaleLine 中的数据以反映 Sale,删除外键和主键,然后放入新的外键和主键打开。这一切皆有可能,但耗时。

是否有可以为我执行此操作的应用程序?或者,某人已经编写的存储过程? SQL Server 有级联删除数据,级联更改数据类型呢?

【问题讨论】:

    标签: sql sql-server database scripting database-schema


    【解决方案1】:

    这是使用 C# 的解决方案。您必须填写 ExecuteScript 和 ExecuteScalar 的空白,但只要您使用 SQL Server,逻辑就可以工作。这只会转换为唯一标识符,但对于不同的数据类型进行更改并不难。这里有一些假设。例如,此代码假定您尝试更改的列最多有一个主键。在某个时候将其移入开源存储库可能是一个很好的项目。此外,这可以很容易地转换为 T/SQL。我只是使用 C# 比使用 T/SQL 更好。

    警告:这是破坏性代码。它会不加选择地删除索引和键等。因此,在运行它时必须小心,以确保生成的列与您的预期数据模式匹配。这只是完成手头任务的工具 - 不是万能的解决方案。不过,这已经在我们的数据库中进行了相当彻底的测试。

        public void ChangeColumnDataTypeToUniqueIdentifier(string schemaName, string tableName, string columnName, bool allowsNull, ConnectionInfo connectionInfo)
        {
            string script = null;
    
            var tempColumnName = $"{columnName}Temp";
    
            var columnDataTypeString = DatabaseAdapter.SqlStatementBuilder.GetSqlDataType(ColumnDataType.UniqueIdentifier, 0, 0, 0);
    
            //Add the temp column
            //TODO: We should try to figure out if this needs not null, but we can rely on the schema upgrade to fix this later...
            ExecuteScript(connectionInfo,
            "alter table " +
            $"  {schemaName}.{tableName} " +
            "add " +
            $"  {tempColumnName} {columnDataTypeString}");
    
            var fullTableName = $"[{schemaName}].[{tableName}]";
    
            //Update the temp column to new values
            //TODO: this contains UniqueIdentifier specific code
            ExecuteScript(connectionInfo,
            "update " +
            $"   {fullTableName} " +
            "set " +
            $"   {tempColumnName} = NEWID()");
    
            ExecuteScript(connectionInfo,
            "alter table " +
            $"   {schemaName}.{tableName} " +
            "alter column " +
            $"   {tempColumnName} {columnDataTypeString} {(!allowsNull ? string.Empty : "not")} null");
    
            //Get the schema id of the target table
            var targetSchemaObjectId = (int)DatabaseAdapter.ExecuteScalar("select schema_id from sys.schemas where name = @Param1", new Collection<DataParameter> { new DataParameter("Param1", schemaName) }, connectionInfo, null);
    
            //Get the object id of the table we are modifying
            var targetTableObjectId = (int)DatabaseAdapter.ExecuteScalar("select object_Id from sys.tables where name = @Param1 and schema_id = @Param2", new Collection<DataParameter> { new DataParameter("Param1", tableName), new DataParameter("Param2", targetSchemaObjectId) }, connectionInfo, null);
    
            //Get foreign keys
            using (var foreignKeyData = DatabaseAdapter.ExecuteReader("select * from Sys.foreign_keys where referenced_object_id = @Param1", new Collection<DataParameter> { new DataParameter("Param1", targetTableObjectId) }, connectionInfo, null))
            {
                //Iterate through foreign keys
                while (foreignKeyData.DataReader.Read())
                {
                    //Get thei object id of the table that references this
                    var tableThatReferencesThisObjectId = (int)foreignKeyData.DataReader["parent_object_Id"];
                    var foreignKeyObjectId = (int)foreignKeyData.DataReader["object_Id"];
                    var foreignKeyName = (string)foreignKeyData.DataReader["name"];
    
                    //Get the tables data
                    using (var tableThatReferencesThisData = DatabaseAdapter.ExecuteReader("select * from Sys.tables where object_id = @Param1", new Collection<DataParameter> { new DataParameter("Param1", tableThatReferencesThisObjectId) }, connectionInfo, null))
                    {
    
                        //Read the record
                        tableThatReferencesThisData.DataReader.Read();
    
                        //Get information about the table references this
                        var tableThatReferencesThisName = (string)tableThatReferencesThisData.DataReader["name"];
                        var tableThatReferencesShemaObjectId = (int)tableThatReferencesThisData.DataReader["schema_id"];
                        var tableThatReferencesShemaName = (string)DatabaseAdapter.ExecuteScalar("select * from sys.schemas where schema_id = @Param1", new Collection<DataParameter> { new DataParameter("Param1", tableThatReferencesShemaObjectId) }, connectionInfo, null);
    
                        //Get the name of the column that references the original column
                        var foreignKeyColumnName = (string)DatabaseAdapter.ExecuteScalar
                            (
                            "select " +
                            "   COL_NAME(fks.parent_object_id, fkcs.parent_column_id) " +
                            "from " +
                            "   sys.foreign_keys fks " +
                            "inner join " +
                            "   Sys.foreign_key_columns fkcs " +
                            "on " +
                            "   fkcs.constraint_object_id = fks.object_id " +
                            "where " +
                            $"   fks.object_id = {foreignKeyObjectId}", new Collection<DataParameter> { new DataParameter("Param1", tableThatReferencesShemaObjectId) }, connectionInfo, null);
    
                        //The new target temp column name
                        var tempForeignKeyColumnName = foreignKeyColumnName + "Temp";
    
                        //Concatenate the name of the table that references the original table
                        var tableThatReferencesFullName = $"[{tableThatReferencesShemaName}].[{tableThatReferencesThisName}]";
    
                        //Add the temp column
                        //TODO: We should try to figure out if this needs not null, but we can rely on the schema upgrade to fix this later...
                        ExecuteScript(connectionInfo,
                        "alter table " +
                        $"   {tableThatReferencesFullName} " +
                        "add " +
                        $"   {tempForeignKeyColumnName} uniqueidentifier");
    
                        //Update the data in the temp column
                        script =
                        "update " +
                        $"   {tableThatReferencesFullName} " +
                        "set " +
                        $"{tempForeignKeyColumnName} = " +
                        "   ( " +
                        "       select " +
                        $"           {tempColumnName} " +
                        "       from " +
                        $"           {fullTableName} referencedtable " +
                        "       where " +
                        $"            {tableThatReferencesFullName}.[{foreignKeyColumnName}] = referencedtable.{columnName} " +
                        "   )";
                        ExecuteScript(connectionInfo, script);
    
                        //Drop the original foreign key
                        script =
                        "alter table " +
                        $"   {tableThatReferencesFullName} " +
                        "drop " +
                        $"   {foreignKeyName} ";
                        ExecuteScript(connectionInfo, script);
    
                        DropIndexesForTable(tableThatReferencesShemaName, tableThatReferencesThisName, foreignKeyColumnName, connectionInfo);
    
                        //Drop the old column
                        script =
                        "alter table " +
                        $"   {tableThatReferencesFullName} " +
                        "drop column " +
                        $"   [{foreignKeyColumnName}] ";
                        ExecuteScript(connectionInfo, script);
    
                        //Rename the new temp column to the old one
                        ExecuteScript(connectionInfo, $"EXEC sp_rename '{tableThatReferencesFullName}.{tempForeignKeyColumnName}', '{foreignKeyColumnName}', 'COLUMN'");
                    }
                }
            }
    
            var pkName = (string)DatabaseAdapter.ExecuteScalar($"select name from sys.key_constraints where parent_object_id = @Param1 and type = 'PK'", new Collection<DataParameter> { new DataParameter("Param1", targetTableObjectId) }, connectionInfo, null);
            if (!string.IsNullOrEmpty(pkName))
            {
                //Drop the old primary key
                script =
                "alter table " +
                $"   {fullTableName} " +
                "drop " +
                $"   {pkName} ";
                ExecuteScript(connectionInfo, script);
            }
    
            var defaultConstraintName = (string)DatabaseAdapter.ExecuteScalar(
            "select " +
            "    dc.name " +
            "FROM " +
            "    SYS.DEFAULT_CONSTRAINTS dc " +
            "inner join " +
            "    sys.all_columns ac " +
            "on " +
            "    ac.object_id = @ObjectId and " +
            "    dc.parent_column_id = ac.column_id " +
            "where " +
            "    parent_object_id = @ObjectId and " +
            "    ac.name = @ColumnName", new Collection<DataParameter> { new DataParameter("ColumnName", columnName), new DataParameter("ObjectId", targetTableObjectId) }, connectionInfo, null);
            if (!string.IsNullOrEmpty(defaultConstraintName))
            {
                //Drop the old primary key
                script =
                "alter table " +
                $"   {fullTableName} " +
                "drop constraint" +
                $"   {defaultConstraintName} ";
                ExecuteScript(connectionInfo, script);
            }
    
            DropIndexesForTable(schemaName, tableName, columnName, connectionInfo);
    
            //Drop the old column
            script =
            "alter table " +
            $"   {fullTableName} " +
            "drop column " +
            $"   {columnName}";
            ExecuteScript(connectionInfo, script);
    
            //Rename the new column to the old one
            ExecuteScript(connectionInfo, $"EXEC sp_rename '{fullTableName}.{tempColumnName}', '{columnName}', 'COLUMN'");
        }
    
        private void DropIndexesForTable(string schemaName, string tableName, string columnName, ConnectionInfo connectionInfo)
        {
            //find indexes dependent on this column
            string script = "SELECT " +
            "   SchemaName = s.name, " +
            "   TableName = t.name, " +
            "   IndexName = ind.name " +
            "FROM " +
            "   sys.indexes ind " +
            "INNER JOIN " +
            "   sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id " +
            "INNER JOIN " +
            "   sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id " +
            "INNER JOIN " +
            "   sys.tables t ON ind.object_id = t.object_id " +
            "INNER JOIN " +
            "   sys.schemas s on t.schema_id = s.schema_id " +
            "WHERE " +
            "   ind.is_primary_key = 0 and" +
            "   s.name = @SchemaName and " +
            "   t.name = @TableName and " +
            "   col.name = @ColumnName";
            using (var obstructingIndexData = DatabaseAdapter.ExecuteReader(script, new Collection<DataParameter> { new DataParameter("SchemaName", schemaName), new DataParameter("TableName", tableName), new DataParameter("ColumnName", columnName) }, connectionInfo, null))
            {
                while (obstructingIndexData.DataReader.Read())
                {
                    var indexSchema = obstructingIndexData.DataReader["SchemaName"];
                    var indexTable = obstructingIndexData.DataReader["TableName"];
                    var IndexName = obstructingIndexData.DataReader["IndexName"];
    
                    ExecuteScript(connectionInfo, $"drop index [{indexSchema}].[{indexTable}].[{IndexName}]");
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-22
      • 2011-05-17
      • 2010-10-12
      • 1970-01-01
      相关资源
      最近更新 更多