Tag Archive varchar

Byphunsanit

SQL Server: ปรับ column string, binary, varchar, nvarchar, char ให้พอดี

ความยากในการออกแบบตาราง column string, binary, varchar, nvarchar ให้พอดีเพราะไม่รู้ว่าหลังจากใช้จริง ๆ แล้ว ได้เผื่อ column size ไว้มากเกินไปรึเปล่า โดยเฉพาะพวกจองพื้นที่ไว้อย่าง char ทำให้เขียน script ไว้ alter column ให้ลดลงเพื่อลดพื้นที่จองไว้ให้ลดลง
alter_table_optimize_by_storage_bytes_columns_types_string_and_binary.sql

/*
================================================================================
STORAGE OPTIMIZATION: FIXED-LENGTH VS VARIABLE-LENGTH
================================================================================
References (Microsoft Learn):
1. CHAR vs VARCHAR:
   https://learn.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver16#storage
   - CHAR(n): Spends 'n' bytes regardless of actual data length.
   - VARCHAR(n): Spends Actual Length + 2 bytes.

2. NCHAR vs NVARCHAR:
   https://learn.microsoft.com/en-us/sql/t-sql/data-types/nchar-and-nvarchar-transact-sql?view=sql-server-ver16#storage
   - NCHAR(n): Spends 'n * 2' bytes regardless of data.
   - NVARCHAR(n): Spends (Actual Length * 2) + 2 bytes.

3. BINARY vs VARBINARY:
   https://learn.microsoft.com/en-us/sql/t-sql/data-types/binary-and-varbinary-transact-sql?view=sql-server-ver16#storage
   - BINARY(n): Spends 'n' bytes.
   - VARBINARY(n): Spends Actual Length + 2 bytes.
================================================================================
*/

SET NOCOUNT ON;

DECLARE @AnalysisTable TABLE (
    DatabaseName NVARCHAR(128), -- เพิ่มเพื่อใช้เรียงลำดับ
    SchemaName NVARCHAR(128),
    TableName NVARCHAR(128),
    ColumnName NVARCHAR(128),
    DataType NVARCHAR(50),
    DefinedLength INT,
    ActualMaxLength INT,
    RowCountVal INT
);

DECLARE @Db NVARCHAR(128), @Schema NVARCHAR(128), @Table NVARCHAR(128), @Col NVARCHAR(128), @Type NVARCHAR(50), @Len INT;
DECLARE @SQL NVARCHAR(MAX);
DECLARE @ActualMax INT, @RowCnt INT;

-- 1. ดึง Column เป้าหมาย (เพิ่ม table_catalog สำหรับ DB Name)
DECLARE cur_all_cols CURSOR FOR
SELECT table_catalog, table_schema, table_name, column_name, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
FROM information_schema.columns
WHERE DATA_TYPE IN ('char', 'nchar', 'binary', 'varchar', 'nvarchar', 'varbinary')
  AND CHARACTER_MAXIMUM_LENGTH > 0
  AND table_schema NOT IN ('sys', 'information_schema');

OPEN cur_all_cols;
FETCH NEXT FROM cur_all_cols INTO @Db, @Schema, @Table, @Col, @Type, @Len;

-- 2. วนลูป Scan หา Max Length ของจริง
WHILE @@FETCH_STATUS = 0
BEGIN
    RAISERROR ('Scanning: %s.%s.%s [%s]', 0, 1, @Db, @Schema, @Table, @Col) WITH NOWAIT;

    IF @Type IN ('char', 'nchar', 'varchar', 'nvarchar')
        SET @SQL = N'SELECT @MaxVal = MAX(LEN(' + QUOTENAME(@Col) + ')), @Cnt = COUNT(*) FROM ' + QUOTENAME(@Schema) + '.' + QUOTENAME(@Table);
    ELSE
        SET @SQL = N'SELECT @MaxVal = MAX(DATALENGTH(' + QUOTENAME(@Col) + ')), @Cnt = COUNT(*) FROM ' + QUOTENAME(@Schema) + '.' + QUOTENAME(@Table);

    EXEC sp_executesql @SQL, N'@MaxVal INT OUTPUT, @Cnt INT OUTPUT', @MaxVal = @ActualMax OUTPUT, @Cnt = @RowCnt OUTPUT;

    -- Insert ลงตารางพร้อมชื่อ Database
    INSERT INTO @AnalysisTable VALUES (@Db, @Schema, @Table, @Col, @Type, @Len, ISNULL(@ActualMax, 0), @RowCnt);

    FETCH NEXT FROM cur_all_cols INTO @Db, @Schema, @Table, @Col, @Type, @Len;
END

CLOSE cur_all_cols;
DEALLOCATE cur_all_cols;

-- 3. แสดงผลและสร้าง Script
SELECT
    DatabaseName, -- แสดงชื่อ DB เป็นคอลัมน์แรก
    SchemaName,
    TableName,
    ColumnName,

    -- Current State
    UPPER(DataType) + '(' + CAST(DefinedLength AS VARCHAR) + ')' AS CurrentType,
    ActualMaxLength,

    -- Target State
    CASE
        WHEN DataType IN ('char', 'varchar') THEN 'VARCHAR'
        WHEN DataType IN ('nchar', 'nvarchar') THEN 'NVARCHAR'
        WHEN DataType IN ('binary', 'varbinary') THEN 'VARBINARY'
    END +
    '(' + CAST(CASE WHEN ActualMaxLength < 1 THEN 1 ELSE ActualMaxLength END AS VARCHAR) + ')'
    AS TargetType,

    -- Priority
    CASE
        WHEN DataType IN ('char', 'nchar', 'binary') AND ActualMaxLength < DefinedLength THEN 'HIGH (Space Saving)'
        WHEN DataType IN ('varchar', 'nvarchar', 'varbinary') AND ActualMaxLength < DefinedLength THEN 'LOW (Schema Tightening)'
        ELSE 'SKIP'
    END AS Priority,

    -- Alter Script
    'ALTER TABLE ' + QUOTENAME(SchemaName) + '.' + QUOTENAME(TableName) +
    ' ALTER COLUMN ' + QUOTENAME(ColumnName) + ' ' +
    CASE
        WHEN DataType IN ('char', 'varchar') THEN 'VARCHAR'
        WHEN DataType IN ('nchar', 'nvarchar') THEN 'NVARCHAR'
        WHEN DataType IN ('binary', 'varbinary') THEN 'VARBINARY'
    END +
    '(' + CAST(CASE WHEN ActualMaxLength < 1 THEN 1 ELSE ActualMaxLength END AS VARCHAR) + ');'
    AS AlterScript

FROM @AnalysisTable
WHERE
    RowCountVal > 0
    AND ActualMaxLength < DefinedLength
ORDER BY
    Priority,
    DatabaseName,  -- เรียงตาม Database
    SchemaName,    -- เรียงตาม Schema
    TableName,     -- เรียงตาม Table
    ColumnName;    -- เรียงตาม Column

อ่านเพิ่มเติม