create table users ( id bigint primary key generated always as identity, email text not null unique, full_name text not null, role text not null check (role in ('customer','agent','admin')), created_at timestamptz not null default now());create table sla_policies ( id bigint primary key generated always as identity, name text not null unique, first_response_minutes integer not null check (first_response_minutes > 0), resolution_minutes integer not null check (resolution_minutes > 0));create table tickets ( id bigint primary key generated always as identity, customer_id bigint not null references users(id), assignee_id bigint references users(id), sla_policy_id bigint references sla_policies(id), subject text not null, priority text not null check (priority in ('low','med','high','urgent')), status text not null check (status in ('new','open','pending','resolved','closed')), first_response_due_at timestamptz, resolution_due_at timestamptz, created_at timestamptz not null default now(), resolved_at timestamptz);create table ticket_comments ( id bigint primary key generated always as identity, ticket_id bigint not null references tickets(id) on delete cascade, author_id bigint not null references users(id), body text not null, is_internal boolean not null default false, created_at timestamptz not null default now());create table ticket_status_history ( id bigint primary key generated always as identity, ticket_id bigint not null references tickets(id) on delete cascade, changed_by bigint not null references users(id), old_status text, new_status text not null, changed_at timestamptz not null default now());create index idx_tickets_assignee_status on tickets(assignee_id, status);create index idx_tickets_priority_due on tickets(priority, resolution_due_at);create index idx_ticket_comments_ticket_created on ticket_comments(ticket_id, created_at);
-- 1) Open tickets breaching first response SLAselect t.id, t.subject, t.priority, t.first_response_due_at, u.email as assignee_emailfrom tickets tleft join users u on u.id = t.assignee_idwhere t.status in ('new', 'open') and t.first_response_due_at is not null and t.first_response_due_at < now()order by t.first_response_due_at;-- 2) Agent workload by statusselect u.email, t.status, count(*) as ticket_countfrom tickets tjoin users u on u.id = t.assignee_idgroup by u.email, t.statusorder by u.email, t.status;-- 3) Average resolution time for resolved ticketsselect round(avg(extract(epoch from (t.resolved_at - t.created_at)) / 3600)::numeric, 2) as avg_resolution_hoursfrom tickets twhere t.status in ('resolved', 'closed') and t.resolved_at is not null;
Loading diagram…
erDiagram users ||--o{ tickets : opens users ||--o{ tickets : assigned_to sla_policies ||--o{ tickets : governs tickets ||--o{ ticket_comments : contains users ||--o{ ticket_comments : writes tickets ||--o{ ticket_status_history : tracks users ||--o{ ticket_status_history : changes users { bigint id PK text email text role } sla_policies { bigint id PK text name int first_response_minutes int resolution_minutes } tickets { bigint id PK bigint customer_id FK bigint assignee_id FK bigint sla_policy_id FK text priority text status } ticket_comments { bigint id PK bigint ticket_id FK bigint author_id FK bool is_internal } ticket_status_history { bigint id PK bigint ticket_id FK bigint changed_by FK text old_status text new_status }
Helpdesk model for support operations, SLA tracking, and ticket lifecycle auditing.