1

I'm trying to convert all ntext columns in my database to nvarchar(max).

This is the code:

        DECLARE @command NVARCHAR(MAX);
        DECLARE @numberOfRecordsToUpdate as int;
        DECLARE @numberOfRowsUpdated as int;
        DECLARE @totalNumberOfRecordsToUpdate as int;
        DECLARE @object_id INT,
                @column_id INT,
                @SchemaName varchar(250),
                @tableName varchar(250),
                @columnName SYSNAME,
                @isNullable BIT,
                @System_Type_Id INT,
                @lenght INT

        CREATE TABLE #chunkOfObjects(ID int, column_id INT, tableName varchar(250), columnName          SYSNAME, isNullable BIT);

        CREATE TABLE #objectsToUpdate(
                ID int,
                column_id INT,
                SchemaName varchar(250),
                tableName varchar(250),
                columnName SYSNAME,
                isNullable BIT,
                System_Type_Id INT,
                lenght INT);

        INSERT INTO #objectsToUpdate 
                        SELECT c.object_id, column_id, (s.name), (o.name), c.name, c.is_nullable, c.system_type_id, DATALENGTH(c.name) as lenght
                        FROM sys.all_columns AS c
                        INNER JOIN sys.objects AS o ON c.object_id = o.object_id
                        INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id
                        WHERE o.type = 'U' AND  c.system_type_id in ('99','35','34') order by (o.name) ;

        DECLARE col_cursor CURSOR FAST_FORWARD FOR SELECT * FROM #objectsToUpdate;
        OPEN col_cursor;
        FETCH NEXT FROM col_cursor INTO @object_id, @column_id, @SchemaName, @tableName, @columnName, @isNullable, @System_Type_Id, @lenght;
        WHILE @@FETCH_STATUS = 0
        BEGIN

                    SELECT @numberOfRecordsToUpdate = count(ID) FROM #objectsToUpdate;
                    SELECT @totalNumberOfRecordsToUpdate = @numberOfRecordsToUpdate;
                    PRINT CURRENT_TIMESTAMP;
                    PRINT 'Cleaning';
                    WHILE (@numberOfRecordsToUpdate > 0)
                        BEGIN
                            INSERT INTO #chunkOfObjects (ID, column_id, tableName, columnName, isNullable  ) SELECT top(10) ID, column_id, tableName, columnName, isNullable FROM #objectsToUpdate;
                            DELETE FROM #objectsToUpdate WHERE ID in ( SELECT ID FROM #chunkOfObjects );
                            BEGIN TRANSACTION tr;                       
                                SELECT @command =
                                    'ALTER TABLE '
                                    + QUOTENAME(OBJECT_SCHEMA_NAME( (SELECT ID FROM #chunkOfObjects)) )
                                    + '.' + QUOTENAME(OBJECT_NAME( (SELECT ID FROM #chunkOfObjects)) )
                                    + ' ALTER COLUMN '
                                    + QUOTENAME((SELECT @columnName FROM #chunkOfObjects where))
                                    +' varchar(max)'    
                                    + CASE
                                        WHEN ( SELECT @isNullable FROM #chunkOfObjects ) = 1 THEN '' ELSE 'NOT'
                                      END
                                    + ' NULL;';
                                PRINT @command;
                                SELECT @command ='UPDATE ' +QUOTENAME(OBJECT_SCHEMA_NAME( (SELECT ID  FROM #chunkOfObjects )) ) + '.' 
                                    + QUOTENAME(OBJECT_NAME( (SELECT ID FROM #chunkOfObjects )) ) + ' SET ' 
                                    + QUOTENAME((SELECT @columnName FROM #chunkOfObjects )) + ' = '
                                    + QUOTENAME((SELECT @columnName FROM #chunkOfObjects )) 
                                PRINT @command;
                                EXEC sp_executesql @command
                                SELECT @numberOfRowsUpdated = COUNT(ID) FROM #chunkOfObjects;
                                PRINT 'Updtated: ' + CAST(@numberOfRowsUpdated as varchar(15)) + ' row(s) of ' + CAST(@totalNumberOfRecordsToUpdate as varchar(15));
                                TRUNCATE TABLE #chunkOfObjects;
                            COMMIT TRANSACTION tr;                  
                            SELECT @numberOfRecordsToUpdate = count(ID) FROM #objectsToUpdate;
                            PRINT 'Remaining ' + CAST(@numberOfRecordsToUpdate as varchar(15)) + ' row(s) ' + NCHAR(10) + '-------------';
                        END
                    PRINT CURRENT_TIMESTAMP
            FETCH NEXT FROM col_cursor INTO @object_id, @column_id, @SchemaName, @tableName, @columnName, @isNullable, @System_Type_Id, @lenght;
        END
        CLOSE col_cursor;
        DEALLOCATE col_cursor;
        DROP TABLE #objectsToUpdate;
        DROP TABLE #chunkOfObjects;

Whenever I try to run it, I get this error:

Msg 512, Level 16, State 1, Line 60
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.

the subquery is when im trying to select the chuck object to alter or update table :

SELECT ID FROM #chunkOfObjects

if i use the below one :

SELECT ID FROM #chunkOfObjects where column_id =@column_id  

it take only one ID, not loop on the #chunkOfObjects table

Any help to fix this error would be great.

satcha
  • 129
  • 1
  • 13
  • This is going to be lines like `QUOTENAME(OBJECT_SCHEMA_NAME( (SELECT ID FROM #chunkOfObjects)) )` As you appear to have more than 1 row in `#chunkOfObjects`. – Thom A Dec 16 '21 at 15:36
  • @Larnu exactly thats my issue. if someone can help to fix it. i means how can i modify by code to select `ID` from my `#chunckobject` one by one – satcha Dec 16 '21 at 15:41
  • YOu need to add a `WHERE`. Presumably on the `object_id`. – Thom A Dec 16 '21 at 15:42
  • Side note, you are altering all your `ntext`, `image` and `text` colums to a `varchar(MAX)`. That could cause *significant* data loss. You should be using `nvarchar`, `varbinary` and `varchar` data types respectively. – Thom A Dec 16 '21 at 15:44
  • thanks for the note, i will change my code – satcha Dec 16 '21 at 15:47
  • now my problem is how can i select ID from `#chunckobject` one by one. – satcha Dec 16 '21 at 15:48
  • It isn't actuall executing anything: `PRINT @command;` does not execute. Why don't you just generate the whole script using `STRING_AGG`, why the cursor loop? – Charlieface Dec 16 '21 at 16:13

1 Answers1

1

The above looks like you are overly complicating the problem. You should be able to achieve this far more easily with some simple string aggregation. I assume you are using SQL Server 2017+; if not use the "old" FOR XML PATH method:

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);

SELECT @SQL = STRING_AGG(N'ALTER TABLE ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N' ALTER COLUMN ' + QUOTENAME(c.[name]) + N' ' + 
                         CASE ct.[name] WHEN N'text' THEN N'varchar'
                                        WHEN N'ntext' THEN N'nvarchar'
                                        WHEN N'image' THEN N'varbinary'
                         END + N'(MAX) ' +
                         CASE c.is_nullable WHEN 1 THEN N'NULL'
                                            ELSE N'NOT NULL'
                         END + N';',@CRLF)
FROM sys.schemas s
     JOIN sys.tables t ON s.schema_id = t.schema_id
     JOIN sys.columns c ON t.object_id = c.object_id
     JOIN sys.types ct ON c.user_type_id = ct.user_type_id
WHERE ct.[name] IN (N'text',N'ntext',N'image');

PRINT @SQL;
EXEC sys.sp_executesql @SQL;
Thom A
  • 88,727
  • 11
  • 45
  • 75
  • Thanks for your help. Now i saw there is no update columns, i have to update column after convert. and no chunk table. i have the large tables so i have to use the chunk tables. – satcha Dec 16 '21 at 16:01
  • What is a "chunk table"? – Thom A Dec 16 '21 at 16:02
  • select some recors to update from original table to the temp table like what i did : `SELECT top(10) ID, column_id, tableName, columnName, isNullable FROM #objectsToUpdate;` – satcha Dec 16 '21 at 16:09
  • I don't follow I'm afraid, @satcha . The above will change all the existing columns in your database from one of the old deprecated data types to the relevant `MAX`. – Thom A Dec 16 '21 at 16:11
  • @satcha As discussed on [your previous question](https://stackoverflow.com/questions/70294930/how-to-update-large-table-when-converting-ntext-to-nvarcharmax), there is no need for the `UPDATE`. It does nothing for the size of your database – Charlieface Dec 16 '21 at 16:14
  • @Charlieface thanks a lot, unfortunately I have to do it that's why i changed my code then i tried to use top ( ) and then update column it will be more better. – satcha Dec 16 '21 at 16:21
  • @satcha If you *really* wanted to do it, and I see no point in it, see this answer https://stackoverflow.com/questions/35903375/how-to-update-large-table-with-millions-of-rows-in-sql-server/35931214 – Charlieface Dec 16 '21 at 16:23
  • You have to do *what*, @satcha ? Do an `UPDATE` that `CONVERT`s your *already* `(n)varchar(MAX)`/`varbinary(MAX)` to a `MAX` of the same data type? Why do you "have" to do that? There is *literally* no point to running a query `UPDATE dbo.MyTable SET VarcharMaxColumn CONVERT(varchar(MAX),VarcharMaxColumn);` other than to waste time. – Thom A Dec 16 '21 at 16:23
  • @larnu i will change my code to convert the right data accordingly, but as what i said my issue is only how can i loop the `#chunkobjects` table to select id one by one . – satcha Dec 16 '21 at 16:24
  • thanks @Charlieface i will check the link – satcha Dec 16 '21 at 16:28
  • Why do this RBAR in the first place, when a set based solution is almost always better, @satcha ..? – Thom A Dec 16 '21 at 16:31
  • but if someone can help to modify my code to fix my issue i ll really appreciate – satcha Dec 16 '21 at 16:33
  • *What* issue, @satcha ...? The above works, so what is the "issue"? What isn't working? – Thom A Dec 16 '21 at 16:38
  • @Larnu cause after i convert ntext to nvarchar i update column to itself but it's take a long time on large tables. so, i used RBAR to update some rows at time – satcha Dec 16 '21 at 16:39
  • 1
    *"i update column to itself"* Why @satcha ? That is pointless other than to waste time. See my prior [comment](https://stackoverflow.com/questions/70381596/sql-server-convert-all-ntext-columns-to-nvarcharmax/70381971?noredirect=1#comment124414094_70381971). With respect, I see no reason to add redundant and unperformant code to my solution. – Thom A Dec 16 '21 at 16:41
  • @Larnu, sorry but as what i read in the answer [link](https://dba.stackexchange.com/questions/36056/sql-server-alter-table-to-change-text-to-nvarcharmax-greatly-increasing-databa/36059) When you alter the column from the TEXT/NTEXT to NVARCHAR(MAX), the way the data is stored is not changed so, that's why i update column to store it in the right way. – satcha Dec 16 '21 at 16:53
  • I suggest seeing Charlie's comments on your prior question about that, @satcha . – Thom A Dec 16 '21 at 16:57
  • @Larnu as i said to charlie, unfortunately I have to do it. but thanks for trying to help me i really appreciate thanks :) – satcha Dec 16 '21 at 17:12
  • You never did explain what "have" means, unfortunately, @satcha . – Thom A Dec 16 '21 at 17:13
  • @satcha What about just doing `WHERE DATALENGTH(yourColumn) <= 8000` that at least limits it to only rows where the data can actually fit in-row. I still don't think it's necessary, because you are only concerned about size, and this if anything will make it larger not smaller (due to page splits). That post was concerned about performance of off-row data, so that's why it was necessary to force it back to in-row. – Charlieface Dec 16 '21 at 17:32
  • @Charlieface i dont know if all DATALENGTH(column) are less than 8000. – satcha Dec 16 '21 at 17:41
  • 1
    @satcha If they are more than 8k you *definitely* should not care, because `nvarchar(max)` more than 8k is also stored off-row so there will be no change at all. And I suggest writing against that note "not necessary" – Charlieface Dec 16 '21 at 17:43
  • great i would do that and i will add this note. thanks a lot @Charlieface – satcha Dec 16 '21 at 17:55