Skip to content

Recommend Decimal Field Sizes

A script that scans actual decimal values and recommends the tightest fitting data type for each column, with ALTER scripts.

Published Oct 15, 2010

Decimal columns are often declared far wider than the data actually needs. This script scans every decimal column in a schema (or one table), finds the minimum and maximum value actually stored, works out the worst-case width of the whole and fractional parts, and recommends the tightest type that will fit - TinyInt, SmallInt, Int, BigInt or a right-sized Decimal(p, s) - generating the ALTER COLUMN statement for each.

Set @SchemaName and optionally @TableName at the top before running.

SET NOCOUNT ON;

DECLARE @tSQL nVarChar(MAX)
DECLARE @SchemaName nVarchar(128)
DECLARE @TableName nVarchar(128)

SET @SchemaName = 'dbo'
SET @TableName = ''

SELECT
	newid() as Id,
	type_name(SysC.user_type_id) as Type,
	OBJECT_SCHEMA_NAME(SysC.object_id) as [Schema],
	OBJECT_NAME(SysC.object_id) as [Object],
	SysC.name as [Column],
	case
		WHEN type_name(SysC.user_type_id) like 'N%' THEN SysC.max_length / 2
		ELSE SysC.max_length
	END as CurrentMaxLength,
	cast(null as bit) as Nullable,
	cast(null as Decimal(38, 5)) as MinEncountered,
	cast(null as Decimal(38, 5)) as MaxEncountered,
	cast(null as VarChar(50)) as WorstCase
INTO
	#RecordSizes
FROM
	sys.columns as SysC
INNER JOIN sys.types as SysT
	ON SysT.user_type_id = SysC.user_type_id
INNER JOIN sys.objects as SysO
	ON SysO.object_id = SysC.object_id
WHERE
	SysO.type = 'U'
	AND (OBJECT_SCHEMA_NAME(SysO.object_id) = @SchemaName OR NullIf(@TableName, '') IS NULL)
	AND (OBJECT_NAME(SysO.object_id) = @TableName OR NullIf(@TableName, '') IS NULL)
	AND SysT.name in ('decimal')

DECLARE RecordChecks CURSOR FAST_FORWARD FOR
	SELECT
		'UPDATE #RecordSizes SET Nullable = IsNull((SELECT TOP 1 1 FROM [' + [Schema] + '].[' + [Object] + '] WHERE [' + [Column] + '] IS NULL), 0),'
		+ ' MaxEncountered = (SELECT MAX(IsNull([' + [Column] + '], 0)) FROM [' + [Schema] + '].[' + [Object] + ']),'
		+ ' MinEncountered = (SELECT MIN(IsNull([' + [Column] + '], 0)) FROM [' + [Schema] + '].[' + [Object] + '])'
		+ ' WHERE Id = ''' + Convert(VarChar(36), Id)  + ''''
	FROM
		#RecordSizes
OPEN RecordChecks

FETCH FROM RecordChecks INTO @tSQL

WHILE(@@FETCH_STATUS = 0)
BEGIN--WHILE
	----------------------------------------------------------------------------------------

	EXEC(@tSQL)

	----------------------------------------------------------------------------------------
	FETCH FROM RecordChecks INTO @tSQL
END--WHILE

CLOSE RecordChecks DEALLOCATE RecordChecks

UPDATE
	#RecordSizes
SET
	WorstCase =
		CASE WHEN
			LEN(Convert(VarChar, MinEncountered)) > LEN(Convert(VarChar, MaxEncountered)) THEN Convert(VarChar, MinEncountered)
			ELSE Convert(VarChar, MaxEncountered)
		END

SELECT
	*,
	CASE WHEN Cast(Fraction as int) = 0
	THEN
		CASE
			WHEN Cast(Whole as Decimal(38)) <= 255 THEN 'TinyInt'
			WHEN Cast(Whole as Decimal(38)) <= 32767 THEN 'SmallInt'
			WHEN Cast(Whole as Decimal(38)) <= 2147483647 THEN 'Int'
			WHEN Cast(Whole as Decimal(38)) <= 9223372036854775808 THEN 'BigInt'
		END
	ELSE
		'Decimal('
		+ Cast( Len(Whole) + Len(Replace(Fraction, '0', ' ')) as varchar)
		+', '
		+ Cast(Len(Replace(Fraction, '0', ' ')) as VarChar) + ')'
	END as DataType,
	'ALTER TABLE  [' + [Schema] + '].[' + [Object] + ']'
	+ ' ALTER COLUMN [' + [Column] + '] '

	+ CASE WHEN Cast(Fraction as int) = 0
	THEN
		CASE
			WHEN Cast(Whole as Decimal(38)) <= 255 THEN 'TinyInt'
			WHEN Cast(Whole as Decimal(38)) <= 32767 THEN 'SmallInt'
			WHEN Cast(Whole as Decimal(38)) <= 2147483647 THEN 'Int'
			WHEN Cast(Whole as Decimal(38)) <= 9223372036854775808 THEN 'BigInt'
		END
	ELSE
		'Decimal('
		+ Cast( Len(Whole) + Len(Replace(Fraction, '0', ' ')) as varchar)
		+', '
		+ Cast(Len(Replace(Fraction, '0', ' ')) as VarChar) + ')'
	END
	+ ' ' + CASE WHEN Nullable = 1 THEN 'NULL' ELSE 'NOT NULL' END as AlterScript
FROM
	(
		SELECT
			Type,
			[Schema],
			[Object],
			[Column],
			Nullable,
			WorstCase,
			Left(WorstCase, CharIndex('.', WorstCase) - 1) as Whole,
			Right(WorstCase, LEN(WorstCase) - CharIndex('.', WorstCase)) as Fraction
		FROM
			#RecordSizes
	) as Info

DROP TABLE #RecordSizes
An unhandled error has occurred. Reload 🗙