---
title: "8 Call Center Metrics Dashboard Examples (With SQL)"
description: "A call center metrics dashboard shows queue status, service level, average speed of answer (ASA), abandonment rate, average handle time (AHT), first call…"
canonical: "https://www.aifordatabase.com/blog/call-center-metrics-dashboard/"
author: "Dhrumil Barot"
author_bio: "Dhrumil is an enterprise analytics expert. Dhrumil built AI for Database so anyone on a team can get answers from live company data in plain English, without waiting on a data team."
published: 2026-10-30
updated: 2026-10-30
---

# 8 Call Center Metrics Dashboard Examples (With SQL)

**TL;DR:** A call center metrics dashboard shows queue status, service level, average speed of answer (ASA), abandonment rate, average handle time (AHT), first call resolution (FCR), CSAT and cost per call. Below are eight dashboard examples, each with the KPI definition and a SQL query that computes it from one `calls` table, so you can build them on your own data instead of a vendor's template.

A call center metrics dashboard is a single screen of contact center KPIs, refreshed in real time for supervisors and daily or weekly for managers. The core set is the same across Amazon Connect, Genesys Cloud and most vendor templates: service level (calls answered within a target such as 20 seconds), ASA, abandonment rate, AHT (talk + hold + after-call work, divided by calls handled), FCR, CSAT, agent occupancy and cost per call. The hard part is not the list, it is computing each number the same way every time. Every example below uses this table:

```
CREATE TABLE calls (
  id               bigint PRIMARY KEY,
  customer_id      bigint,
  agent_id         int,            -- NULL if never answered
  queue            text,           -- 'billing', 'support', ...
  entered_queue_at timestamptz NOT NULL,
  answered_at      timestamptz,    -- NULL if abandoned
  ended_at         timestamptz,
  hold_seconds     int DEFAULT 0,
  acw_seconds      int DEFAULT 0,  -- after-call work
  abandoned        boolean NOT NULL DEFAULT false,
  csat             smallint        -- 1-5 post-call survey, NULL if none
);
```

The SQL is PostgreSQL; it ports to MySQL or BigQuery with date-function changes.

![One calls table feeds every KPI on the dashboard](https://cdn.sanity.io/images/upsr56wu/production/699d7d1e740494a6a95028872ea27f79c68a598b-1344x456.png?w=1280)

FIG 01 — 4 offered, 2 answered within 20 s, 1 abandoned, AHT over 3 handled calls.

## What metrics should a call center dashboard show?

A call center dashboard should show metrics from three groups, and each audience needs a different slice:

| Group | Metrics | Who watches | Refresh |
| --- | --- | --- | --- |
| Accessibility | Calls in queue, longest wait, service level, ASA, abandonment | Supervisors | Real time (seconds) |
| Efficiency | AHT, talk/hold/ACW split, occupancy, calls handled | Team leads | Every 15-30 min |
| Outcome and cost | FCR, CSAT, cost per call, volume trend | Managers, execs | Daily, weekly |

Klipfolio's template lists the same core KPIs ([Klipfolio](https://www.klipfolio.com/resources/dashboard-examples/call-center/metrics-dashboard)); what vendor pages skip is how each one is calculated. That is where dashboards quietly disagree with each other.

## 1\. Real-time queue status dashboard

Scenario: a supervisor needs to see, right now, which queue is backing up.

```
SELECT
  queue,
  COUNT(*)                                             AS calls_waiting,
  MAX(EXTRACT(EPOCH FROM now() - entered_queue_at))::int AS longest_wait_s
FROM calls
WHERE answered_at IS NULL
  AND abandoned = false
  AND ended_at IS NULL
GROUP BY queue
ORDER BY longest_wait_s DESC;
```

What it shows: calls waiting and the oldest call per queue. Adapt it by adding `COUNT(DISTINCT agent_id)` from an agent-status table for available agents. This panel is useless if it refreshes hourly; point it at the phone system's live feed or refresh every 10-30 seconds.

## 2\. Service level and ASA by half hour

Service level is the percentage of offered calls answered within a threshold. The classic target is 80/20, 80% answered within 20 seconds, which Call Centre Helper calls the traditional industry standard although no standards body set it ([Call Centre Helper](https://callcentrehelper.com/metrics-that-matter-12272.htm)). ASA is the average wait of answered calls only.

```
SELECT
  date_trunc('hour', entered_queue_at)
    + INTERVAL '30 min' * (EXTRACT(MINUTE FROM entered_queue_at)::int / 30) AS slot,
  COUNT(*) AS offered,
  ROUND(100.0 * COUNT(*) FILTER (
          WHERE answered_at - entered_queue_at <= INTERVAL '20 seconds')
        / NULLIF(COUNT(*), 0), 1) AS service_level_pct,
  ROUND(AVG(EXTRACT(EPOCH FROM answered_at - entered_queue_at))
        FILTER (WHERE answered_at IS NOT NULL)) AS asa_s
FROM calls
WHERE entered_queue_at >= current_date
GROUP BY slot
ORDER BY slot;
```

What it shows: whether staffing matched demand in each 30-minute interval, the granularity most centers use (larger ones use 15 minutes). Adapt the denominator to your policy: some centers exclude calls abandoned in the first 5 seconds, Amazon Connect exposes "Contacts abandoned in X seconds" for exactly that. Pick one rule, write it on the dashboard and keep it.

## 3\. Abandonment rate dashboard

Abandonment rate is abandoned calls divided by calls offered. Amazon Connect counts a contact as abandoned when the customer disconnects while in queue, and does not count contacts queued for callback ([Amazon Connect metric definitions](https://docs.aws.amazon.com/connect/latest/adminguide/metrics-definitions.html)). 40 abandons out of 1,000 calls is a 4% rate.

```
SELECT
  queue,
  COUNT(*)                                        AS offered,
  COUNT(*) FILTER (WHERE abandoned)               AS abandoned,
  ROUND(100.0 * COUNT(*) FILTER (WHERE abandoned) / NULLIF(COUNT(*), 0), 1) AS abandon_pct,
  ROUND(AVG(EXTRACT(EPOCH FROM ended_at - entered_queue_at))
        FILTER (WHERE abandoned))                 AS avg_time_to_abandon_s
FROM calls
WHERE entered_queue_at >= now() - INTERVAL '7 days'
GROUP BY queue
ORDER BY abandon_pct DESC;
```

What it shows: which queues lose callers and how long callers tolerate waiting. Average time to abandon is the number to set your service level threshold against: if callers give up after 40 seconds, a 60-second target is meaningless.

## 4\. Average handle time breakdown by agent

Average handle time is (talk time + hold time + after-call work) divided by calls handled. Genesys Cloud uses that formula and adds dialing and contacting time for outbound calls ([Genesys Cloud](https://help.mypurecloud.com/?p=972)); Call Centre Helper uses the same three parts ([Call Centre Helper](https://www.callcentrehelper.com/how-to-measure-average-handling-time-52403.htm)).

```
SELECT
  agent_id,
  COUNT(*) AS handled,
  ROUND(AVG(EXTRACT(EPOCH FROM ended_at - answered_at) - hold_seconds)) AS avg_talk_s,
  ROUND(AVG(hold_seconds))  AS avg_hold_s,
  ROUND(AVG(acw_seconds))   AS avg_acw_s,
  ROUND(AVG(EXTRACT(EPOCH FROM ended_at - answered_at) + acw_seconds)) AS aht_s
FROM calls
WHERE answered_at IS NOT NULL
  AND ended_at IS NOT NULL
  AND answered_at >= now() - INTERVAL '30 days'
GROUP BY agent_id
HAVING COUNT(*) >= 20
ORDER BY aht_s DESC;
```

Here `ended_at - answered_at` already includes hold, so talk is that span minus `hold_seconds`, and AHT adds ACW on top. What it shows: why an agent's AHT is high. A long hold share usually means knowledge gaps; long ACW means tooling or note-taking friction. Compare agents only within the same queue, since billing and tech support calls differ in length.

![AHT adds talk, hold and after-call work, then divides by calls handled](https://cdn.sanity.io/images/upsr56wu/production/6e02be02c12e4677bb690747932ec0048f48fb9e-1344x326.png?w=1280)

FIG 02 — Genesys adds dialing and contacting time for outbound calls.

VIDEO · [Basic Call Center Metrics and Formulas](https://www.youtube.com/watch?v=gqzegli6kRQ) (Call Center Studio)

## 5\. First call resolution from repeat calls

FCR is the share of issues resolved on the first contact. Without a disposition field, the common proxy is: a call counts as resolved if the same customer does not call again within 7 days.

```
WITH ordered AS (
  SELECT
    id, customer_id, queue, answered_at,
    LEAD(entered_queue_at) OVER (PARTITION BY customer_id ORDER BY entered_queue_at) AS next_call_at
  FROM calls
  WHERE answered_at IS NOT NULL
)
SELECT
  queue,
  COUNT(*) AS handled,
  ROUND(100.0 * COUNT(*) FILTER (
          WHERE next_call_at IS NULL OR next_call_at > answered_at + INTERVAL '7 days')
        / NULLIF(COUNT(*), 0), 1) AS fcr_pct
FROM ordered
WHERE answered_at < now() - INTERVAL '7 days'  -- give every call a full 7-day window
GROUP BY queue;
```

What it shows: which queues generate callbacks. Adapt the window (48 hours for simple queries, 14 days for technical support), and exclude calls younger than the window, otherwise the most recent week always looks perfect.

## 6\. CSAT by queue and agent

CSAT is the share of survey responses scoring 4 or 5 on a 5-point scale.

```
SELECT
  queue,
  agent_id,
  COUNT(csat) AS responses,
  ROUND(100.0 * COUNT(*) FILTER (WHERE csat >= 4) / NULLIF(COUNT(csat), 0), 1) AS csat_pct
FROM calls
WHERE answered_at >= now() - INTERVAL '30 days'
GROUP BY queue, agent_id
HAVING COUNT(csat) >= 15
ORDER BY csat_pct;
```

What it shows: low scorers, sorted to the top. The `HAVING` floor stops one angry survey from ranking an agent last. Put it next to example 4: an agent with short AHT and low CSAT is rushing calls.

## 7\. Agent occupancy

Occupancy is the share of logged-in time an agent spends on calls (talk, hold and ACW) rather than waiting. In the Call Center Studio example, 5.5 hours on calls in an 8-hour shift is 69%. It needs an agent sessions table alongside `calls`:

```
WITH busy AS (
  SELECT agent_id, answered_at::date AS day,
         SUM(EXTRACT(EPOCH FROM ended_at - answered_at) + acw_seconds) AS busy_s
  FROM calls WHERE answered_at IS NOT NULL
  GROUP BY 1, 2
), logged AS (
  SELECT agent_id, login_at::date AS day,
         SUM(EXTRACT(EPOCH FROM logout_at - login_at)) AS logged_s
  FROM agent_sessions
  GROUP BY 1, 2
)
SELECT l.agent_id, l.day,
       ROUND(100.0 * COALESCE(b.busy_s, 0) / NULLIF(l.logged_s, 0), 1) AS occupancy_pct
FROM logged l LEFT JOIN busy b USING (agent_id, day)
ORDER BY l.day DESC, occupancy_pct DESC;
```

What it shows: burnout risk at the top and overstaffing at the bottom. Occupancy that stays very high for weeks tends to show up later as rising AHT and absence, so track the trend, not one day.

## 8\. Executive weekly dashboard: volume, cost per call and trend

Cost per call is total operating cost for a period divided by calls handled in it. Cost lives outside the phone system, so keep a small `weekly_costs(week, total_cost)` table.

```
WITH weekly AS (
  SELECT date_trunc('week', entered_queue_at)::date AS week,
         COUNT(*) AS offered,
         COUNT(*) FILTER (WHERE answered_at IS NOT NULL) AS handled,
         ROUND(100.0 * COUNT(*) FILTER (WHERE abandoned) / COUNT(*), 1) AS abandon_pct
  FROM calls
  GROUP BY 1
)
SELECT w.week, w.offered, w.handled, w.abandon_pct,
       ROUND(c.total_cost / NULLIF(w.handled, 0), 2) AS cost_per_call,
       w.offered - LAG(w.offered) OVER (ORDER BY w.week) AS volume_change
FROM weekly w
LEFT JOIN weekly_costs c USING (week)
ORDER BY w.week DESC
LIMIT 12;
```

What it shows: twelve weeks of volume, cost and accessibility on one table, the view leadership asks for. A rising `volume_change` with flat staffing predicts next month's service level drop.

## Which dashboard example should you use?

| Example | Use it when | Refresh |
| --- | --- | --- |
| 1\. Queue status | Supervising a live floor | 10-30 s |
| 2\. Service level and ASA | Checking staffing against demand | 15-30 min |
| 3\. Abandonment | Callers complain about waits | Hourly |
| 4\. AHT breakdown | Coaching agents | Daily |
| 5\. FCR | Repeat calls are driving volume | Weekly |
| 6\. CSAT | Tracking outcome quality | Daily |
| 7\. Occupancy | Planning schedules | Daily |
| 8\. Executive weekly | Reporting to leadership | Weekly |

## Real-time vs historical call center dashboards: what's the difference?

A real-time dashboard shows the current state (calls waiting, longest wait, agents available, today's service level) and is read by supervisors who act within minutes. A historical dashboard shows trends (AHT, FCR, CSAT, cost per call) over days and weeks and is read by managers planning staffing and training. Examples 1 and 2 are real-time; 4 to 8 are historical. Don't mix them on one screen: a supervisor glancing at a 30-day CSAT trend learns nothing they can act on in the next 10 minutes. Note also how platforms bucket time: Genesys Cloud reports handle time in the interval when after-call work completes, so a 45-minute call lands in the second 30-minute interval.

## How do you build these dashboards without writing the SQL yourself?

If your call data already lands in a database (most contact center platforms export to Postgres, MySQL or a warehouse), you can build the historical panels with any SQL dashboard tool: Metabase, Looker Studio, Power BI or Grafana. If nobody on the ops team writes SQL, AI for Database lets you ask "abandonment rate by queue for the last 7 days" in plain English, shows the SQL it ran, pins the answer to a self-refreshing [dashboard](https://www.aifordatabase.com/solutions/dashboards-and-reports/) and can alert Slack when service level drops below target. Try AI for Database free at aifordatabase.com.

For the SQL patterns used above, see our guides to [the SQL FILTER clause](https://www.aifordatabase.com/blog/filter-in-sql/), [PARTITION BY](https://www.aifordatabase.com/blog/sql-partition-by/) and the [SQL cheat sheet](https://www.aifordatabase.com/blog/sql-cheat-sheet/).

D

Written by [Dhrumil Barot](https://www.aifordatabase.com/about/#founder)

Dhrumil is an enterprise analytics expert. Dhrumil built AI for Database so anyone on a team can get answers from live company data in plain English, without waiting on a data team.

[LinkedIn](https://www.linkedin.com/in/bdhrumil)[X](https://x.com/fyidhrumil)

## Frequently Asked Questions

### How is average handle time calculated?

AHT = (total talk time + total hold time + total after-call work) / calls handled. Genesys Cloud also adds dialing and contacting time for outbound calls.

### How do you calculate service level and abandonment rate?

Service level = calls answered within the threshold / calls offered x 100, commonly against 80% in 20 seconds. Abandonment rate = calls abandoned in queue / calls offered x 100. State whether short abandons (for example under 5 seconds) are excluded.

### What is a good abandonment rate?

There is no industry standard. It depends on caller tolerance, channel alternatives and whether you offer callbacks, so benchmark against your own trend and your average time to abandon.

### What is the difference between ASA and service level?

ASA is the average wait of answered calls. Service level is the share of calls answered within a threshold. ASA excludes abandoned calls, so a good ASA can hide a high abandonment rate; show both.

### What is the difference between occupancy and utilization?

Occupancy divides time on calls by logged-in time. Utilization divides it by total paid time, so it also counts breaks, training and meetings against the agent.

### How often should a call center dashboard refresh?

Queue status every 10-30 seconds, service level every 15-30 minutes, and AHT, FCR, CSAT and cost per call daily or weekly.

Read next

*   [9 BEST QUERY BUILDERS FOR SQL: VISUAL, CODE, AI (COMPARED) →](https://www.aifordatabase.com/blog/query-builders/)
*   [WHY IS POSTGRES BETTER THAN MYSQL? POSTGRES VS MYSQL →](https://www.aifordatabase.com/blog/why-is-postgres-better-than-mysql/)
*   [SUPABASE SERVICE ROLE KEY: WHERE TO FIND IT, USE IT SAFELY →](https://www.aifordatabase.com/blog/supabase-service-role-key/)

Source: https://www.aifordatabase.com/blog/call-center-metrics-dashboard/
