﻿/*
 ------- Uppercase All String Columns (and in a Single Query!) -------

Created By: Solomon Rutzky / Sql Quantum Leap
Created On: 2019-03-20
Updated On: n/a

Location:          https://pastebin.com/vbzz0LqN
Related blog post: https://sqlquantumleap.com/2019/03/20/uppercase-all-characters-and-in-a-single-query/
----------------------------------------------------------------------
*/


USE [tempdb];
SET NOCOUNT ON;


IF (OBJECT_ID(N'dbo.One''Column') IS NULL)
BEGIN
  CREATE TABLE dbo.[One'Column]
  (
    [One'ColumnID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_One'Column] PRIMARY KEY,
    [String] VARCHAR(100) COLLATE SQL_Latin1_General_CP1_CI_AS,
    [SomeDate] DATETIME
  );

  CREATE TABLE dbo.TwoColumns
  (
    [TwoColumnsID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_TwoColumns] PRIMARY KEY,
    [Stringy]]] NCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS,
    [SomeDate] DATETIME,
    [LOBy] TEXT COLLATE SQL_Latin1_General_CP1_CI_AS
  );

  CREATE TABLE dbo.NoColumns
  (
    [NoColumnsID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_NoColumns] PRIMARY KEY,
    [SomeDate] DATETIME,
    [SomeMoney] MONEY
  );

  CREATE TABLE dbo.[Another]]One'Column]
  (
    [Another]]One'ColumnID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_Another]]One'Column] PRIMARY KEY,
    [Stringy] CHAR(100) COLLATE Latin1_General_100_CI_AS,
    [SomeDate] DATETIME,

  );

  CREATE TABLE dbo.ThreeColumns
  (
    [ThreeColumnsID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_ThreeColumns] PRIMARY KEY,
    ['FarCar] VARCHAR(100) COLLATE Latin1_General_CI_AS,
    [SomeDate] DATETIME,
	[InTharKahr] NVARCHAR(50) COLLATE Latin1_General_100_CI_AS,
    [💣EnTexxed💥] NTEXT COLLATE Latin1_General_CI_AS
  );

  CREATE TABLE dbo.[Yet🙀Another]]One'Column]
  (
    [Yet🙀Another]]One'ColumnID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_Yet🙀Another]]One'Column] PRIMARY KEY,
    [Stringy] NVARCHAR(50) COLLATE Syriac_90_CI_AS_SC,
    [SomeDate] DATETIME,

  );

  CREATE TABLE dbo.[DatabaseCollation]
  (
    [DatabaseCollationID] INT NOT NULL IDENTITY(1, 1)
      CONSTRAINT [PK_DatabaseCollation] PRIMARY KEY,
    [VarChar] VARCHAR(100),
    [NVarChar] NVARCHAR(50),
    [SomeDate] DATETIME,

  );


  DECLARE @TestValue NVARCHAR(100) = NCHAR(0xE0) + NCHAR(0xE1) + NCHAR(0xE2) + NCHAR(0xE3) + NCHAR(0xE4) +
                       NCHAR(0xE4) + NCHAR(0xE5) + NCHAR(0xE6) + NCHAR(0xE7) + NCHAR(0xE8) + NCHAR(0xE9) +
                       NCHAR(0xEA) + NCHAR(0xEB) + NCHAR(0xEC) + NCHAR(0xED) + NCHAR(0xEE) + NCHAR(0xEF) +
                       NCHAR(0xF1) + NCHAR(0xF2) + NCHAR(0xF3) + NCHAR(0xF4) + NCHAR(0xF5) + NCHAR(0xF6) +
                       NCHAR(0xF8) + NCHAR(0xF9) + NCHAR(0xFA) + NCHAR(0xFB) + NCHAR(0xFC) + NCHAR(0xFD) +
                       NCHAR(0xFE) + N' ' + NCHAR(0x01F9) + N' ' + NCHAR(0x0219) + N' ' + NCHAR(0x03E1) +
                       N' ' + NCHAR(0x1F90); 

  SELECT @TestValue AS [NVarCharValue],
         DATALENGTH(@TestValue) AS [NVarCharBytes],
         CONVERT(VARCHAR(100), @TestValue) AS [VarCharValue],
         DATALENGTH(CONVERT(VARCHAR(100), @TestValue)) AS [VarCharBytes];


  INSERT INTO dbo.[One'Column] ([SomeDate], [String])
    VALUES (GETDATE(), @TestValue); -- VC (SQL)
  INSERT INTO dbo.TwoColumns ([Stringy]]], [SomeDate], [LOBy])
    VALUES (@TestValue, GETDATE(), @TestValue); -- NC (SQL), TX (SQL)
  INSERT INTO dbo.NoColumns ([SomeDate], [SomeMoney])
    VALUES (GETDATE(), $123.45);
  INSERT INTO dbo.[Another]]One'Column] ([Stringy], [SomeDate])
    VALUES (@TestValue, GETDATE()); -- C (100)
  INSERT INTO dbo.ThreeColumns (['FarCar], [SomeDate], [InTharKahr], [💣EnTexxed💥])
    VALUES (@TestValue, GETDATE(), @TestValue, @TestValue); -- VC (80), NVC (100), NTX (80)
  INSERT INTO dbo.[Yet🙀Another]]One'Column] ([Stringy], [SomeDate])
    VALUES (@TestValue, GETDATE()); -- NVC (90)
  INSERT INTO dbo.[DatabaseCollation] ([VarChar], [NVarChar], [SomeDate])
    VALUES (@TestValue, @TestValue, GETDATE()); -- VC (DATABASE_DEFAULT), NVC (DATABASE_DEFAULT)

END;


SELECT 'BEFORE' AS [WHEN], @@TRANCOUNT AS [@@TRANCOUNT];
SELECT * FROM dbo.[One'Column];
SELECT * FROM dbo.TwoColumns;
SELECT * FROM dbo.NoColumns;
SELECT * FROM dbo.[Another]]One'Column];
SELECT * FROM dbo.ThreeColumns;
SELECT * FROM dbo.[Yet🙀Another]]One'Column]
SELECT * FROM dbo.[DatabaseCollation];

-------------------------------------------------------------------------------
--===========================================================================--
-------------------------------------------------------------------------------
DECLARE @TablesPerTransaction TINYINT = 2,
        @FixNVarCharCollation BIT = 1;

DECLARE @SQL NVARCHAR(MAX) = N'',
        @CurrentTableName NVARCHAR(MAX) = N'',
        @PreviousTableName NVARCHAR(MAX) = N'',
        @FullScript NVARCHAR(MAX) = N'';

;WITH pieces AS
(
  SELECT QUOTENAME(sch.[name]) AS [SchemaName],
         QUOTENAME(obj.[name]) AS [TableName],
         QUOTENAME(col.[name]) AS [ColumnName],
         typ.[name] AS [DataType],
         IIF(    COLLATIONPROPERTY(col.[collation_name], 'version') < 2
             AND (   typ.[name] IN (N'nchar', N'nvarchar', N'ntext')
                  OR col.[collation_name] LIKE N'%UTF8%'),
             1, 0) AS [CollationNeedsFixin],
         ROW_NUMBER() OVER (PARTITION BY sch.[name], obj.[name]
                            ORDER BY sch.[name], obj.[name], col.[name]) AS [ColumnNum],
         DENSE_RANK() OVER (ORDER BY sch.[name], obj.[name]) AS [ObjectNum]
  FROM   sys.objects obj
  INNER JOIN sys.schemas sch
          ON sch.[schema_id] = obj.[schema_id]
  INNER JOIN sys.columns col
          ON col.[object_id] = obj.[object_id]
  INNER JOIN sys.types typ
          ON typ.[user_type_id] = col.[system_type_id]
  WHERE  obj.[is_ms_shipped] = 0
  AND    obj.[type] = N'U'
  AND    typ.[name] IN (N'char', N'varchar', N'text', N'nchar', N'nvarchar', N'ntext')
  --ORDER BY sch.[name], obj.[name], col.[name]
), core AS
(
  SELECT NCHAR(0x000D) + NCHAR(0x000A) AS [CRLF],
         NCHAR(0x0009) AS [TAB],
         NCHAR(0x0027) AS [APOS], -- single-quote
         NCHAR(0x0027) + NCHAR(0x0027) AS [APOSx2], -- double single-quote
         CONVERT(NVARCHAR(128), SERVERPROPERTY('ProductVersion')) AS [ProdVer]
), util AS
(
  SELECT core.[CRLF], -- pass-through
         core.[TAB], -- pass-through
         core.[APOS], -- pass-through
         core.[APOSx2], -- pass-through
         CONVERT(INT, SUBSTRING(core.[ProdVer], 1, CHARINDEX(N'.', core.[ProdVer]) - 1)) AS [Version],
         N'DECLARE @ErrorMessage NVARCHAR(MAX),' + core.[CRLF]
           + N'        @CurrentTime NVARCHAR(50);' + core.[CRLF] AS [ScriptStart],
         N'BEGIN TRAN;' + core.[CRLF] AS [TranStart],
         N'BEGIN TRY' + core.[CRLF]
           + core.[TAB] + N'SET @CurrentTime = CONVERT(VARCHAR(50), SYSDATETIME(), 121);' + core.[CRLF]
           + core.[TAB] + N'RAISERROR(N''%s -- %s ...'', 10, 1, @CurrentTime, N''{{TABLE_NAME}}'') WITH NOWAIT;'
           + core.[CRLF] AS [TryCatchStart],
         core.[TAB] + N'UPDATE {{TABLE_NAME}} SET' + core.[CRLF] AS [TableStart],
         N',' + core.[CRLF] AS [ColumnEnd],
         N';' + core.[CRLF] AS [TableEnd],
         N'END TRY' + core.[CRLF]
           + N'BEGIN CATCH' + core.[CRLF]
           + core.[TAB] + N'IF (@@TRANCOUNT > 0) ROLLBACK TRAN;' + core.[CRLF]
           + core.[TAB] + N'SET @ErrorMessage = N''( {{TABLE_NAME}} ): '' + ERROR_MESSAGE();' + core.[CRLF]
           + core.[TAB] + N'RAISERROR(@ErrorMessage, 16, 1);' + core.[CRLF]
           + core.[TAB] + N'RETURN;' + core.[CRLF]
           + N'END CATCH;' + core.[CRLF] + core.[CRLF] AS [TryCatchEnd],
         N'IF (@@TRANCOUNT > 0) COMMIT TRAN;' + core.[CRLF] AS [TranEnd]
  FROM   core
)
SELECT -- { store value to use multiple times in remaining variable concatenations }
       @CurrentTableName = p.[SchemaName] + N'.' + p.[TableName],

       -- { main script generation }
       @SQL += -- { end previous line }
               IIF(DATALENGTH(@SQL) = 0,
                   u.[ScriptStart] + u.[CRLF] -- no previous line
                     + IIF((@TablesPerTransaction > 1) AND (p.[ObjectNum] % @TablesPerTransaction = 1),
                           N'',
                           N'--') + u.[TranStart] + u.[CRLF],
                   IIF(p.[ColumnNum] = 1, -- previous line was final column of a table
                       u.[TableEnd]
                         + REPLACE(u.[TryCatchEnd], N'{{TABLE_NAME}}', @PreviousTableName)
                         + IIF((@TablesPerTransaction > 1) AND (p.[ObjectNum] % @TablesPerTransaction = 1),
                               u.[TranEnd] + u.[TranStart],
                               N'--' + u.[TranEnd] + N'--' + u.[TranStart]) + u.[CRLF],
                       u.[ColumnEnd]))
               -- { start new table }
             + IIF(p.[ColumnNum] = 1,
                   REPLACE(u.[TryCatchStart], N'{{TABLE_NAME}}', REPLACE(@CurrentTableName, u.[APOS], u.[APOSx2]))
                     + REPLACE(u.[TableStart], N'{{TABLE_NAME}}', @CurrentTableName),
                   N'')
               -- { start column }
             + u.[TAB] + u.[TAB] + p.[ColumnName] + N' = UPPER('
               -- { TEXT and NTEXT aren't valid for UPPER() ; UTF8 needs special handling }
             + CASE 
                 WHEN (p.[DataType] IN (N'char', N'varchar')) AND (p.[CollationNeedsFixin] = 1)
                   THEN N'CONVERT(NVARCHAR(MAX), ' + p.[ColumnName] + N')'
                 WHEN p.[DataType] = N'text' THEN N'CONVERT(VARCHAR(MAX), ' + p.[ColumnName] + N')'
                 WHEN p.[DataType] = N'ntext' THEN N'CONVERT(NVARCHAR(MAX), ' + p.[ColumnName] + N')'
                 ELSE p.[ColumnName]
               END
               -- { For SQL Server 2008 or newer, NVARCHAR data must use a version 100 (or newer) collation }
             + IIF((@FixNVarCharCollation = 1) AND (p.[CollationNeedsFixin] = 1),
                   N' COLLATE '
                     + CASE
                         WHEN u.[Version] = 9 THEN N'Latin1_General_BIN2' -- 2005
                         WHEN u.[Version] < 14 THEN N'Latin1_General_100_BIN2' -- 2008, 2008 R2, 2012, 2014, 2016
                         ELSE N'Japanese_XJIS_140_BIN2' -- 2017 and newer
                       END,
                   N'')
               -- { finish column }
             + N')',

       -- { store value to use at beginning of next row }
       @PreviousTableName = REPLACE(@CurrentTableName, u.[APOS], u.[APOSx2]), -- store TableName for CATCH BLOCK

       -- { store value to use after query finishes }
       @FullScript = @SQL + u.[TableEnd]
                    + REPLACE(u.[TryCatchEnd], N'{{TABLE_NAME}}', @PreviousTableName)
                    + u.[TranEnd]
                    + u.[CRLF] + u.[CRLF]
FROM   pieces p
CROSS JOIN util u
ORDER BY p.[ObjectNum], p.[ColumnNum];



PRINT '-----------------------';
--PRINT @PreviousTableName;
--PRINT @SQL;

PRINT @FullScript; -- DEBUG (this is usually commented-out)
PRINT '-----------------------';

EXEC (@FullScript);
-------------------------------------------------------------------------------
--===========================================================================--
-------------------------------------------------------------------------------

SELECT 'AFTER' AS [WHEN], @@TRANCOUNT AS [@@TRANCOUNT];
SELECT * FROM dbo.[One'Column];
SELECT * FROM dbo.TwoColumns;
SELECT * FROM dbo.NoColumns;
SELECT * FROM dbo.[Another]]One'Column];
SELECT * FROM dbo.ThreeColumns;
SELECT * FROM dbo.[Yet🙀Another]]One'Column]
SELECT * FROM dbo.[DatabaseCollation];
GO


IF (OBJECT_ID(N'dbo.One''Column') IS NOT NULL)
BEGIN
  RAISERROR('Deleting test tables...', 10, 1) WITH NOWAIT;
  DROP TABLE dbo.[One'Column];
  DROP TABLE dbo.TwoColumns;
  DROP TABLE dbo.NoColumns;
  DROP TABLE dbo.[Another]]One'Column];
  DROP TABLE dbo.ThreeColumns;
  DROP TABLE dbo.[Yet🙀Another]]One'Column];
  DROP TABLE dbo.[DatabaseCollation];
END;

GO
----------------------------------------------------------------------------------------------------------