-- ============================================================================= -- CRUD PATTERNS — Sáng kiến application system -- ============================================================================= -- ============================================================================= -- CREATE: Submit a new application with multiple authors (atomic) -- ============================================================================= BEGIN; -- Set audit context SELECT set_config('my.user_id', '42', true); -- 1. Main record INSERT INTO applications(code, title, registration_year, status, purpose, is_technical_solution, primary_unit_id, created_by) VALUES ('SK-2025-007', 'Hệ thống tự động điền hồ sơ sáng kiến', 2025, 'DRAFT', 'Tự động hoá việc điền các mẫu số 01–04', TRUE, 2, 42) RETURNING application_id \gset -- 2. Authors (defer contribution-sum check until COMMIT) SET CONSTRAINTS trg_contribution_total DEFERRED; INSERT INTO application_authors(application_id, user_id, contribution_pct, role, display_order) VALUES (:application_id, 42, 60.00, 'PRIMARY', 1), (:application_id, 13, 25.00, 'CO_AUTHOR', 2), (:application_id, 27, 15.00, 'CO_AUTHOR', 3); -- 3. Orgs that tested it INSERT INTO application_adopters(application_id, org_name, address, field) VALUES (:application_id, 'Phòng KHCN', '217 Hồng Bàng, Q.5', 'Cải cách hành chính'); COMMIT; -- ============================================================================= -- READ: Dashboard — paginated list with filters -- ============================================================================= SELECT * FROM v_application_summary WHERE registration_year = 2025 AND status = ANY(ARRAY['UNDER_REVIEW','EVALUATED']::text[]) AND title ILIKE '%động vật%' -- uses trigram index ORDER BY avg_score DESC NULLS LAST, submitted_at DESC LIMIT 20 OFFSET 0; -- Read: full application with nested data (app layer usually does this as N queries -- or one JSON aggregate — here's the aggregate version) SELECT jsonb_build_object( 'application', to_jsonb(a.*), 'authors', (SELECT jsonb_agg(jsonb_build_object( 'user_id', u.user_id, 'name', u.full_name, 'pct', aa.contribution_pct, 'role', aa.role ) ORDER BY aa.display_order) FROM application_authors aa JOIN users u USING (user_id) WHERE aa.application_id = a.application_id), 'evaluations',(SELECT jsonb_agg(to_jsonb(e.*)) FROM evaluations e WHERE e.application_id = a.application_id), 'attachments',(SELECT jsonb_agg(to_jsonb(att.*)) FROM attachments att WHERE att.application_id = a.application_id) ) AS document FROM applications a WHERE a.application_id = 1 AND a.deleted_at IS NULL; -- Full-text search (Vietnamese-friendly; combine with unaccent for better recall) SELECT application_id, code, title FROM applications WHERE to_tsvector('simple', title || ' ' || coalesce(introduction,'')) @@ plainto_tsquery('simple', 'đạo đức động vật') ORDER BY registration_year DESC LIMIT 10; -- ============================================================================= -- UPDATE: Progress an application through the workflow -- ============================================================================= -- Submit (DRAFT → SUBMITTED). Triggers populate submitted_at automatically. UPDATE applications SET status = 'SUBMITTED' WHERE application_id = 7; -- Assign to review panel UPDATE applications SET status = 'UNDER_REVIEW' WHERE application_id = 7; -- Upsert an evaluation (same evaluator re-scores) INSERT INTO evaluations (application_id, evaluator_id, novelty_score, effectiveness_score, conclusion) VALUES (7, 99, 32, 48, 'Đề nghị công nhận') ON CONFLICT (application_id, evaluator_id) DO UPDATE SET novelty_score = EXCLUDED.novelty_score, effectiveness_score = EXCLUDED.effectiveness_score, conclusion = EXCLUDED.conclusion, evaluated_at = NOW(); -- Update JSONB field: patch a single effectiveness sub-field UPDATE applications SET effectiveness = effectiveness || jsonb_build_object( 'economic', 'Tiết kiệm ~30% thời gian xét duyệt' ) WHERE application_id = 7; -- Partial update (PATCH-style) — only update provided fields. The app layer -- generates SET clauses from the non-null fields in the request body. UPDATE applications SET title = COALESCE($1, title), purpose = COALESCE($2, purpose), updated_at = NOW() WHERE application_id = $3 AND deleted_at IS NULL RETURNING *; -- ============================================================================= -- DELETE: Soft delete + restore -- ============================================================================= -- Soft delete UPDATE applications SET deleted_at = NOW() WHERE application_id = 7; -- Restore UPDATE applications SET deleted_at = NULL WHERE application_id = 7; -- Hard delete (only for drafts, cascades to authors/evaluations/etc.) DELETE FROM applications WHERE application_id = 7 AND status = 'DRAFT'; -- ============================================================================= -- ANALYTICS: Materialized-view refresh (run nightly via cron/pgAgent) -- ============================================================================= REFRESH MATERIALIZED VIEW CONCURRENTLY mv_annual_stats; -- Leaderboard: top-scoring approved innovations SELECT code, title, avg_score FROM v_application_summary WHERE status = 'APPROVED' ORDER BY avg_score DESC LIMIT 10;