Files
lcbp3/specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-03-views-indexes.sql
admin b79a3ff68b
All checks were successful
Build and Deploy / deploy (push) Successful in 1m33s
260304:1716 20260304:1700 update n8n
2026-03-04 17:16:42 +07:00

517 lines
19 KiB
SQL

-- ==========================================================
-- DMS v1.8.0 Schema Part 3/3: Views, Indexes, Partitioning
-- รัน: หลังจาก 02-schema-tables.sql เสร็จ
-- ==========================================================
SET NAMES utf8mb4;
SET time_zone = '+07:00';
-- ============================================================
-- 5. PARTITIONING PREPARATION (Advance - Optional)
-- ============================================================
-- หมายเหตุ: การทำ Partitioning บนตารางที่มีอยู่แล้ว (audit_logs, notifications)
-- มักจะต้อง Drop Primary Key เดิม แล้วสร้างใหม่โดยรวม Partition Key (created_at) เข้าไป
-- ขั้นตอนนี้ควรทำแยกต่างหากเมื่อระบบเริ่มมีข้อมูลเยอะ หรือทำใน Maintenance Window
--
-- ตัวอย่าง SQL สำหรับ Audit Logs (Reference Only):
-- ALTER TABLE audit_logs DROP PRIMARY KEY, ADD PRIMARY KEY (audit_id, created_at);
-- ALTER TABLE audit_logs PARTITION BY RANGE (YEAR(created_at)) (
-- PARTITION p2024 VALUES LESS THAN (2025),
-- PARTITION p2025 VALUES LESS THAN (2026),
-- PARTITION p_future VALUES LESS THAN MAXVALUE
-- );
-- =====================================================
-- CREATE INDEXES
-- =====================================================
-- Indexes for correspondences
CREATE INDEX idx_corr_type ON correspondences(correspondence_type_id);
CREATE INDEX idx_corr_project ON correspondences(project_id);
CREATE INDEX idx_rfa_rev_v_drawing_count ON rfa_revisions (v_ref_drawing_count);
-- Indexes for document_number_formats
CREATE INDEX idx_document_number_formats_project ON document_number_formats (project_id);
CREATE INDEX idx_document_number_formats_type ON document_number_formats (correspondence_type_id);
CREATE INDEX idx_document_number_formats_project_type ON document_number_formats (project_id, correspondence_type_id);
-- Indexes for document_number_counters
CREATE INDEX idx_document_number_counters_project ON document_number_counters (project_id);
CREATE INDEX idx_document_number_counters_org ON document_number_counters (originator_organization_id);
CREATE INDEX idx_document_number_counters_type ON document_number_counters (correspondence_type_id);
-- Indexes for tags
CREATE INDEX idx_tags_name ON tags (tag_name);
CREATE INDEX idx_tags_created_at ON tags (created_at);
-- Indexes for correspondence_tags
CREATE INDEX idx_correspondence_tags_correspondence ON correspondence_tags (correspondence_id);
CREATE INDEX idx_correspondence_tags_tag ON correspondence_tags (tag_id);
-- Indexes for audit_logs
CREATE INDEX idx_audit_logs_user ON audit_logs (user_id);
CREATE INDEX idx_audit_logs_action ON audit_logs (ACTION);
CREATE INDEX idx_audit_logs_entity ON audit_logs (entity_type, entity_id);
CREATE INDEX idx_audit_logs_created_at ON audit_logs (created_at);
CREATE INDEX idx_audit_logs_ip ON audit_logs (ip_address);
-- Indexes for notifications
CREATE INDEX idx_notifications_user ON notifications (user_id);
CREATE INDEX idx_notifications_type ON notifications (notification_type);
CREATE INDEX idx_notifications_read ON notifications (is_read);
CREATE INDEX idx_notifications_entity ON notifications (entity_type, entity_id);
CREATE INDEX idx_notifications_created_at ON notifications (created_at);
-- Indexes for search_indices
CREATE INDEX idx_search_indices_entity ON search_indices (entity_type, entity_id);
CREATE INDEX idx_search_indices_indexed_at ON search_indices (indexed_at);
-- Indexes for backup_logs
CREATE INDEX idx_backup_logs_type ON backup_logs (backup_type);
CREATE INDEX idx_backup_logs_status ON backup_logs (STATUS);
CREATE INDEX idx_backup_logs_started_at ON backup_logs (started_at);
CREATE INDEX idx_backup_logs_completed_at ON backup_logs (completed_at);
-- =====================================================
-- Additional Composite Indexes for Performance
-- =====================================================
-- Composite index for document_number_counters for faster lookups
-- Composite index for notifications for user-specific queries
CREATE INDEX idx_notifications_user_unread ON notifications (user_id, is_read, created_at);
-- Composite index for audit_logs for reporting
CREATE INDEX idx_audit_logs_reporting ON audit_logs (created_at, entity_type, ACTION);
-- Composite index for search_indices for entity-based queries
CREATE INDEX idx_search_entities ON search_indices (entity_type, entity_id, indexed_at);
-- สร้าง Index สำหรับ Cleanup Job
CREATE INDEX idx_attachments_temp_cleanup ON attachments (is_temporary, expires_at);
CREATE INDEX idx_attachments_temp_id ON attachments (temp_id);
CREATE INDEX idx_audit_request_id ON audit_logs (request_id);
-- =====================================================
-- SQL Script for LCBP3-DMS (V1.4.0) - MariaDB
-- Generated from Data Dictionary
-- =====================================================
-- =====================================================
-- 11. 📊 Views & Procedures (วิว และ โปรซีเดอร์)
-- =====================================================
-- View แสดง Revision "ปัจจุบัน" ของ correspondences ทั้งหมด (ที่ไม่ใช่ RFA)
CREATE VIEW v_current_correspondences AS
SELECT c.id AS correspondence_id,
c.correspondence_number,
c.correspondence_type_id,
ct.type_code AS correspondence_type_code,
ct.type_name AS correspondence_type_name,
c.project_id,
p.project_code,
p.project_name,
c.originator_id,
org.organization_code AS originator_code,
org.organization_name AS originator_name,
cr.id AS revision_id,
cr.revision_number,
cr.revision_label,
cr.subject,
cr.document_date,
cr.issued_date,
cr.received_date,
cr.due_date,
cr.correspondence_status_id,
cs.status_code,
cs.status_name,
cr.created_by,
u.username AS created_by_username,
cr.created_at AS revision_created_at
FROM correspondences c
INNER JOIN correspondence_types ct ON c.correspondence_type_id = ct.id
INNER JOIN projects p ON c.project_id = p.id
LEFT JOIN organizations org ON c.originator_id = org.id
INNER JOIN correspondence_revisions cr ON c.id = cr.correspondence_id
INNER JOIN correspondence_status cs ON cr.correspondence_status_id = cs.id
LEFT JOIN users u ON cr.created_by = u.user_id
WHERE cr.is_current = TRUE
AND c.correspondence_type_id NOT IN(
SELECT id
FROM correspondence_types
WHERE type_code = 'RFA'
)
AND c.deleted_at IS NULL;
-- View แสดง Revision "ปัจจุบัน" ของ rfa_revisions ทั้งหมด
CREATE VIEW v_current_rfas AS
SELECT r.id AS rfa_id,
r.rfa_type_id,
rt.type_code AS rfa_type_code,
rt.type_name_th AS rfa_type_name_th,
rt.type_name_en AS rfa_type_name_en,
c.correspondence_number,
c.discipline_id,
-- ✅ ดึงจาก Correspondences
d.discipline_code,
-- ✅ Join เพิ่มเพื่อแสดง code
c.project_id,
p.project_code,
p.project_name,
c.originator_id,
org.organization_name AS originator_name,
rr.id AS revision_id,
rr.revision_number,
rr.revision_label,
rr.subject,
rr.document_date,
rr.issued_date,
rr.received_date,
rr.approved_date,
rr.rfa_status_code_id,
rsc.status_code AS rfa_status_code,
rsc.status_name AS rfa_status_name,
rr.rfa_approve_code_id,
rac.approve_code AS rfa_approve_code,
rac.approve_name AS rfa_approve_name,
rr.created_by,
u.username AS created_by_username,
rr.created_at AS revision_created_at
FROM rfas r
INNER JOIN rfa_types rt ON r.rfa_type_id = rt.id
INNER JOIN rfa_revisions rr ON r.id = rr.rfa_id -- RFA uses shared primary key with correspondences (1:1)
INNER JOIN correspondences c ON r.id = c.id -- [FIX 1] เพิ่มการ Join ตาราง disciplines
LEFT JOIN disciplines d ON c.discipline_id = d.id
INNER JOIN projects p ON c.project_id = p.id
INNER JOIN organizations org ON c.originator_id = org.id
INNER JOIN rfa_status_codes rsc ON rr.rfa_status_code_id = rsc.id
LEFT JOIN rfa_approve_codes rac ON rr.rfa_approve_code_id = rac.id
LEFT JOIN users u ON rr.created_by = u.user_id
WHERE rr.is_current = TRUE
AND r.deleted_at IS NULL
AND c.deleted_at IS NULL;
-- View แสดงความสัมพันธ์ทั้งหมดระหว่าง Contract, Project, และ Organization
CREATE VIEW v_contract_parties_all AS
SELECT c.id AS contract_id,
c.contract_code,
c.contract_name,
p.id AS project_id,
p.project_code,
p.project_name,
o.id AS organization_id,
o.organization_code,
o.organization_name,
co.role_in_contract
FROM contracts c
INNER JOIN projects p ON c.project_id = p.id
INNER JOIN contract_organizations co ON c.id = co.contract_id
INNER JOIN organizations o ON co.organization_id = o.id
WHERE c.is_active = TRUE;
-- ============================================================
-- View: v_user_tasks (Unified Workflow Engine Edition)
-- ============================================================
-- หน้าที่: รวมรายการงานที่ยังค้างอยู่ (Status = ACTIVE) จากทุกระบบ (RFA, Circulation, Correspondence)
-- เพื่อนำไปแสดงในหน้า Dashboard "My Tasks"
-- ============================================================
CREATE OR REPLACE VIEW v_user_tasks AS
SELECT -- 1. Workflow Instance Info
wi.id AS instance_id,
wd.workflow_code,
wi.current_state,
wi.status AS workflow_status,
wi.created_at AS assigned_at,
-- 2. Entity Info (Polymorphic Identity)
wi.entity_type,
wi.entity_id,
-- 3. Normalized Document Info (ดึงข้อมูลจริงจากตารางลูกตามประเภท)
-- ใช้ CASE WHEN เพื่อรวมคอลัมน์ที่ชื่อต่างกันให้เป็นชื่อกลาง (document_number, subject)
CASE
WHEN wi.entity_type = 'rfa_revision' THEN rfa_corr.correspondence_number
WHEN wi.entity_type = 'circulation' THEN circ.circulation_no
WHEN wi.entity_type = 'correspondence_revision' THEN corr_corr.correspondence_number
ELSE 'N/A'
END AS document_number,
CASE
WHEN wi.entity_type = 'rfa_revision' THEN rfa_rev.subject
WHEN wi.entity_type = 'circulation' THEN circ.circulation_subject
WHEN wi.entity_type = 'correspondence_revision' THEN corr_rev.subject
ELSE 'Unknown Document'
END AS subject,
-- 4. Context Info (สำหรับ Filter สิทธิ์การมองเห็นที่ Backend)
-- ดึงเป็น JSON String เพื่อให้ Backend ไป Parse หรือใช้ JSON_CONTAINS
JSON_UNQUOTE(JSON_EXTRACT(wi.context, '$.ownerId')) AS owner_id,
JSON_EXTRACT(wi.context, '$.assigneeIds') AS assignee_ids_json
FROM workflow_instances wi
JOIN workflow_definitions wd ON wi.definition_id = wd.id -- 5. Joins for RFA (ซับซ้อนหน่อยเพราะ RFA ผูกกับ Correspondence อีกที)
LEFT JOIN rfa_revisions rfa_rev ON wi.entity_type = 'rfa_revision'
AND wi.entity_id = CAST(rfa_rev.id AS CHAR)
LEFT JOIN correspondences rfa_corr ON rfa_rev.id = rfa_corr.id -- 6. Joins for Circulation
LEFT JOIN circulations circ ON wi.entity_type = 'circulation'
AND wi.entity_id = CAST(circ.id AS CHAR) -- 7. Joins for Correspondence
LEFT JOIN correspondence_revisions corr_rev ON wi.entity_type = 'correspondence_revision'
AND wi.entity_id = CAST(corr_rev.id AS CHAR)
LEFT JOIN correspondences corr_corr ON corr_rev.correspondence_id = corr_corr.id -- 8. Filter เฉพาะงานที่ยัง Active อยู่
WHERE wi.status = 'ACTIVE';
-- View แสดง audit_logs พร้อมข้อมูล username และ email ของผู้กระทำ
CREATE VIEW v_audit_log_details AS
SELECT al.audit_id,
al.user_id,
u.username,
u.email,
u.first_name,
u.last_name,
al.action,
al.entity_type,
al.entity_id,
al.details_json,
al.ip_address,
al.user_agent,
al.created_at
FROM audit_logs al
LEFT JOIN users u ON al.user_id = u.user_id;
-- View รวมสิทธิ์ทั้งหมด (Global + Project) ของผู้ใช้ทุกคน
CREATE VIEW v_user_all_permissions AS -- Global Permissions
SELECT ua.user_id,
ua.role_id,
r.role_name,
rp.permission_id,
p.permission_name,
p.module,
p.scope_level,
ua.organization_id,
NULL AS project_id,
NULL AS contract_id,
'GLOBAL' AS permission_scope
FROM user_assignments ua
INNER JOIN roles r ON ua.role_id = r.role_id
INNER JOIN role_permissions rp ON ua.role_id = rp.role_id
INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Global scope
WHERE p.is_active = 1
AND ua.organization_id IS NULL
AND ua.project_id IS NULL
AND ua.contract_id IS NULL
UNION ALL
-- Organization-specific Permissions
SELECT ua.user_id,
ua.role_id,
r.role_name,
rp.permission_id,
p.permission_name,
p.module,
p.scope_level,
ua.organization_id,
NULL AS project_id,
NULL AS contract_id,
'ORGANIZATION' AS permission_scope
FROM user_assignments ua
INNER JOIN roles r ON ua.role_id = r.role_id
INNER JOIN role_permissions rp ON ua.role_id = rp.role_id
INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Organization scope
WHERE p.is_active = 1
AND ua.organization_id IS NOT NULL
AND ua.project_id IS NULL
AND ua.contract_id IS NULL
UNION ALL
-- Project-specific Permissions
SELECT ua.user_id,
ua.role_id,
r.role_name,
rp.permission_id,
p.permission_name,
p.module,
p.scope_level,
ua.organization_id,
ua.project_id,
NULL AS contract_id,
'PROJECT' AS permission_scope
FROM user_assignments ua
INNER JOIN roles r ON ua.role_id = r.role_id
INNER JOIN role_permissions rp ON ua.role_id = rp.role_id
INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Project scope
WHERE p.is_active = 1
AND ua.project_id IS NOT NULL
AND ua.contract_id IS NULL
UNION ALL
-- Contract-specific Permissions
SELECT ua.user_id,
ua.role_id,
r.role_name,
rp.permission_id,
p.permission_name,
p.module,
p.scope_level,
ua.organization_id,
ua.project_id,
ua.contract_id,
'CONTRACT' AS permission_scope
FROM user_assignments ua
INNER JOIN roles r ON ua.role_id = r.role_id
INNER JOIN role_permissions rp ON ua.role_id = rp.role_id
INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Contract scope
WHERE p.is_active = 1
AND ua.contract_id IS NOT NULL;
-- =====================================================
-- Additional Useful Views
-- =====================================================
-- View แสดงเอกสารทั้งหมดที่มีไฟล์แนบ
CREATE VIEW v_documents_with_attachments AS
SELECT 'CORRESPONDENCE' AS document_type,
c.id AS document_id,
c.correspondence_number AS document_number,
c.project_id,
p.project_code,
p.project_name,
COUNT(ca.attachment_id) AS attachment_count,
MAX(a.created_at) AS latest_attachment_date
FROM correspondences c
INNER JOIN projects p ON c.project_id = p.id
LEFT JOIN correspondence_attachments ca ON c.id = ca.correspondence_id
LEFT JOIN attachments a ON ca.attachment_id = a.id
WHERE c.deleted_at IS NULL
GROUP BY c.id,
c.correspondence_number,
c.project_id,
p.project_code,
p.project_name
UNION ALL
SELECT 'CIRCULATION' AS document_type,
circ.id AS document_id,
circ.circulation_no AS document_number,
corr.project_id,
p.project_code,
p.project_name,
COUNT(ca.attachment_id) AS attachment_count,
MAX(a.created_at) AS latest_attachment_date
FROM circulations circ
INNER JOIN correspondences corr ON circ.correspondence_id = corr.id
INNER JOIN projects p ON corr.project_id = p.id
LEFT JOIN circulation_attachments ca ON circ.id = ca.circulation_id
LEFT JOIN attachments a ON ca.attachment_id = a.id
GROUP BY circ.id,
circ.circulation_no,
corr.project_id,
p.project_code,
p.project_name
UNION ALL
SELECT 'SHOP_DRAWING' AS document_type,
sdr.id AS document_id,
sd.drawing_number AS document_number,
sd.project_id,
p.project_code,
p.project_name,
COUNT(sdra.attachment_id) AS attachment_count,
MAX(a.created_at) AS latest_attachment_date
FROM shop_drawing_revisions sdr
INNER JOIN shop_drawings sd ON sdr.shop_drawing_id = sd.id
INNER JOIN projects p ON sd.project_id = p.id
LEFT JOIN shop_drawing_revision_attachments sdra ON sdr.id = sdra.shop_drawing_revision_id
LEFT JOIN attachments a ON sdra.attachment_id = a.id
WHERE sd.deleted_at IS NULL
GROUP BY sdr.id,
sd.drawing_number,
sd.project_id,
p.project_code,
p.project_name
UNION ALL
SELECT 'CONTRACT_DRAWING' AS document_type,
cd.id AS document_id,
cd.condwg_no AS document_number,
cd.project_id,
p.project_code,
p.project_name,
COUNT(cda.attachment_id) AS attachment_count,
MAX(a.created_at) AS latest_attachment_date
FROM contract_drawings cd
INNER JOIN projects p ON cd.project_id = p.id
LEFT JOIN contract_drawing_attachments cda ON cd.id = cda.contract_drawing_id
LEFT JOIN attachments a ON cda.attachment_id = a.id
WHERE cd.deleted_at IS NULL
GROUP BY cd.id,
cd.condwg_no,
cd.project_id,
p.project_code,
p.project_name;
-- View แสดงสถิติเอกสารตามประเภทและสถานะ
CREATE VIEW v_document_statistics AS
SELECT p.id AS project_id,
p.project_code,
p.project_name,
ct.id AS correspondence_type_id,
ct.type_code,
ct.type_name,
cs.id AS status_id,
cs.status_code,
cs.status_name,
COUNT(DISTINCT c.id) AS document_count,
COUNT(DISTINCT cr.id) AS revision_count
FROM projects p
CROSS JOIN correspondence_types ct
CROSS JOIN correspondence_status cs
LEFT JOIN correspondences c ON p.id = c.project_id
AND ct.id = c.correspondence_type_id
LEFT JOIN correspondence_revisions cr ON c.id = cr.correspondence_id
AND cs.id = cr.correspondence_status_id
AND cr.is_current = TRUE
WHERE p.is_active = 1
AND ct.is_active = 1
AND cs.is_active = 1
GROUP BY p.id,
p.project_code,
p.project_name,
ct.id,
ct.type_code,
ct.type_name,
cs.id,
cs.status_code,
cs.status_name;
-- =====================================================
-- Indexes for View Performance Optimization
-- =====================================================
-- Indexes for v_current_correspondences performance
CREATE INDEX idx_correspondences_type_project ON correspondences (correspondence_type_id, project_id);
CREATE INDEX idx_corr_revisions_current_status ON correspondence_revisions (is_current, correspondence_status_id);
CREATE INDEX idx_corr_revisions_correspondence_current ON correspondence_revisions (correspondence_id, is_current);
-- Indexes for v_current_rfas performance
CREATE INDEX idx_rfa_revisions_current_status ON rfa_revisions (is_current, rfa_status_code_id);
CREATE INDEX idx_rfa_revisions_rfa_current ON rfa_revisions (rfa_id, is_current);
-- Indexes for document statistics performance
CREATE INDEX idx_correspondences_project_type ON correspondences (project_id, correspondence_type_id);
CREATE INDEX idx_corr_revisions_status_current ON correspondence_revisions (correspondence_status_id, is_current);
CREATE INDEX IDX_AUDIT_DOC_ID ON document_number_audit (document_id);
CREATE INDEX IDX_AUDIT_STATUS ON document_number_audit (STATUS);
CREATE INDEX IDX_AUDIT_OPERATION ON document_number_audit (operation);
SET FOREIGN_KEY_CHECKS = 1;