Technical Guides

VICIdial Custom Report Building with MySQL Views and Queries

ViciStack Team · · 14 min read
vicidial mysql reporting custom reports grafana metabase database

VICIdial’s built-in reporting covers the basics, but if you’re running a serious call center with 25+ agents, you’ve almost certainly hit the wall. The admin reports are slow, the export options are limited, and when your client asks for a custom metric that doesn’t exist in the GUI, you’re stuck building it yourself.

The good news: VICIdial stores everything in MySQL. Every call, every disposition, every agent state change, every pause code — it’s all there in well-structured tables. Once you understand the schema, you can build reports that are faster, more flexible, and far more powerful than anything the web interface offers.

This guide walks through the VICIdial database schema, the key tables you need to know, and practical MySQL queries and views that will transform your reporting capabilities.

VICIdial Database Schema Overview

VICIdial uses a single MySQL database called asterisk (the default name, though some installations rename it). The schema contains over 200 tables, but for reporting purposes, you only need to understand about 15 of them.

Connect to the database:

mysql -u cron -p asterisk

The default cron user typically has read access to all tables. If you’re building a dedicated reporting user (recommended), grant SELECT-only permissions:

CREATE USER 'reports'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT SELECT ON asterisk.* TO 'reports'@'localhost';
FLUSH PRIVILEGES;

The Core Reporting Tables

Here’s a map of the tables that matter most for custom reports:

TableWhat It StoresKey Use
vicidial_logOutbound call recordsCall outcomes, talk time, wait time
vicidial_closer_logInbound/transfer call recordsQueue times, handle times
vicidial_agent_logAgent state changesLogin time, pause time, productivity
vicidial_listLead/contact recordsLead data, status, source
vicidial_usersAgent profilesNames, groups, permissions
vicidial_campaignsCampaign configurationDial method, settings
vicidial_inbound_groupsInbound group configQueue settings, routing
vicidial_carrier_logSIP trunk recordsCarrier-level call data
vicidial_did_logDID routing logInbound call routing
vicidial_lead_recycleRecycled leadsRetry scheduling
vicidial_campaign_statsAggregated campaign statsPre-calculated metrics
recording_logCall recordingsRecording file paths, durations
vicidial_user_logUser login/logout eventsSession tracking
vicidial_pause_codesPause reason definitionsBreak types

Deep Dive: vicidial_log

The vicidial_log table is the backbone of outbound reporting. Every outbound call attempt gets a row here.

DESCRIBE vicidial_log;

Key columns:

ColumnTypeDescription
uniqueidVARCHAR(20)Asterisk unique call ID
lead_idINT(9)Links to vicidial_list
list_idBIGINT(14)List the lead belongs to
campaign_idVARCHAR(8)Campaign that placed the call
call_dateDATETIMEWhen the call was placed
start_epochINT(10)Unix timestamp of call start
end_epochINT(10)Unix timestamp of call end
length_in_secINT(10)Total call duration
statusVARCHAR(6)Disposition code
phone_numberVARCHAR(18)Number dialed
userVARCHAR(20)Agent who handled the call
term_reasonVARCHAR(6)How the call ended
alt_dialVARCHAR(6)Which phone number was dialed (MAIN, ALT, ADDR3)
called_countSMALLINT(5)How many times this lead has been called

A basic query to see today’s call volume by campaign:

SELECT
    campaign_id,
    COUNT(*) AS total_calls,
    SUM(CASE WHEN status IN ('SALE','XFER','CALLBK') THEN 1 ELSE 0 END) AS connects,
    ROUND(SUM(CASE WHEN status IN ('SALE','XFER','CALLBK') THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS connect_pct,
    ROUND(AVG(length_in_sec), 1) AS avg_duration
FROM vicidial_log
WHERE call_date >= CURDATE()
GROUP BY campaign_id
ORDER BY total_calls DESC;

Deep Dive: vicidial_closer_log

The vicidial_closer_log mirrors vicidial_log but for inbound and transferred calls. If you’re running blended campaigns or have inbound queues, this table is essential.

Key additional columns beyond what vicidial_log has:

ColumnTypeDescription
campaign_idVARCHAR(20)The inbound group (not the outbound campaign)
queue_secondsINT(10)Time the caller waited in queue
xfercallidINT(10)Links to the original outbound call
queue_positionINT(4)Caller’s position in queue when answered

Query to check queue performance:

SELECT
    campaign_id AS inbound_group,
    COUNT(*) AS total_calls,
    ROUND(AVG(queue_seconds), 1) AS avg_queue_sec,
    MAX(queue_seconds) AS max_queue_sec,
    SUM(CASE WHEN queue_seconds <= 30 THEN 1 ELSE 0 END) AS answered_within_30s,
    ROUND(SUM(CASE WHEN queue_seconds <= 30 THEN 1 ELSE 0 END) / COUNT(*) * 100, 1) AS service_level_pct
FROM vicidial_closer_log
WHERE call_date >= CURDATE()
  AND status NOT IN ('DROP','TIMEOT','AFTHRS')
GROUP BY campaign_id;

Deep Dive: vicidial_agent_log

The vicidial_agent_log is where agent productivity data lives. Every time an agent changes state (available, in-call, paused, wrapping up), a row is written.

Key columns:

ColumnTypeDescription
agent_log_idINT(9)Primary key
userVARCHAR(20)Agent login ID
event_timeDATETIMEWhen the state change occurred
campaign_idVARCHAR(8)Campaign the agent was logged into
pause_epochINT(10)When the agent entered pause
pause_secSMALLINT(5)Seconds spent paused
wait_epochINT(10)When the agent became available
wait_secSMALLINT(5)Seconds waiting for a call
talk_epochINT(10)When the agent started talking
talk_secSMALLINT(5)Seconds in conversation
dispo_epochINT(10)When the agent entered disposition
dispo_secSMALLINT(5)Seconds in wrap-up
statusVARCHAR(6)Disposition assigned
sub_statusVARCHAR(6)Pause code (if paused)

Creating MySQL Views for Common Reports

MySQL views let you save complex queries as virtual tables. They’re perfect for VICIdial reporting because they give you a clean interface over the messy underlying data, and you can query them from any tool that connects to MySQL.

View 1: Agent Daily Summary

This view aggregates agent performance by day:

CREATE OR REPLACE VIEW v_agent_daily_summary AS
SELECT
    DATE(event_time) AS report_date,
    a.user AS agent_id,
    u.full_name AS agent_name,
    a.campaign_id,
    COUNT(*) AS total_calls,
    SUM(a.talk_sec) AS total_talk_sec,
    SUM(a.pause_sec) AS total_pause_sec,
    SUM(a.wait_sec) AS total_wait_sec,
    SUM(a.dispo_sec) AS total_dispo_sec,
    ROUND(SUM(a.talk_sec) / NULLIF(SUM(a.talk_sec + a.pause_sec + a.wait_sec + a.dispo_sec), 0) * 100, 1) AS talk_pct,
    SUM(CASE WHEN a.status IN ('SALE','XFER') THEN 1 ELSE 0 END) AS conversions,
    ROUND(SUM(CASE WHEN a.status IN ('SALE','XFER') THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) * 100, 2) AS conversion_rate
FROM vicidial_agent_log a
LEFT JOIN vicidial_users u ON a.user = u.user
WHERE a.talk_sec > 0
GROUP BY DATE(event_time), a.user, u.full_name, a.campaign_id;

Query it simply:

SELECT * FROM v_agent_daily_summary
WHERE report_date = CURDATE()
ORDER BY conversion_rate DESC;

View 2: Campaign Hourly Metrics

Track campaign performance hour by hour to identify peak times:

CREATE OR REPLACE VIEW v_campaign_hourly AS
SELECT
    DATE(call_date) AS report_date,
    HOUR(call_date) AS hour_of_day,
    campaign_id,
    COUNT(*) AS attempts,
    SUM(CASE WHEN user != 'VDAD' THEN 1 ELSE 0 END) AS agent_handled,
    SUM(CASE WHEN status = 'DROP' THEN 1 ELSE 0 END) AS drops,
    SUM(CASE WHEN status IN ('A','AA','AM','AL','N','NI','NP','DC','DNC','DNCL','DNCC') THEN 1 ELSE 0 END) AS non_contacts,
    SUM(CASE WHEN status NOT IN ('A','AA','AM','AL','N','NI','NP','DC','DNC','DNCL','DNCC','DROP','B','NA','ADC') THEN 1 ELSE 0 END) AS contacts,
    ROUND(AVG(CASE WHEN length_in_sec > 0 THEN length_in_sec ELSE NULL END), 1) AS avg_talk_sec,
    ROUND(SUM(CASE WHEN status = 'DROP' THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) * 100, 2) AS drop_rate
FROM vicidial_log
GROUP BY DATE(call_date), HOUR(call_date), campaign_id;

View 3: List Penetration Report

Know exactly how deep into each list you’ve dialed:

CREATE OR REPLACE VIEW v_list_penetration AS
SELECT
    l.list_id,
    vl.list_name,
    vl.campaign_id,
    COUNT(*) AS total_leads,
    SUM(CASE WHEN l.called_count > 0 THEN 1 ELSE 0 END) AS dialed_leads,
    ROUND(SUM(CASE WHEN l.called_count > 0 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) * 100, 1) AS penetration_pct,
    SUM(CASE WHEN l.status IN ('NEW','QUEUE') THEN 1 ELSE 0 END) AS dialable_remaining,
    AVG(l.called_count) AS avg_call_attempts,
    SUM(CASE WHEN l.status IN ('SALE','XFER') THEN 1 ELSE 0 END) AS conversions
FROM vicidial_list l
JOIN vicidial_lists vl ON l.list_id = vl.list_id
GROUP BY l.list_id, vl.list_name, vl.campaign_id;

View 4: AMD Performance Tracker

For operations relying on Answering Machine Detection, this view tracks AMD accuracy:

CREATE OR REPLACE VIEW v_amd_performance AS
SELECT
    DATE(call_date) AS report_date,
    campaign_id,
    COUNT(*) AS total_calls,
    SUM(CASE WHEN status = 'AA' THEN 1 ELSE 0 END) AS amd_detected,
    SUM(CASE WHEN status = 'A' THEN 1 ELSE 0 END) AS amd_human_auto,
    SUM(CASE WHEN status = 'AL' THEN 1 ELSE 0 END) AS amd_left_message,
    SUM(CASE WHEN status = 'AM' THEN 1 ELSE 0 END) AS amd_machine,
    ROUND(SUM(CASE WHEN status IN ('AA','AM','AL') THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) * 100, 1) AS amd_rate,
    SUM(CASE WHEN length_in_sec BETWEEN 1 AND 3 AND status NOT IN ('AA','AM','AL','B','NA','DC') THEN 1 ELSE 0 END) AS possible_false_positives
FROM vicidial_log
GROUP BY DATE(call_date), campaign_id;

The possible_false_positives column catches calls that were very short (1-3 seconds) but weren’t dispositioned as machines — a signal that AMD may be dropping live humans.

Agent Performance Report Queries

Detailed Agent Scorecard

This query builds a comprehensive agent scorecard suitable for team leads:

SELECT
    u.full_name AS agent,
    a.user AS agent_id,
    SEC_TO_TIME(SUM(a.talk_sec)) AS total_talk_time,
    SEC_TO_TIME(SUM(a.pause_sec)) AS total_pause_time,
    SEC_TO_TIME(SUM(a.wait_sec)) AS total_wait_time,
    SEC_TO_TIME(SUM(a.dispo_sec)) AS total_dispo_time,
    SEC_TO_TIME(SUM(a.talk_sec + a.pause_sec + a.wait_sec + a.dispo_sec)) AS total_logged_time,
    COUNT(CASE WHEN a.talk_sec > 0 THEN 1 END) AS calls_handled,
    ROUND(AVG(CASE WHEN a.talk_sec > 0 THEN a.talk_sec END), 0) AS avg_handle_sec,
    SUM(CASE WHEN a.status IN ('SALE','XFER') THEN 1 ELSE 0 END) AS conversions,
    ROUND(
        SUM(a.talk_sec) / NULLIF(SUM(a.talk_sec + a.pause_sec + a.wait_sec + a.dispo_sec), 0) * 100,
    1) AS utilization_pct,
    -- Calls per hour calculation
    ROUND(
        COUNT(CASE WHEN a.talk_sec > 0 THEN 1 END) /
        NULLIF(SUM(a.talk_sec + a.pause_sec + a.wait_sec + a.dispo_sec) / 3600, 0),
    1) AS calls_per_hour
FROM vicidial_agent_log a
JOIN vicidial_users u ON a.user = u.user
WHERE DATE(a.event_time) = CURDATE()
GROUP BY u.full_name, a.user
ORDER BY conversions DESC, utilization_pct DESC;

Pause Code Breakdown

Identify which pause codes agents are using and how much time is going to each:

SELECT
    a.user AS agent_id,
    u.full_name AS agent,
    a.sub_status AS pause_code,
    COALESCE(p.pause_code_name, 'UNKNOWN') AS pause_name,
    COUNT(*) AS pause_count,
    SEC_TO_TIME(SUM(a.pause_sec)) AS total_pause_time,
    ROUND(AVG(a.pause_sec), 0) AS avg_pause_sec
FROM vicidial_agent_log a
JOIN vicidial_users u ON a.user = u.user
LEFT JOIN vicidial_pause_codes p
    ON a.sub_status = p.pause_code
    AND a.campaign_id = p.campaign_id
WHERE DATE(a.event_time) = CURDATE()
  AND a.pause_sec > 0
GROUP BY a.user, u.full_name, a.sub_status, p.pause_code_name
ORDER BY SUM(a.pause_sec) DESC;

Agent Login/Logout Timeline

Track when agents log in and out to verify schedule adherence:

SELECT
    user,
    event,
    event_date,
    event_epoch,
    campaign_id
FROM vicidial_user_log
WHERE event IN ('LOGIN','LOGOUT')
  AND DATE(event_date) = CURDATE()
ORDER BY user, event_epoch;

Campaign Performance Dashboards

Real-Time Campaign Dashboard Query

This query gives you a snapshot of where each campaign stands right now:

SELECT
    vl.campaign_id,
    -- Today's metrics from vicidial_log
    COUNT(*) AS calls_today,
    SUM(CASE WHEN vl.status NOT IN ('A','AA','AM','AL','N','NI','NP','DC','DNC','B','NA','ADC','DROP','DNCL','DNCC') THEN 1 ELSE 0 END) AS contacts_today,
    ROUND(
        SUM(CASE WHEN vl.status NOT IN ('A','AA','AM','AL','N','NI','NP','DC','DNC','B','NA','ADC','DROP','DNCL','DNCC') THEN 1 ELSE 0 END)
        / NULLIF(COUNT(*), 0) * 100,
    2) AS contact_rate,
    SUM(CASE WHEN vl.status = 'DROP' THEN 1 ELSE 0 END) AS drops,
    ROUND(SUM(CASE WHEN vl.status = 'DROP' THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) * 100, 2) AS drop_rate,
    SUM(CASE WHEN vl.status IN ('SALE','XFER') THEN 1 ELSE 0 END) AS sales,
    ROUND(AVG(vl.length_in_sec), 0) AS avg_call_sec,
    SEC_TO_TIME(SUM(CASE WHEN vl.length_in_sec > 30 THEN vl.length_in_sec ELSE 0 END)) AS total_talk_time
FROM vicidial_log vl
WHERE vl.call_date >= CURDATE()
GROUP BY vl.campaign_id
ORDER BY calls_today DESC;

Disposition Breakdown by Campaign

See exactly how calls are being dispositioned:

SELECT
    campaign_id,
    status,
    sd.status_name,
    COUNT(*) AS call_count,
    ROUND(COUNT(*) / SUM(COUNT(*)) OVER (PARTITION BY campaign_id) * 100, 2) AS pct_of_campaign
FROM vicidial_log vl
LEFT JOIN vicidial_statuses sd ON vl.status = sd.status
WHERE call_date >= CURDATE()
GROUP BY campaign_id, status, sd.status_name
ORDER BY campaign_id, call_count DESC;

Carrier Performance Report

Track which SIP carriers are performing best:

SELECT
    server_ip,
    dialstatus,
    COUNT(*) AS calls,
    ROUND(AVG(answered_time), 1) AS avg_answer_sec,
    ROUND(COUNT(*) / SUM(COUNT(*)) OVER () * 100, 2) AS pct_of_total
FROM vicidial_carrier_log
WHERE call_date >= CURDATE()
GROUP BY server_ip, dialstatus
ORDER BY server_ip, calls DESC;

Scheduled Report Generation with Cron

Manual queries are useful for ad-hoc analysis, but production reporting should be automated. Here’s how to set up scheduled report generation.

Step 1: Create a Report Script

Save this as /opt/vicidial-reports/daily_agent_report.sh:

#!/bin/bash
# Daily Agent Performance Report
# Generates CSV and emails to management

REPORT_DATE=$(date -d "yesterday" +%Y-%m-%d)
REPORT_DIR="/opt/vicidial-reports/output"
REPORT_FILE="${REPORT_DIR}/agent_report_${REPORT_DATE}.csv"

mkdir -p "$REPORT_DIR"

mysql -u reports -p'your_password' -h localhost asterisk \
  --batch --raw -e "
SELECT
    u.full_name AS 'Agent Name',
    a.user AS 'Agent ID',
    a.campaign_id AS 'Campaign',
    COUNT(CASE WHEN a.talk_sec > 0 THEN 1 END) AS 'Calls Handled',
    SEC_TO_TIME(SUM(a.talk_sec)) AS 'Talk Time',
    SEC_TO_TIME(SUM(a.pause_sec)) AS 'Pause Time',
    ROUND(SUM(a.talk_sec) / NULLIF(SUM(a.talk_sec + a.pause_sec + a.wait_sec + a.dispo_sec), 0) * 100, 1) AS 'Utilization %',
    SUM(CASE WHEN a.status IN ('SALE','XFER') THEN 1 ELSE 0 END) AS 'Conversions',
    ROUND(SUM(CASE WHEN a.status IN ('SALE','XFER') THEN 1 ELSE 0 END) / NULLIF(COUNT(CASE WHEN a.talk_sec > 0 THEN 1 END), 0) * 100, 2) AS 'Conv Rate %'
FROM vicidial_agent_log a
JOIN vicidial_users u ON a.user = u.user
WHERE DATE(a.event_time) = '${REPORT_DATE}'
GROUP BY u.full_name, a.user, a.campaign_id
ORDER BY SUM(CASE WHEN a.status IN ('SALE','XFER') THEN 1 ELSE 0 END) DESC
" > "$REPORT_FILE"

# Email the report
if [ -s "$REPORT_FILE" ]; then
    echo "Agent performance report for ${REPORT_DATE}" | \
    mail -s "Daily Agent Report - ${REPORT_DATE}" \
         -a "$REPORT_FILE" \
         manager@yourcallcenter.com
    echo "$(date): Report sent for ${REPORT_DATE}" >> /var/log/vicidial-reports.log
else
    echo "$(date): No data for ${REPORT_DATE}" >> /var/log/vicidial-reports.log
fi

Step 2: Schedule with Cron

chmod +x /opt/vicidial-reports/daily_agent_report.sh

# Edit crontab
crontab -e

Add these entries:

# Daily agent report at 6:00 AM
0 6 * * * /opt/vicidial-reports/daily_agent_report.sh

# Hourly campaign snapshot during business hours (9 AM - 9 PM)
0 9-21 * * 1-6 /opt/vicidial-reports/hourly_campaign_snapshot.sh

# Weekly summary every Monday at 7:00 AM
0 7 * * 1 /opt/vicidial-reports/weekly_summary.sh

Step 3: Create an Hourly Snapshot Script

For real-time operations, an hourly snapshot keeps leadership informed:

#!/bin/bash
# Hourly Campaign Snapshot
HOUR=$(date +%H)
TODAY=$(date +%Y-%m-%d)

mysql -u reports -p'your_password' asterisk --batch -e "
SELECT
    campaign_id AS Campaign,
    COUNT(*) AS 'Calls This Hour',
    SUM(CASE WHEN status NOT IN ('A','AA','AM','AL','N','NI','NP','DC','DNC','B','NA','ADC','DROP') THEN 1 ELSE 0 END) AS Contacts,
    ROUND(SUM(CASE WHEN status = 'DROP' THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) * 100, 1) AS 'Drop %'
FROM vicidial_log
WHERE call_date >= '${TODAY} ${HOUR}:00:00'
  AND call_date < '${TODAY} ${HOUR}:59:59'
GROUP BY campaign_id;
" | mail -s "Hourly Snapshot - ${TODAY} ${HOUR}:00" ops-team@yourcallcenter.com

Connecting to BI Tools

Grafana Setup

Grafana turns your VICIdial data into live dashboards with auto-refresh. Here’s how to set it up:

1. Install the MySQL data source plugin (usually included by default):

# Install Grafana if not already present
sudo yum install -y grafana
sudo systemctl enable --now grafana-server

2. Add MySQL as a data source in Grafana (Settings > Data Sources > Add > MySQL):

  • Host: localhost:3306
  • Database: asterisk
  • User: reports (the read-only user you created)
  • TLS/SSL: Disable for localhost

3. Create a dashboard panel with a query like:

SELECT
    $__timeGroupAlias(call_date, '1h') AS time,
    campaign_id AS metric,
    COUNT(*) AS value
FROM vicidial_log
WHERE $__timeFilter(call_date)
GROUP BY time, campaign_id
ORDER BY time;

Grafana’s $__timeFilter macro automatically injects the time range from the dashboard selector.

4. A real-time agent count panel:

SELECT
    NOW() AS time,
    campaign_id AS metric,
    COUNT(DISTINCT user) AS value
FROM vicidial_live_agents
WHERE status IN ('INCALL','QUEUE','READY')
GROUP BY campaign_id;

Set this panel to refresh every 10 seconds for a live wallboard.

Metabase Setup

Metabase is easier to set up than Grafana and better for non-technical users who want to explore data themselves.

# Quick Docker install
docker run -d -p 3000:3000 \
  --name metabase \
  -e "MB_DB_TYPE=mysql" \
  -e "MB_DB_DBNAME=metabase" \
  -e "MB_DB_PORT=3306" \
  -e "MB_DB_USER=metabase" \
  -e "MB_DB_PASS=your_password" \
  -e "MB_DB_HOST=localhost" \
  metabase/metabase

After setup, add the asterisk database as a data source. Metabase will auto-detect tables and let users build questions using a point-and-click interface.

Pro tip: Create the MySQL views described earlier in this article. Metabase works much better with views because they hide the complexity of joins and give clean, human-readable column names.

Security Considerations

When connecting external BI tools to your VICIdial database:

  1. Always use a read-only MySQL user. Never give BI tools write access.
  2. Restrict to localhost if the BI tool runs on the same server. If remote, use SSH tunneling:
ssh -L 3306:localhost:3306 user@vicidial-server
  1. Create views that exclude sensitive data. Don’t expose SSNs, full credit card numbers, or other PII through BI tools:
CREATE OR REPLACE VIEW v_leads_safe AS
SELECT
    lead_id, list_id, status, called_count,
    entry_date, modify_date, source_id,
    state, vendor_lead_code,
    -- Mask sensitive fields
    CONCAT(LEFT(phone_number, 3), '***', RIGHT(phone_number, 2)) AS phone_masked,
    CONCAT(LEFT(first_name, 1), '***') AS first_name_masked
FROM vicidial_list;
  1. Monitor query performance. Heavy reporting queries can slow down live dialing. Run expensive queries during off-hours or use a read replica.

Performance Optimization Tips

VICIdial databases can grow massive. A center running 50 agents for a year can easily have 10+ million rows in vicidial_log. Here’s how to keep reporting fast:

Add Indexes for Report Queries

-- Speed up date-range queries on vicidial_log
ALTER TABLE vicidial_log ADD INDEX idx_call_date_campaign (call_date, campaign_id);

-- Speed up agent log queries
ALTER TABLE vicidial_agent_log ADD INDEX idx_event_time_user (event_time, user);

-- Speed up list penetration queries
ALTER TABLE vicidial_list ADD INDEX idx_list_status_count (list_id, status, called_count);

Warning: Only add indexes during off-hours. ALTER TABLE locks the table on older MySQL versions, which will freeze your dialer.

Use Summary Tables for Historical Data

For reports spanning months, query summary tables instead of raw data:

CREATE TABLE report_daily_summary (
    report_date DATE NOT NULL,
    campaign_id VARCHAR(8) NOT NULL,
    total_calls INT DEFAULT 0,
    total_contacts INT DEFAULT 0,
    total_sales INT DEFAULT 0,
    total_drops INT DEFAULT 0,
    total_talk_sec INT DEFAULT 0,
    avg_handle_sec DECIMAL(8,1) DEFAULT 0,
    PRIMARY KEY (report_date, campaign_id)
);

-- Populate nightly via cron
INSERT INTO report_daily_summary
SELECT
    DATE(call_date), campaign_id,
    COUNT(*),
    SUM(CASE WHEN status NOT IN ('A','AA','AM','AL','N','NI','NP','DC','DNC','B','NA','ADC','DROP') THEN 1 ELSE 0 END),
    SUM(CASE WHEN status IN ('SALE','XFER') THEN 1 ELSE 0 END),
    SUM(CASE WHEN status = 'DROP' THEN 1 ELSE 0 END),
    SUM(length_in_sec),
    ROUND(AVG(length_in_sec), 1)
FROM vicidial_log
WHERE DATE(call_date) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)
GROUP BY DATE(call_date), campaign_id
ON DUPLICATE KEY UPDATE
    total_calls = VALUES(total_calls),
    total_contacts = VALUES(total_contacts),
    total_sales = VALUES(total_sales);

How ViciStack Helps

Building custom reports is powerful, but it takes time and MySQL expertise that most call center teams don’t have in-house. At ViciStack, we’ve built and optimized VICIdial reporting for over 100 call centers. Our managed service includes:

  • Pre-built reporting dashboards covering agent performance, campaign metrics, AMD accuracy, and carrier health — ready from day one
  • Automated daily and weekly reports delivered to your inbox with the metrics that matter for your specific operation
  • Database optimization including proper indexing, query tuning, and summary table management to keep reports fast even at scale
  • Custom report development for any metric your clients or management need, built and maintained by VICIdial database experts
  • Real-time monitoring that catches problems (drop rate spikes, AMD drift, carrier failures) before they cost you money

All of this is included in our flat $150/agent/month pricing. No per-minute charges, no surprise fees.

Get a free analysis of your VICIdial reporting and performance. We’ll review your current setup and show you exactly where you’re leaving data (and money) on the table. Request your free analysis at vicistack.com/proof/ — we respond within 5 minutes during business hours.

Get Your Free 247-Point VICIdial Audit
Real-time support within 5 minutes. 24/7. Call or text 343-204-2353
← Previous VICIdial CNAM Lookup Integration for Inbound Routing Next → VICIdial Database Partitioning for High-Volume Call Centers

Related Articles

VICIdial Optimization

VICIdial Reporting & Real-Time Monitoring Guide

· 18 min read
Technical Guides

VICIdial Asterisk CDR Analysis for Connect Rate Optimization

· 11 min read
Technical Guides

VICIdial Real-Time Agent Dashboard Customization Guide

· 11 min read

Related Status Codes

A — Answering Machine AA — Auto Answering Machine ADC — Auto Disconnected AL — Auto Alt Dial AM — Answering Machine — Message Left

Related Glossary Terms

Answering Machine Detection (AMD) Asterisk Blended Campaign Calls Per Hour Campaign Carrier Caller ID (CID) Closer

Comprehensive Guides

Complete VICIdial Implementation Guide Read guide → VICIdial Performance Optimization Guide Read guide →

Want These Results for Your Center?

Get a free performance audit from our VICIdial optimization experts. We'll identify the highest-impact changes for your specific setup.

System Down? Get Help in 5 Minutes
Real-time support within 5 minutes. 24/7. Call or text 343-204-2353