SQL 服务器 - SQL 级联更改列的数据类型

SQL Server - SQL Cascade Change Data Type of a Column

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

如果我在一个 table 中有一个主键列,其他 table 有链接到该列的外键,我想更改该列的数据类型,请问无需手动编写临时数据库脚本且无需删除数据即可实现此目标的最佳工具? 有这方面的工具吗?

所以,举个例子,我有两个 tables

促销

销售密钥(整数)

总计(十进制)

销售线

SaleLineKey(整数)

Sale (int) <- 返回 Sale 的外键

数量(整数)

比率(十进制)

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

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

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

更擅长 C#

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

    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}]");
            }
        }
    }