patternsqlModerate
what is causing arithmetic overflow in the query below?
Viewed 0 times
thewhatoverflowquerycausingbelowarithmetic
Problem
when I run the following query on MY_Database
I get the following results:
But when I run a dynamic query that gets the percent of free space I get:
Msg 8115, Level 16, State 7, Line 93 Arithmetic overflow error
converting numeric to data type numeric.
I am struggling to find the reason of the arithmetic overflow.
why is it happening?
Why divide by 128? It is because both sys.sysfiles and FILEPROPERTY give the number of 8K
pages, not MB, and to convert from 8K pages to MB you divide by 128
as it is explained here
Why is it dynamic?
Because I actually get the values from each database using sp_ForEachDB as you can see on the example below:
select * from sys.sysfilesI get the following results:
But when I run a dynamic query that gets the percent of free space I get:
Msg 8115, Level 16, State 7, Line 93 Arithmetic overflow error
converting numeric to data type numeric.
DECLARE @command NVARCHAR(MAX)
SELECT @command = 'SELECT db_name() as db_name,
CAST(S.size/128.0 - CAST(FILEPROPERTY(S.name, ' + '''' +
'SpaceUsed' + '''' + ' ) AS int)/128.0 AS int) AS FreeSpaceMB
,CAST(100 * (CAST (((S.size/128.0 -CAST(FILEPROPERTY(S.name,
' + '''' + 'SpaceUsed' + '''' + ' ) AS int)/128.0)/(S.size/128.0))
AS decimal(5,2))) AS varchar(8)) + ' + '''' + '''' + ' AS FreeSpacePct
FROM sys.sysfiles S'
exec sp_executesql @statement = @commandI am struggling to find the reason of the arithmetic overflow.
why is it happening?
Why divide by 128? It is because both sys.sysfiles and FILEPROPERTY give the number of 8K
pages, not MB, and to convert from 8K pages to MB you divide by 128
as it is explained here
Why is it dynamic?
Because I actually get the values from each database using sp_ForEachDB as you can see on the example below:
DECLARE @command VARCHAR(5000)
SELECT @command = 'Use ' + '?' + ' SELECT db_name() as db_name,
CAST(S.size/128.0 - CAST(FILEPROPERTY(S.name, ' + '''' +
'SpaceUsed' + '''' + ' ) AS int)/128.0 AS int) AS FreeSpaceMB
--,CAST(100 * (CAST (((S.size/128.0 -CAST(FILEPROPERTY(S.name,
--' + '''' + 'SpaceUsed' + '''' + ' ) AS int)/128.0)/(S.size/128.0))
--AS decimal(4,2))) AS varchar(8)) + ' + '''' + '''' + ' AS FreeSpacePct
FROM dbo.sysfiles S'
EXEC sp_ForEachDB @commandSolution
I am struggling to find the reason of the arithmetic overflow. why is it happening?
Most likely the metadata is returning some unexpected values that your code cannot handle. For example:
...returns the error mentioned in the question, because the computed (negative!) value will not fit in
There are reasons why size might be reported as much lower than space used, including tempdb file growths, filestream files, bugs in older versions of SQL Server...too many to list. You could/should code defensively against this possibility (and also for offline/defunct files...and so on).
The question is tagged SQL Server 2014, so there's no need to use the deprecated
I might write this query as:
Main advantages:
A dynamic SQL version (to collect information for all databases):
Usual caveats about using
Most likely the metadata is returning some unexpected values that your code cannot handle. For example:
-- Example values returned from sysfiles and FILEPROPERTY
DECLARE
@size integer = 1,
@spaceused integer = 10000;
-- The essence of the code in the question
SELECT
CAST
(
100 *
(
CAST
(
(
(@size/128.0 - @spaceused/128.0)/(@size/128.0)
)
AS decimal(5,2)
)
)
AS varchar(8)
) + '' AS FreeSpacePct;...returns the error mentioned in the question, because the computed (negative!) value will not fit in
decimal(5,2).There are reasons why size might be reported as much lower than space used, including tempdb file growths, filestream files, bugs in older versions of SQL Server...too many to list. You could/should code defensively against this possibility (and also for offline/defunct files...and so on).
The question is tagged SQL Server 2014, so there's no need to use the deprecated
sys.sysfiles view (for backward compatibility with SQL Server 2000):I might write this query as:
SELECT
DatabaseName = DB_NAME(),
[FileName] = DF.name,
FileType = DF.type_desc,
SizeMB = STR(DF.size * Factor.PagesToMB, 10, 2),
SpaceUsedMB = STR(FP.SpaceUsed * Factor.PagesToMB, 10, 2),
FreeSpaceMB = STR(FS.FreeSpace * Factor.PagesToMB, 10, 2),
FreeSpacePct = STR(Factor.ToPct * FS.FreeSpace / DF.size, 7, 4)
FROM sys.database_files AS DF
CROSS APPLY (SELECT FILEPROPERTY(DF.name, 'SpaceUsed')) AS FP (SpaceUsed)
CROSS APPLY (SELECT DF.size - FP.SpaceUsed) AS FS (FreeSpace)
CROSS JOIN (SELECT 8e0 / 1024e0, 1e2) AS Factor (PagesToMB, ToPct);Main advantages:
- It separates out the calculation steps
- Uses float arithmetic to avoid overflows
STRformats the result and does not raise an error on overflow
- It does not cause the error in the question to be thrown
A dynamic SQL version (to collect information for all databases):
DECLARE @SQL nvarchar(2000);
SET @SQL = N'
USE ?;
SELECT
DatabaseName = DB_NAME(),
[FileName] = DF.name,
FileType = DF.type_desc,
SizeMB = STR(DF.size * Factor.PagesToMB, 10, 2),
SpaceUsedMB = STR(FP.SpaceUsed * Factor.PagesToMB, 10, 2),
FreeSpaceMB = STR(FS.FreeSpace * Factor.PagesToMB, 10, 2),
FreeSpacePct = STR(Factor.ToPct * FS.FreeSpace / DF.size, 7, 4)
FROM sys.database_files AS DF
CROSS APPLY (SELECT FILEPROPERTY(DF.name, ''SpaceUsed'')) AS FP (SpaceUsed)
CROSS APPLY (SELECT DF.size - FP.SpaceUsed) AS FS (FreeSpace)
CROSS JOIN (SELECT 8e0 / 1024e0, 1e2) AS Factor (PagesToMB, ToPct);
';
DECLARE @Results AS table
(
DatabaseName sysname NOT NULL,
[FileName] sysname NOT NULL,
FileType nvarchar(60) NOT NULL,
SizeMB char(10) NULL,
SpaceUsedMB char(10) NULL,
FreeSpaceMB char(10) NULL,
FreeSpacePct char(7) NULL
);
INSERT @Results
EXECUTE sys.sp_MSforeachdb
@command1 = @SQL;
SELECT R.*
FROM @Results AS R
ORDER BY R.DatabaseName; -- Or whateverUsual caveats about using
sp_MSforeachdb.Code Snippets
-- Example values returned from sysfiles and FILEPROPERTY
DECLARE
@size integer = 1,
@spaceused integer = 10000;
-- The essence of the code in the question
SELECT
CAST
(
100 *
(
CAST
(
(
(@size/128.0 - @spaceused/128.0)/(@size/128.0)
)
AS decimal(5,2)
)
)
AS varchar(8)
) + '' AS FreeSpacePct;SELECT
DatabaseName = DB_NAME(),
[FileName] = DF.name,
FileType = DF.type_desc,
SizeMB = STR(DF.size * Factor.PagesToMB, 10, 2),
SpaceUsedMB = STR(FP.SpaceUsed * Factor.PagesToMB, 10, 2),
FreeSpaceMB = STR(FS.FreeSpace * Factor.PagesToMB, 10, 2),
FreeSpacePct = STR(Factor.ToPct * FS.FreeSpace / DF.size, 7, 4)
FROM sys.database_files AS DF
CROSS APPLY (SELECT FILEPROPERTY(DF.name, 'SpaceUsed')) AS FP (SpaceUsed)
CROSS APPLY (SELECT DF.size - FP.SpaceUsed) AS FS (FreeSpace)
CROSS JOIN (SELECT 8e0 / 1024e0, 1e2) AS Factor (PagesToMB, ToPct);DECLARE @SQL nvarchar(2000);
SET @SQL = N'
USE ?;
SELECT
DatabaseName = DB_NAME(),
[FileName] = DF.name,
FileType = DF.type_desc,
SizeMB = STR(DF.size * Factor.PagesToMB, 10, 2),
SpaceUsedMB = STR(FP.SpaceUsed * Factor.PagesToMB, 10, 2),
FreeSpaceMB = STR(FS.FreeSpace * Factor.PagesToMB, 10, 2),
FreeSpacePct = STR(Factor.ToPct * FS.FreeSpace / DF.size, 7, 4)
FROM sys.database_files AS DF
CROSS APPLY (SELECT FILEPROPERTY(DF.name, ''SpaceUsed'')) AS FP (SpaceUsed)
CROSS APPLY (SELECT DF.size - FP.SpaceUsed) AS FS (FreeSpace)
CROSS JOIN (SELECT 8e0 / 1024e0, 1e2) AS Factor (PagesToMB, ToPct);
';
DECLARE @Results AS table
(
DatabaseName sysname NOT NULL,
[FileName] sysname NOT NULL,
FileType nvarchar(60) NOT NULL,
SizeMB char(10) NULL,
SpaceUsedMB char(10) NULL,
FreeSpaceMB char(10) NULL,
FreeSpacePct char(7) NULL
);
INSERT @Results
EXECUTE sys.sp_MSforeachdb
@command1 = @SQL;
SELECT R.*
FROM @Results AS R
ORDER BY R.DatabaseName; -- Or whateverContext
StackExchange Database Administrators Q#139986, answer score: 15
Revisions (0)
No revisions yet.