Here is my final code:

/* Finds all db files with less than 20% free space and create statements to grow

to an appropriate size.

*/

SET NOCOUNT ON

DECLARE @dbname NVARCHAR(200)

DECLARE @SQLStr NVARCHAR(4000)

DECLARE @filetype VARCHAR(10)

DECLARE @filesizeMB INT

DECLARE @logicalname NVARCHAR(200)

DECLARE @queryonly BIT

SET @filetype = ‘DATA’ — Use DATA for data files, Use LOG for transaction log files

SET @queryonly = 0 — 0 = generate ALTER DATABASE statements, 1 = just run query to show free space data

IF OBJECT_ID(‘tempdb..#DBName’) IS NOT NULL

DROP TABLE #DBName;

CREATE TABLE #DBName (Name NVARCHAR(200))

IF OBJECT_ID(‘tempdb..##FileStats’) IS NOT NULL

DROP TABLE ##FileStats;

CREATE TABLE ##FileStats (DBName NVARCHAR(200),

FileType NVARCHAR(100), [FileName] NVARCHAR(100),

CurrentSizeMB FLOAT, FreeSpaceMB FLOAT,

PercentMBFree FLOAT, FileLocation NVARCHAR(2000))

IF OBJECT_ID(‘tempdb..#FilesToGrow’) IS NOT NULL

DROP TABLE #FilesToGrow;

CREATE TABLE #FilesToGrow (FileShouldBeThisSizeMB INT, DBName NVARCHAR(200), [FileName] NVARCHAR(200))

INSERT INTO #DBName

SELECT sd.name

FROM sys.databases sd

WHERE sd.name NOT IN (‘master’,’model’,’msdb’,’tempdb’)

AND sd.name NOT IN (SELECT d.name

FROM sys.databases d

INNER JOIN sys.availability_databases_cluster adc ON d.group_database_id = adc.group_database_id

INNER JOIN sys.availability_groups ag ON adc.group_id = ag.group_id

INNER JOIN sys.dm_hadr_availability_replica_states rs ON ag.group_id = rs.group_id AND d.replica_id = rs.replica_id

WHERE rs.role_desc = ‘SECONDARY’) — exclude dbs in AG secondary

AND sd.is_read_only = 0 AND sd.state = 0 — exclude dbs that are read only or offline

DECLARE DBCur CURSOR FOR

SELECT Name FROM #DBName

OPEN DBCur

WHILE (1=1)

BEGIN

FETCH NEXT FROM DBCur INTO @dbname

IF @@FETCH_STATUS < 0 BREAK

SET @SQLStr = ‘USE [‘ + @dbname + ‘]

INSERT INTO ##FileStats

SELECT DB_NAME() AS DbName,

CASE WHEN type = 0 THEN ”DATA” ELSE ”LOG” END AS FileType,

name AS FileName,

size/128.0 AS CurrentSizeMB,

size/128.0 – CAST(FILEPROPERTY(name, ”SpaceUsed”) AS INT)/128.0 AS FreeSpaceMB,

100*(1 – ((CAST(FILEPROPERTY(name, ”SpaceUsed”) AS INT)/128.0)/(size/128.0))) AS PercentMBFree,

physical_name AS FileLocation

FROM sys.database_files’

EXEC sp_executesql @SQLStr

END

— Close and deallocate the cursor

CLOSE DBCur

DEALLOCATE DBCur

IF @queryonly = 1

SELECT DBName,

FileType,

FileName,

ROUND(CurrentSizeMB, 2) AS CurrentSizeMB,

ROUND(FreeSpaceMB, 2) AS FreeSpaceMB,

ROUND((FreeSpaceMB*100/CurrentSizeMB),2) AS PercentFree,

ROUND(((CurrentSizeMB/.8)-(CurrentSizeMB)),2) AS MBNeededToMakeFileHave20PctFreeSpace,

ROUND(((CurrentSizeMB/.8)-(CurrentSizeMB))+(CurrentSizeMB)+5,0) AS FileShouldBeThisSizeMB, — pad it by 5MB and round to a whole number

FileLocation

FROM ##FileStats

WHERE ROUND((FreeSpaceMB*100/CurrentSizeMB), 2) < 20

AND FileType = @filetype

ORDER BY DBName

IF @queryonly = 0

BEGIN

INSERT INTO #FilesToGrow (FileShouldBeThisSizeMB, DBName, [FileName])

SELECT

ROUND(((CurrentSizeMB/.8)-(CurrentSizeMB))+(CurrentSizeMB)+5,0) AS FileShouldBeThisSizeMB, — pad it by 5MB and round to a whole number

DBName, [FileName]

FROM ##FileStats

WHERE ROUND((FreeSpaceMB*100/CurrentSizeMB), 2) < 20 — file has less than 20% free space

AND FileType = @filetype

ORDER BY DBName

DECLARE DBFileGrow CURSOR FOR

SELECT FileShouldBeThisSizeMB, DBName, [FileName] FROM #FilesToGrow

ORDER BY DBName

OPEN DBFileGrow

WHILE (1=1)

BEGIN

FETCH NEXT FROM DBFileGrow INTO @filesizeMB, @dbname, @logicalname

IF @@FETCH_STATUS < 0 BREAK

SET @SQLStr = ‘ALTER DATABASE [‘ + @dbname + ‘] MODIFY FILE (NAME = N”’+@logicalname+”’, SIZE = ‘+CAST(@filesizeMB AS NVARCHAR(10))+’MB)’

–EXEC (@SQLStr)

PRINT @SQLStr

END

— Close and deallocate the cursor

CLOSE DBFileGrow

DEALLOCATE DBFileGrow

END

— Clean up

DROP TABLE #DBName

DROP TABLE ##FileStats

DROP TABLE #FilesToGrow

There are 2 parameters at the top of the script.  Set @filetype to query either DATA or LOG files and use @queryonly to have the file space information displayed instead of generating the ALTER DATABASE statements. 

I chose to look at data and log files separately because I felt that log files may not need to be grown if there is less than 20% space free in it.  If there is not much free space in the log file, then it really might be best to back it or the database up to free space in it.  That is why I felt that log files need to be handled a bit differently than the data file so I queried them separately.

I also thought that I needed to check the space needed for all files so that I would be able to take that information to Excel so that I can sort by drive and SUM up the total space needed and could then make sure that my drives had enough space free for the files to grow into.  When you set @queryonly = 1, this is the result.

You will also notice that in my calculation for the final file size, I added 5MB.  I did that just to give each file a tiny bit more space than exactly 20% free.  You can modify or remove that if you want.  From experience, it seems like the right thing to do to me.

Just like my previous post, I have the statements being printed for now but I put in an EXEC command that can be uncommented to allow the ALTER DATABASE statements to run.

Hope this helps someone else.  I know it helped me a lot!

Leave a Reply

Your email address will not be published. Required fields are marked *