SQL Query Examples#

Eight complete queries covering the most common Znuny reporting use cases. All queries have been tested against a live Znuny database. Run them in Admin → SQL Box or via a direct database connection.

Note

All queries filter archive_flag = 0 to exclude archived tickets unless the example specifically addresses archived tickets.

1. Open Tickets per Queue with Owner Name#

SELECT
    q.name                                         AS queue,
    COUNT(*)                                       AS open_tickets,
    CONCAT(u.first_name, ' ', u.last_name)         AS owner
FROM   ticket t
JOIN   ticket_state      ts ON ts.id = t.ticket_state_id
JOIN   ticket_state_type st ON st.id = ts.type_id
JOIN   queue             q  ON  q.id = t.queue_id
JOIN   users             u  ON  u.id = t.user_id
WHERE  st.name    = 'open'
AND    t.archive_flag = 0
GROUP BY q.id, q.name, u.id, u.first_name, u.last_name
ORDER BY q.name, open_tickets DESC;

2. Ticket Volume by State — Last 30 Days#

SELECT
    ts.name  AS state,
    COUNT(*) AS tickets
FROM   ticket t
JOIN   ticket_state ts ON ts.id = t.ticket_state_id
WHERE  t.create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND    t.archive_flag  = 0
GROUP BY ts.id, ts.name
ORDER BY tickets DESC;

3. Average First-Response Time per Queue (Minutes)#

Uses ticket_history to find when the first agent response was sent per ticket, then averages the gap from ticket creation.

SELECT
    q.name                                        AS queue,
    COUNT(DISTINCT t.id)                          AS tickets,
    ROUND(AVG(
        TIMESTAMPDIFF(MINUTE, t.create_time, fr.first_response_at)
    ))                                            AS avg_first_response_min
FROM ticket t
JOIN queue q ON q.id = t.queue_id
JOIN (
    SELECT   th.ticket_id,
             MIN(th.create_time) AS first_response_at
    FROM     ticket_history      th
    JOIN     ticket_history_type tht ON tht.id = th.history_type_id
    WHERE    tht.name IN ('EmailAgent', 'SendAnswer', 'PhoneCallAgent')
    GROUP BY th.ticket_id
) fr ON fr.ticket_id = t.id
WHERE t.create_time  >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND   t.archive_flag  = 0
GROUP BY q.id, q.name
ORDER BY avg_first_response_min;

4. All Inbound Emails Received Today#

SELECT
    t.tn,
    adm.a_from        AS sender,
    adm.a_subject     AS subject,
    a.create_time     AS received_at,
    q.name            AS queue
FROM   article               a
JOIN   article_data_mime     adm ON adm.article_id = a.id
JOIN   article_sender_type   ast ON ast.id = a.article_sender_type_id
JOIN   communication_channel cc  ON  cc.id = a.communication_channel_id
JOIN   ticket                t   ON  t.id  = a.ticket_id
JOIN   queue                 q   ON  q.id  = t.queue_id
WHERE  ast.name            = 'customer'
AND    cc.name             = 'Email'
AND    DATE(a.create_time) = CURDATE()
ORDER BY a.create_time DESC;

5. Tickets with a Specific Dynamic Field Value#

Replace 'Category' with the internal field name and 'Hardware' with the value you are looking for.

SELECT
    t.tn,
    t.title,
    dfv.value_text  AS category,
    t.create_time
FROM   ticket              t
JOIN   dynamic_field_value dfv ON dfv.object_id = t.id
JOIN   dynamic_field       df  ON df.id = dfv.field_id
WHERE  df.name        = 'Category'
AND    df.object_type = 'Ticket'
AND    dfv.value_text = 'Hardware'
AND    t.archive_flag = 0
ORDER BY t.create_time DESC;

6. Tickets by Priority × State (Pivot)#

SELECT
    tp.name                                        AS priority,
    SUM(CASE WHEN tst.name = 'new'    THEN 1 ELSE 0 END)  AS new_tickets,
    SUM(CASE WHEN tst.name = 'open'   THEN 1 ELSE 0 END)  AS open_tickets,
    SUM(CASE WHEN tst.name = 'closed' THEN 1 ELSE 0 END)  AS closed_tickets,
    COUNT(*)                                       AS total
FROM   ticket              t
JOIN   ticket_priority     tp  ON tp.id  = t.ticket_priority_id
JOIN   ticket_state        ts  ON ts.id  = t.ticket_state_id
JOIN   ticket_state_type   tst ON tst.id = ts.type_id
WHERE  t.create_time  >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND    t.archive_flag  = 0
GROUP BY tp.id, tp.name
ORDER BY tp.id;

7. Currently Escalated Tickets#

Tickets where any escalation clock has passed its deadline right now.

SELECT
    t.tn,
    t.title,
    q.name                                           AS queue,
    CONCAT(u.first_name, ' ', u.last_name)           AS owner,
    FROM_UNIXTIME(t.escalation_time)                 AS escalated_at,
    CASE
        WHEN t.escalation_response_time > 0
         AND t.escalation_response_time < UNIX_TIMESTAMP(NOW())
        THEN 'First Response'
        WHEN t.escalation_update_time > 0
         AND t.escalation_update_time < UNIX_TIMESTAMP(NOW())
        THEN 'Update'
        WHEN t.escalation_solution_time > 0
         AND t.escalation_solution_time < UNIX_TIMESTAMP(NOW())
        THEN 'Solution'
        ELSE 'Other'
    END                                              AS escalation_type
FROM   ticket t
JOIN   queue  q ON q.id = t.queue_id
JOIN   users  u ON u.id = t.user_id
WHERE  t.escalation_time  > 0
AND    t.escalation_time  < UNIX_TIMESTAMP(NOW())
AND    t.archive_flag     = 0
ORDER BY t.escalation_time;

8. Customer Companies Ranked by Open Ticket Count#

SELECT
    COALESCE(cc.name, t.customer_id)               AS company,
    COUNT(*)                                        AS open_tickets
FROM   ticket               t
LEFT JOIN customer_company  cc  ON cc.customer_id = t.customer_id
JOIN   ticket_state         ts  ON ts.id = t.ticket_state_id
JOIN   ticket_state_type    tst ON tst.id = ts.type_id
WHERE  tst.name       IN ('new', 'open')
AND    t.archive_flag  = 0
AND    t.customer_id  IS NOT NULL
AND    t.customer_id  != ''
GROUP BY t.customer_id, cc.name
ORDER BY open_tickets DESC
LIMIT 25;