Optimize Azure Spend Using Cost Analysis Filters: A Practical Azure FinOps Guide

Cost Analysis Is the Starting Point for Every Optimization Decision

Azure Cost Management provides a powerful query engine for investigating where money goes. But most teams use it at a surface level — opening the default view, glancing at the top-level chart, and closing the tab. The real optimization value comes from understanding the filtering and grouping system well enough to ask precise questions about your spend. This guide covers every filter dimension, view configuration, and query pattern you need to turn Cost Analysis from a reporting tool into an optimization engine.

Understanding the Filter Dimensions

Cost Analysis supports over 30 filter dimensions. Each dimension lets you slice cost data along a different axis. Knowing which dimension to use for which question is the foundation of effective cost analysis.

Resource-Level Dimensions

Dimension What It Tells You Optimization Use
Resource Individual resource name Find top-spending resources for right-sizing
ResourceId Full ARM resource ID Programmatic identification for automation
Resource group Resource group name Team/project-level cost attribution
Resource type ARM resource type (e.g., Microsoft.Compute/virtualMachines) Category-level spend analysis
Location Azure region Identify cross-region cost differences and optimization by region selection

Service and Meter Dimensions

Dimension What It Tells You Optimization Use
ServiceName Azure service classification Identify which services drive the most cost
ServiceFamily Service family (Compute, Analytics, Security) High-level spending category analysis
Meter category Service breakdown by meter Distinguish compute vs. storage vs. networking costs within a service
Meter subcategory Further meter classification Identify specific SKU tiers generating cost
Meter Individual usage meter Root-cause analysis of cost changes at the meter level

Pricing and Purchase Dimensions

Dimension What It Tells You Optimization Use
Pricing model OnDemand, Reservation, Spot, Savings Plan Measure reservation and savings plan coverage
Charge type Usage, purchase, refund, unused reservation Find wasted reservation spend (amortized view)
Frequency OneTime, Recurring, UsageBased Separate one-time purchases from recurring charges
Publisher type Microsoft vs. Marketplace Track third-party Marketplace spending separately

Organizational Dimensions

Dimension What It Tells You Optimization Use
Subscription Azure subscription Per-subscription cost tracking and comparison
Department EA department Business unit cost allocation (EA only)
BillingProfileName Billing profile Invoice-level cost segmentation
Tag Resource tag key:value pairs Custom cost allocation by environment, team, project, cost center

Practical Filter Patterns for Optimization

Cost Analysis does not support grouping by multiple dimensions simultaneously. The workaround is combining a filter on one dimension with a group-by on another. This filter-then-group pattern is the core technique for targeted cost investigation.

Pattern 1: Find Top-Spending Resources Per Service

  1. Open Cost Analysis → select Cost by service smart view
  2. Identify the highest-cost service (for example, Virtual Machines)
  3. Click the service slice to apply it as a filter (or add filter manually: ServiceName = Virtual Machines)
  4. Change Group by to Resource
  5. Sort by cost to identify the top-spending VMs for right-sizing evaluation

Pattern 2: Identify Unused Reservations

  1. Switch to Amortized cost view (bottom of the view selector or toggle in customization)
  2. Add filter: Charge type = UnusedReservation
  3. Group by Reservation
  4. The result shows each reservation with unused capacity and the dollar amount wasted

This pattern only works in the amortized view. In actual cost view, reservation purchases appear as lump sums on the purchase date, and unused capacity is invisible.

Pattern 3: Compare On-Demand vs. Reserved Pricing

  1. Group by Pricing model
  2. The stacked chart shows OnDemand, Reservation, Savings Plan, and Spot segments
  3. A large OnDemand segment relative to total compute indicates reservation purchase opportunities
  4. Cross-reference with Azure Advisor reservation recommendations for specific purchase suggestions

Pattern 4: Track Costs by Environment Tag

  1. Add filter: Tag → select your environment tag key (e.g., “environment”)
  2. Select values: Production, Development, Staging
  3. Group by ServiceName to see which services cost the most in each environment
  4. Untagged resources appear in a separate group — these represent your tagging compliance gap

Tags are only included in cost data while actively applied and the resource is running. Tags are not applied retroactively. Enable tag inheritance in Cost Management settings to have resource group and subscription tags flow down to child resource usage records automatically.

Pattern 5: Spot Resources Running 24/7

  1. Set granularity to Daily
  2. Group by Resource
  3. Filter to a specific resource group or service
  4. Look for resources with consistent daily costs seven days a week — these might be dev/test workloads that should only run during business hours

Actual vs. Amortized Cost: When to Use Each

Choosing the wrong cost view leads to incorrect optimization conclusions. The two views answer fundamentally different questions.

Aspect Actual Cost Amortized Cost
Reservation purchases Full amount on purchase date Spread evenly over the term (daily or monthly)
Unused reservations Not visible Shown as UnusedReservation charge type
Best for Invoice reconciliation, cash flow tracking True resource cost, chargeback, finding wasted reservations
Monthly pattern Purchase month shows large spike Costs distributed evenly across months

For optimization work, always use amortized cost. It reveals the true cost of running each resource (including the amortized portion of reservations) and exposes unused reservation capacity that actual cost hides.

Granularity Selection Strategy

Cost Analysis supports three granularity levels, each with specific limits and use cases:

Granularity Maximum Date Range Best For
Daily 3 months (92 days); 1 month at management group scope Spike investigation, usage patterns, identifying weekend/overnight waste
Monthly 12 months Trend analysis, month-over-month comparisons, budget tracking
None (accumulated) 12 months Summary totals, forecast visualization, budget vs. actual comparison

If you select daily granularity with a date range exceeding 92 days, Cost Analysis automatically switches to monthly granularity and adjusts the dates to align with full calendar months. For cost spike investigation, always start with daily granularity to pinpoint the exact day of the cost change.

Saved Views for Recurring Analysis

Once you build a useful filter configuration, save it to avoid recreating it every time. Each user can create up to 100 private views across all scopes, and each scope supports up to 100 shared views visible to anyone with Cost Management Reader access.

Creating a Saved View

  1. Configure your filters, grouping, granularity, and chart type
  2. Select Save as to create a new view (or Save to update the current one)
  3. Name the view descriptively (e.g., “Prod VM Costs – Daily” or “Unused Reservations – Monthly”)
  4. Choose Private (only you) or Shared (visible to anyone with Reader access)

The saved view stores only the query configuration, not the underlying data. Each time you open the view, it executes a fresh query against current cost data.

Subscribing to Scheduled Email Reports

Turn any saved view into an automated email report:

  1. Open the saved view
  2. Select Subscribe from the toolbar
  3. Set the frequency: Daily, Weekly (specific day), or Monthly (specific day)
  4. Add email recipients (up to 20)
  5. Optionally include a CSV data attachment along with the chart image

This creates a scheduled action that emails the view’s chart and optionally the raw data on your chosen cadence. Use this for weekly cost reviews or monthly showback reports without requiring portal access.

Querying Costs Programmatically with Filters

REST API Query with Filters

The Cost Management Query API accepts dimension and tag filters for programmatic cost analysis. This enables building custom reports, integrations, and automated optimization workflows.

POST https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2025-03-01

{
  "type": "AmortizedCost",
  "timeframe": "TheLastMonth",
  "dataset": {
    "granularity": "Daily",
    "aggregation": {
      "totalCost": {
        "name": "PreTaxCost",
        "function": "Sum"
      }
    },
    "grouping": [
      {
        "name": "ResourceGroup",
        "type": "Dimension"
      }
    ],
    "filter": {
      "and": [
        {
          "dimensions": {
            "name": "ServiceName",
            "operator": "In",
            "values": ["Virtual Machines", "Storage"]
          }
        },
        {
          "tags": {
            "name": "environment",
            "operator": "In",
            "values": ["production"]
          }
        }
      ]
    }
  }
}

The API supports up to 2 grouping clauses and 2 aggregation clauses per query. The only filter operator available is In. Filter expressions support and and or logical operators, each requiring at least two child expressions.

PowerShell Queries with Filters

# Query costs filtered by resource group
$dimensionFilter = New-AzCostManagementQueryComparisonExpressionObject `
    -Name 'ResourceGroup' `
    -Value @('rg-prod-compute', 'rg-prod-data')

$filter = New-AzCostManagementQueryFilterObject -Dimensions $dimensionFilter

$result = Invoke-AzCostManagementQuery `
    -Scope "/subscriptions/$subscriptionId" `
    -Timeframe TheLastMonth `
    -Type AmortizedCost `
    -DatasetGranularity Monthly `
    -DatasetFilter $filter

# Parse results
$columns = $result.Column
$result.Row | ForEach-Object {
    $row = $_
    $obj = [ordered]@{}
    for ($i = 0; $i -lt $columns.Count; $i++) {
        $obj[$columns[$i].Name] = $row[$i]
    }
    [PSCustomObject]$obj
} | Format-Table -AutoSize

Tag-Based Cost Report Script

# Generate a cost-by-environment report using tag filters
$environments = @("production", "staging", "development")
$subscriptionId = "your-subscription-id"
$report = @()

foreach ($env in $environments) {
    $tagFilter = New-AzCostManagementQueryComparisonExpressionObject `
        -Name 'environment' `
        -Value @($env)
    
    $filter = New-AzCostManagementQueryFilterObject -Tag $tagFilter
    
    $result = Invoke-AzCostManagementQuery `
        -Scope "/subscriptions/$subscriptionId" `
        -Timeframe TheLastMonth `
        -Type AmortizedCost `
        -DatasetGranularity None `
        -DatasetFilter $filter
    
    $totalCost = ($result.Row | ForEach-Object { $_[0] } | Measure-Object -Sum).Sum
    
    $report += [PSCustomObject]@{
        Environment = $env
        MonthlyCost = [math]::Round($totalCost, 2)
    }
}

$report | Sort-Object MonthlyCost -Descending | Format-Table -AutoSize

# Output:
# Environment  MonthlyCost
# -----------  -----------
# production       12847.33
# staging           3421.67
# development       1893.22

Building an Optimization Workflow with Filters

Combine filter patterns into a systematic monthly optimization review.

Week 1: Service-Level Review

  1. Open Cost Analysis with Monthly granularity, TheLastMonth timeframe
  2. Group by ServiceName
  3. Compare to the previous month — identify any service with more than 10% cost increase
  4. For each service with significant increase, drill down by filtering to that service and grouping by Resource

Week 2: Reservation and Savings Plan Health

  1. Switch to Amortized cost view
  2. Filter: Charge type = UnusedReservation
  3. Group by Reservation — quantify wasted reservation spend
  4. Group by Pricing model — calculate reservation and savings plan coverage percentage
  5. Review Azure Advisor recommendations for new reservation purchases

Week 3: Tag Compliance and Showback

  1. Group by your primary cost allocation tag (e.g., CostCenter)
  2. Measure the “Untagged” segment as a percentage of total spend
  3. If untagged spend exceeds your threshold (e.g., 5%), filter to untagged resources and group by Resource group to identify which teams need to improve tagging
  4. Generate per-team showback reports using tag filters

Week 4: Waste Identification

  1. Set granularity to Daily, timeframe to last 30 days
  2. Group by Resource for compute services
  3. Identify resources with flat daily costs (no weekday/weekend variation) — candidates for scheduled shutdown
  4. Cross-reference with Advisor shutdown recommendations (CPU < 3% P95)
  5. Check for orphaned resources: disks without VMs, public IPs without NICs, idle load balancers

Common Mistakes That Undermine Cost Analysis

  • Using actual cost for optimization — Actual cost hides reservation waste and distorts monthly comparisons when purchases occur. Always use amortized cost for optimization analysis.
  • Ignoring the “Others” group — Cost Analysis shows the top 9 items plus an “Others” bucket. If “Others” is a significant portion of total cost, you are missing important detail. Add a filter to reduce the result set so the top 9 covers meaningful items.
  • Not using daily granularity for spike investigation — Monthly granularity masks daily patterns. A resource running 24/7 costs the same monthly as one running only weekdays if the monthly total is the same, but daily granularity reveals the weekday-only pattern.
  • Trusting tags without checking compliance — Tag-based cost reports are only accurate when tagging compliance is high. Always measure the untagged percentage before trusting tag-based showback numbers.
  • Querying the API without caching — The Query API has strict rate limits (12 QPU per 10 seconds). Building a dashboard that queries on every page load will hit these limits with modest user counts. Cache results server-side with a 4-hour TTL.

Automating Cost Analysis Reports

Schedule automated cost reports using the Scheduled Actions API to deliver filtered views directly to stakeholders’ inboxes without requiring portal access.

# Create a scheduled weekly cost report for the production team
$token = (Get-AzAccessToken -ResourceUrl "https://management.azure.com").Token
$subscriptionId = "your-subscription-id"

$body = @{
    kind = "Email"
    properties = @{
        displayName = "Weekly Production Cost Report"
        notification = @{
            subject = "Weekly Azure Cost Report - Production"
            to = @("prod-team@contoso.com", "finops@contoso.com")
            message = "Automated weekly cost report for production resources"
        }
        schedule = @{
            frequency = "Weekly"
            dayOfWeek = "Monday"
            startDate = (Get-Date -Format "yyyy-MM-ddT00:00:00Z")
            endDate = (Get-Date).AddYears(1).ToString("yyyy-MM-ddT00:00:00Z")
        }
        status = "Enabled"
        viewId = "/subscriptions/$subscriptionId/providers/Microsoft.CostManagement/views/prod-weekly-review"
    }
} | ConvertTo-Json -Depth 5

Invoke-RestMethod `
    -Uri "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.CostManagement/scheduledActions/weekly-prod-report?api-version=2025-03-01" `
    -Method PUT `
    -Headers @{ Authorization = "Bearer $token" } `
    -Body $body `
    -ContentType "application/json"

The viewId references a saved view that contains your filter configuration. Create the view with the desired filters first (either in the portal or via API), then reference it in the scheduled action. This decouples the report schedule from the query configuration — update the view’s filters without touching the schedule.

For more details, refer to the official documentation: What is Microsoft Cost Management.

Leave a Reply