wiki:AdvancedReports

Version 2 (modified by 183164, 12 days ago) ( diff )

--

Напредни извештаи

Извештаите се извршуваат со SET search_path TO project; Двата извештаи се во прикачената датотека advanced_reports.sql​.

Во релационата алгебра се користи проширената нотација: σ (селекција), π (генерализирана проекција, со пресметани атрибути), ⋈ (спојување), ⟕ (лево надворешно спојување), ρ (преименување), γ (групирање и агрегатни функции), τ (подредување) и ← (доделување на привремена релација).

Квартален учинок во решавањето на пријавите по категорија

Општината сака да знае колку ефикасно ги решава различните видови комунални проблеми и дали се подобрува со текот на времето. Извештајот за секој квартал и за секоја категорија прикажува: вкупен број на пријави, број на решени и одбиени пријави, процент на решени, просечно време до првиот одговор (од поднесување до статус „примена“, во часови), просечно време до решавање (во денови), промена на времето до решавање во однос на претходниот квартал, и рангирање на категориите во кварталот од најбавната кон најбрзата. Со извештајот се откриваат категориите каде е потребно повеќе ресурси, и се следи долгорочниот тренд на квартално, полугодишно и годишно ниво.

Решение во SQL

WITH report_times AS (
    SELECT r.report_id, r.category_id, r.status, r.created_at,
           date_trunc('quarter', r.created_at) AS quarter,
           MIN(l.changed_at) FILTER (WHERE l.status = 'received') AS received_at,
           MIN(l.changed_at) FILTER (WHERE l.status = 'resolved') AS resolved_at
    FROM reports r
    JOIN status_logs l ON l.report_id = r.report_id
    GROUP BY r.report_id
),
per_quarter AS (
    SELECT quarter, category_id,
           COUNT(*)                                     AS total,
           COUNT(*) FILTER (WHERE status = 'resolved')  AS resolved,
           COUNT(*) FILTER (WHERE status = 'rejected')  AS rejected,
           AVG(EXTRACT(EPOCH FROM received_at - created_at) / 3600)  AS avg_response_hours,
           AVG(EXTRACT(EPOCH FROM resolved_at - created_at) / 86400) AS avg_resolution_days
    FROM report_times
    GROUP BY quarter, category_id
)
SELECT to_char(pq.quarter, 'YYYY-"Q"Q')                         AS quarter,
       c.name                                                   AS category,
       pq.total, pq.resolved, pq.rejected,
       ROUND(100.0 * pq.resolved / pq.total, 1)                 AS resolved_pct,
       ROUND(pq.avg_response_hours, 1)                          AS avg_response_hours,
       ROUND(pq.avg_resolution_days, 1)                         AS avg_resolution_days,
       ROUND(pq.avg_resolution_days - prev.avg_resolution_days, 1) AS change_vs_prev_quarter,
       RANK() OVER (PARTITION BY pq.quarter
                    ORDER BY pq.avg_resolution_days DESC NULLS LAST)      AS slowest_rank
FROM per_quarter pq
JOIN categories c ON c.category_id = pq.category_id
LEFT JOIN per_quarter prev
       ON prev.category_id = pq.category_id
      AND prev.quarter = pq.quarter - INTERVAL '3 months'
ORDER BY pq.quarter, slowest_rank;

Решение во релациона алгебра

Rec ← report_id γ MIN(changed_at)→received_at ( σ status='received' (status_logs) )

Res ← report_id γ MIN(changed_at)→resolved_at ( σ status='resolved' (status_logs) )

RT  ← π report_id, category_id, created_at,
        quarter(created_at)→quarter,
        received_at, resolved_at,
        (1 if status='resolved' else 0)→is_resolved,
        (1 if status='rejected' else 0)→is_rejected
      ( reports ⟕ Rec ⟕ Res )

PQ  ← quarter, category_id γ COUNT(report_id)→total,
                              SUM(is_resolved)→resolved,
                              SUM(is_rejected)→rejected,
                              AVG(received_at − created_at)→avg_response,
                              AVG(resolved_at − created_at)→avg_resolution
      ( RT )

Prev ← ρ Prev(prev_of, category_id, prev_avg)
       ( π quarter + 3 months, category_id, avg_resolution (PQ) )

PQK ← π quarter, category_id, COALESCE(avg_resolution, −1)→sort_key (PQ)

Rank ← a.quarter, a.category_id γ COUNT(b.category_id)→slower_count
       ( ρ a(PQK) ⟕ a.quarter = b.quarter ∧ b.sort_key > a.sort_key ρ b(PQK) )

Result ← τ quarter, slowest_rank
         ( π quarter, name, total, resolved, rejected,
             100 · resolved / total→resolved_pct,
             avg_response, avg_resolution,
             avg_resolution − prev_avg→change_vs_prev_quarter,
             1 + slower_count→slowest_rank
           ( ( PQ ⋈ Rank ⋈ categories )
             ⟕ PQ.quarter = Prev.prev_of ∧ PQ.category_id = Prev.category_id Prev ) )

Рангот (RANK) е изразен преку лево надворешно самоспојување: рангот на една категорија е 1 плус бројот на категории во истиот квартал со подолго време на решавање. Категориите без решени пријави добиваат клуч −1, што одговара на NULLS LAST.

Жаришта на повторувачки проблеми

Општината сака да ги открие локациите каде истиот вид проблем постојано се повторува, бидејќи тоа укажува на потреба од трајно решение (на пример реконструкција на улица, дополнителни контејнери или замена на инсталации) наместо постојани интервенции. Градот се дели на мрежа од ќелии со големина околу 500 метри (координатите се заокружуваат на 0,005 степени). За секоја ќелија и категорија се пресметува: број на пријави, број на различни граѓани кои пријавиле, број на различни квартали во кои има пријави, број на моментално отворени пријави, просечно време на решавање и датум на последната пријава. Се прикажуваат само ќелиите со најмалку 3 пријави во најмалку 2 различни квартали, рангирани според бројот на пријави помножен со бројот на квартали. Одбиените пријави не се сметаат. Извештајот се користи на годишно и повеќегодишно ниво за планирање на инвестициите.

Решение во SQL

WITH resolution AS (
    SELECT report_id, MIN(changed_at) FILTER (WHERE status = 'resolved') AS resolved_at
    FROM status_logs
    GROUP BY report_id
),
located AS (
    SELECT r.report_id, r.category_id, r.citizen_id, r.status, r.created_at, r.location_text,
           ROUND(r.latitude  / 0.005) * 0.005 AS cell_lat,
           ROUND(r.longitude / 0.005) * 0.005 AS cell_lon,
           res.resolved_at
    FROM reports r
    JOIN resolution res ON res.report_id = r.report_id
    WHERE r.latitude IS NOT NULL
      AND r.status <> 'rejected'
)
SELECT RANK() OVER (ORDER BY COUNT(*) * COUNT(DISTINCT date_trunc('quarter', l.created_at)) DESC) AS hotspot_rank,
       l.cell_lat, l.cell_lon,
       c.name                                                    AS category,
       MAX(l.location_text)                                      AS example_address,
       COUNT(*)                                                  AS reports,
       COUNT(DISTINCT l.citizen_id)                              AS distinct_citizens,
       COUNT(DISTINCT date_trunc('quarter', l.created_at))       AS quarters_with_reports,
       COUNT(*) FILTER (WHERE l.status NOT IN ('resolved', 'rejected')) AS open_now,
       ROUND(AVG(EXTRACT(EPOCH FROM l.resolved_at - l.created_at) / 86400), 1) AS avg_resolution_days,
       to_char(MAX(l.created_at), 'DD.MM.YYYY')                  AS last_report
FROM located l
JOIN categories c ON c.category_id = l.category_id
GROUP BY l.cell_lat, l.cell_lon, c.name
HAVING COUNT(*) >= 3
   AND COUNT(DISTINCT date_trunc('quarter', l.created_at)) >= 2
ORDER BY hotspot_rank, reports DESC;

Решение во релациона алгебра

Res ← report_id γ MIN(changed_at)→resolved_at ( σ status='resolved' (status_logs) )

L   ← π report_id, category_id, citizen_id, created_at, location_text,
        round(latitude / 0.005) · 0.005→cell_lat,
        round(longitude / 0.005) · 0.005→cell_lon,
        quarter(created_at)→quarter,
        resolved_at,
        (1 if status ∉ {'resolved','rejected'} else 0)→is_open
      ( σ latitude ≠ NULL ∧ status ≠ 'rejected' (reports) ⟕ Res )

G   ← cell_lat, cell_lon, category_id
      γ COUNT(report_id)→reports,
        COUNT-DISTINCT(citizen_id)→distinct_citizens,
        COUNT-DISTINCT(quarter)→quarters_with_reports,
        SUM(is_open)→open_now,
        AVG(resolved_at − created_at)→avg_resolution,
        MAX(location_text)→example_address,
        MAX(created_at)→last_report
      ( L )

H   ← π cell_lat, cell_lon, category_id, reports, distinct_citizens,
        quarters_with_reports, open_now, avg_resolution, example_address,
        last_report, reports · quarters_with_reports→score
      ( σ reports ≥ 3 ∧ quarters_with_reports ≥ 2 (G) )

Rank ← a.cell_lat, a.cell_lon, a.category_id γ COUNT(b.category_id)→higher_count
       ( ρ a(H) ⟕ b.score > a.score ρ b(H) )

Result ← τ hotspot_rank, reports desc
         ( π 1 + higher_count→hotspot_rank, cell_lat, cell_lon, name,
             example_address, reports, distinct_citizens,
             quarters_with_reports, open_now, avg_resolution, last_report
           ( H ⋈ Rank ⋈ categories ) )

Во SQL решението групирањето е според името на категоријата, а во релационата алгебра според category_id. Двете се еквивалентни, бидејќи името на категоријата е единствено (category_name → category_id).

Attachments (2)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.