Azure waste

SQL elastic pools with no databases

A shared database pool with no databases in it
The agent reports this
Why it happens

The pool reserves and bills capacity up front. Databases get moved out during a tidy-up and the empty pool keeps charging.

An elastic pool bills for its reserved capacity whether or not any database sits in it. Databases get moved out during a consolidation and the pool is left.

Paste this into Resource Graph Explorer in the Azure portal.

resources
| where type =~ 'microsoft.sql/servers/elasticpools'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
| extend poolId = tolower(id)
| join kind=leftouter (
    resources
    | where type =~ 'microsoft.sql/servers/databases'
    | extend poolId = tolower(tostring(properties.elasticPoolId))
    | where isnotempty(poolId)
    | summarize databases = count() by poolId
  ) on poolId
| extend databases = coalesce(databases, 0)
| where databases <= 0
| project pool = name, resourceGroup, location, databases,
          sku = tostring(sku.name),
          capacity = toint(sku.capacity)

The rule is named for pools with NO databases and the previous version of this query counted none, listing every pool you own including busy ones. The join is the whole check: a database names its pool in properties.elasticPoolId, and the detector lowercases both sides because that property returns the ARM id in either case. Deleting a pool that still hosts databases takes them offline, which is why the detector refuses to report anything at all when databases were not collected rather than assuming a pool is empty, and why a query without the count is the wrong shape rather than merely a loose one. Honest about verification: the reference subscription owns no elastic pools, so this returns nothing there and the selection is unproven on real pools. What was proven is the mechanism, by running the identical join and summarize against virtual machines and their disks in the same subscription, which returned the three machines with one disk each.

Applies to microsoft.sql/servers/elasticpools
What the agent does about it

A SQL elastic pool billing its configured DTU or vCore capacity with no databases inside it.

It will not fix this one

It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.