DBA Scripts: Get Backup Size Trend

🔧Part of the DBA-Tools Project, copy/paste SQL Server scripts and health checks.In: Backup & Recovery

What Your Backup Sizes Are Quietly Telling You

The backup drive is filling faster than planned, or a full backup suddenly ran long, and you need to know which database grew and when. This rolls msdb backup history up to one row per database, per backup type, per month, so the change shows in a few lines instead of hundreds of backup rows.


Why Backup Size Trend Matters

  • A month where a database’s full backup size drops sharply is worth investigating immediately, it can mean real, unexpected data loss, not just less activity
  • Steady month-over-month growth tells you when a database will outgrow its current backup window or storage allocation, well before it becomes an emergency
  • avg_compression_pct moving over time can flag a change in the data itself (more already-compressed data like images, or a schema change) as easily as a backup configuration change
  • This is a trend script, not a point-in-time check, one month’s number means little, the shape of the line across twelve months is the actual signal

When to Run This Script

  • Monthly, as part of a capacity-planning or storage-review cadence, not just when something already looks wrong
  • After any backup job or maintenance plan change, to confirm sizes are still behaving as expected
  • When a backup suddenly takes longer or uses more storage, to see whether database growth is the cause
  • Alongside Database Sizes and Free Space when building a full storage capacity picture

The Script

Run the following script against your SQL Server instance.

✓ Verified
  • Tested on: SQL Server 2025 (RTM CU8) 17.0.4075.5, Windows lab instance
  • Last verified: 2026-10-05 (the Example Output is a real run on the lab instance)
  • Permissions: any login that can connect, because msdb grants SELECT on dbo.backupset to public
  • Safety: read-only, impact low
/*
Script Name : Get-BackupSizeTrend
Category    : backups
Purpose     : Monthly backup size trend per database over the last 12 months, an indirect proxy for data growth rate. Shrinking backups can indicate unexpected data loss; growing backups inform storage planning.
Author      : Peter Whyte (https://sqldba.blog/dba-scripts-get-backup-size-trend/)
Requires    : any login that can connect (msdb grants SELECT on dbo.backupset to public)
*/
-- SAFE:ReadOnly
-- IMPACT:Low
SET NOCOUNT ON;

SELECT
    database_name,
    CASE type
        WHEN 'D' THEN 'Full'
        WHEN 'I' THEN 'Differential'
        WHEN 'L' THEN 'Log'
        ELSE type
    END AS backup_type,
    CAST(YEAR(backup_finish_date) AS CHAR(4)) + '-' +
    RIGHT('0' + CAST(MONTH(backup_finish_date) AS VARCHAR(2)), 2) AS year_month,
    COUNT(*) AS backup_count,
    CAST(AVG(backup_size) / 1073741824.0 AS DECIMAL(10,3)) AS avg_size_gb,
    CAST(MAX(backup_size) / 1073741824.0 AS DECIMAL(10,3)) AS max_size_gb,
    CAST(MIN(backup_size) / 1073741824.0 AS DECIMAL(10,3)) AS min_size_gb,
    CAST(AVG(CASE WHEN compressed_backup_size > 0
                  THEN compressed_backup_size END)
                                    / 1073741824.0 AS DECIMAL(10,3)) AS avg_compressed_gb,
    CAST(AVG(CASE WHEN compressed_backup_size > 0 AND backup_size > 0
                  THEN CAST(compressed_backup_size AS FLOAT) / backup_size * 100
                  END) AS DECIMAL(5,1)) AS avg_compression_pct
FROM msdb.dbo.backupset
WHERE backup_finish_date >= DATEADD(MONTH, -12, GETDATE())
  AND type IN ('D', 'I', 'L')
GROUP BY database_name, type, YEAR(backup_finish_date), MONTH(backup_finish_date)
ORDER BY database_name, backup_type, year_month;
SSMS results grid from Get-BackupSizeTrend: one row per database, backup type and month with backup count, average and largest size in GB and compression ratio

One row per database, per backup type, per month, pulled straight from msdb.dbo.backupset (MS Docs), no other server required.


How To Run From The Repo

Clone DBA Tools, initialize and run the script:

# Clone dba-tools repo:
git clone https://github.com/peterwhyte-lgtm/dba-tools

# Initialize environment:
cd dba-tools
.\Initialize-Environment.ps1

# Get 12 months of backup size trend per database:
.\run.ps1 Get-BackupSizeTrend

# To run against a remote sql server:
.\run.ps1 Get-BackupSizeTrend -ServerInstance SQLSERVER01

This script lives in the repo at:


Example Output

One run on the lab instance returned 151 rows across 96 databases. These are the 10 rows for 4 of them, with max_size_gb and min_size_gb left out for width.

Get-BackupSizeTrend on the lab instanceexample output, not something to copy
database_name backup_type year_month backup_count avg_size_gb avg_compressed_gb avg_compression_pct ----------------- ----------- ---------- ------------ ----------- ----------------- ------------------- GrowthLab Full 2026-05 3 .804 .008 1.0 GrowthLab Full 2026-07 3 .875 .009 1.0 GrowthLab Log 2026-07 5 .256 .025 24.4 RandomLab Full 2026-05 3 .005 .001 11.7 RandomLab Full 2026-07 3 .039 .001 2.6 RandomLab Log 2026-07 5 .012 .001 17.0 SalesDemo Full 2026-05 3 .128 .022 17.0 SalesDemo Full 2026-07 2 .205 .022 10.8 WatchtowerMetrics Full 2026-05 3 2.336 .145 6.2 WatchtowerMetrics Full 2026-07 4 2.337 .144 6.2

Read down each database. GrowthLab’s full backup grew from 0.804 GB to 0.875 GB over 2 months, 8.8%, steady growth to plan storage around. SalesDemo’s grew 60%, from 0.128 GB to 0.205 GB, yet its backup file stayed at 0.022 GB because the new data compressed well.

When a drive is filling, read avg_compressed_gb: it is what lands on disk. avg_size_gb is the data before compression, so on GrowthLab 0.875 GB of data wrote a 0.009 GB file.


Understanding the Results

avg_compressed_gb
The backup file as written to disk, averaged over the month’s backups. MS Docs defines compressed_backup_size as the bytes stored on disk, and on an uncompressed backup it equals the uncompressed size.Act when the backup drive is filling. This column times backup_count times your retention is the space the backups take. avg_size_gb overstates it on every compressed backup.
avg_size_gb
max_size_gb
min_size_gb
The uncompressed size of the backup set: the month’s average, largest and smallest. For a Full row it tracks the data in the database. For a Differential row it is the data changed since the last full, and for a Log row the log generated since the previous log backup, so those 2 measure change rate, not database size.Act when a Full row’s avg_size_gb drops between months. Treat it as possible data loss until a cleanup, an archive job or a backup of the wrong database explains it.
avg_compression_pct
The compressed file as a percentage of the uncompressed size, so lower is better compression and 100.0 means the backups were not compressed. It is an average across the month, so a month mixing compressed and uncompressed backups shows a figure neither kind produced.Act when it jumps toward 100. Either backup compression was switched off, or the data stopped compressing: adding random binary data to a lab database took its full backups from 1.6 to 28.6.
backup_count
Backups that finished in that month, COPY_ONLY backups included. The window is 12 months back from the moment you run it, so the current month and the oldest month are both partial.Act when it does not match the schedule. Too many is usually copy-only backups taken outside it; too few is a schedule that stopped, so cross-check against Backup Coverage.

Best Practices

  • Review this monthly as part of a standing capacity or storage cadence, not just reactively
  • Treat any sharp drop in avg_size_gb as an incident-worthy finding until proven otherwise, silent data loss is the worst-case explanation
  • Pair with Database Sizes and Free Space and Backup Restore Duration Estimate for a full picture of where storage and backup-window pressure is heading
  • Twelve months is the window this script covers by design, long enough to see a real trend, short enough to stay fast; adjust the DATEADD(MONTH, -12, ...) window if your retention or reporting cadence needs more history

Frequently Asked Questions

Why 12 months instead of all available history?

Twelve months is enough to see a genuine trend, including any seasonal pattern in data volume, without scanning years of msdb.dbo.backupset history on a busy instance. The window runs 12 months back from the moment you run it, so the first and the current month are both partial. Extend the DATEADD window if you need a longer view for a specific investigation.

Does a shrinking backup always mean data loss?

Not always, an archival job, a deliberate cleanup, or a change to what’s included in the backup can all explain it legitimately. But it’s always worth confirming which of those it is rather than assuming, since real unexpected data loss looks exactly the same in this report.

Why is avg_compression_pct 100.0 on some rows?

Those backups were not compressed. SQL Server records an uncompressed backup with compressed_backup_size equal to backup_size, so the ratio is exactly 100. A figure in between can be a blend: 1 uncompressed and 3 compressed full backups in one month on the lab averaged 39.8. Check the job’s WITH COMPRESSION option and the instance’s backup compression default.

Why does one month show far more full backups than the schedule takes?

The script counts COPY_ONLY backups with the scheduled ones, because msdb records both in the same table. Ad hoc copies, a third-party tool or a test harness can add hundreds: one lab database logged 589 copy-only fulls in a single month, and because they were uncompressed its avg_compression_pct went from 7.9 to 100.0 at the same time. Add AND is_copy_only = 0 to the WHERE clause to see the scheduled backups alone.

Who can run this script?

Any login that can connect. msdb grants SELECT on dbo.backupset to public and lets guest in, so on the lab instance a login with no msdb user and no role returned the same 151 rows as sysadmin. Backup history, database names included, is readable by every login unless that grant has been revoked.


Summary

Backup size is data SQL Server already tracks for you, msdb.dbo.backupset has the full history, this script just turns twelve months of it into one readable trend per database. A shrinking backup deserves the same urgency as a failed one, and a steadily growing one is exactly the early signal capacity planning needs.

Add this to a monthly review alongside the rest of the backup and storage series, and treat any unexplained drop in size as worth investigating immediately, not filed away for later.


Related

Comments

Leave a Reply

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