-- Migration: Optimize weekly_report_mv by denormalizing created_date into nodes and excluding current week from MV -- Date: 2026-01-26 -- -- This migration: -- 1. Adds created_date column to nodes table (denormalization to avoid JOIN with entries) -- 2. Populates existing data from entries -- 3. Creates indexes for optimized queries -- 4. Updates MV to exclude current week and use nodes.created_date instead of entries.created_date -- ============================================ -- Step 1: Add created_date column to nodes -- ============================================ ALTER TABLE nodes ADD COLUMN created_date TIMESTAMP WITH TIME ZONE; -- ============================================ -- Step 2: Populate existing data from entries -- ============================================ UPDATE nodes n SET created_date = e.created_date FROM entries e WHERE n.entry_id = e.id; -- ============================================ -- Step 3: Set NOT NULL constraint -- ============================================ ALTER TABLE nodes ALTER COLUMN created_date SET NOT NULL; -- ============================================ -- Step 4: Create indexes for optimized queries -- ============================================ -- Index for filtering by date and user (for current week queries) CREATE INDEX IF NOT EXISTS idx_nodes_created_date_user ON nodes(created_date, user_id); -- Index for queries with grouping by project (for current week queries) CREATE INDEX IF NOT EXISTS idx_nodes_project_user_created_date ON nodes(project_id, user_id, created_date); COMMENT ON INDEX idx_nodes_created_date_user IS 'Index for filtering nodes by created_date and user_id - optimized for current week queries'; COMMENT ON INDEX idx_nodes_project_user_created_date IS 'Index for grouping nodes by project, user and created_date - optimized for current week aggregation queries'; -- ============================================ -- Step 5: Recreate MV to exclude current week and use nodes.created_date -- ============================================ DROP MATERIALIZED VIEW IF EXISTS weekly_report_mv; CREATE MATERIALIZED VIEW weekly_report_mv AS SELECT p.id AS project_id, agg.report_year, agg.report_week, COALESCE(agg.total_score, 0.0000) AS total_score, CASE WHEN wg.max_score IS NULL THEN COALESCE(agg.total_score, 0.0000) ELSE LEAST(COALESCE(agg.total_score, 0.0000), wg.max_score) END AS normalized_total_score FROM projects p LEFT JOIN ( SELECT n.project_id, EXTRACT(ISOYEAR FROM n.created_date)::INTEGER AS report_year, EXTRACT(WEEK FROM n.created_date)::INTEGER AS report_week, SUM(n.score) AS total_score FROM nodes n WHERE -- Exclude current week: only include data from previous weeks (EXTRACT(ISOYEAR FROM n.created_date)::INTEGER < EXTRACT(ISOYEAR FROM CURRENT_DATE)::INTEGER) OR (EXTRACT(ISOYEAR FROM n.created_date)::INTEGER = EXTRACT(ISOYEAR FROM CURRENT_DATE)::INTEGER AND EXTRACT(WEEK FROM n.created_date)::INTEGER < EXTRACT(WEEK FROM CURRENT_DATE)::INTEGER) GROUP BY 1, 2, 3 ) agg ON p.id = agg.project_id LEFT JOIN weekly_goals wg ON wg.project_id = p.id AND wg.goal_year = agg.report_year AND wg.goal_week = agg.report_week WHERE p.deleted = FALSE ORDER BY p.id, agg.report_year, agg.report_week WITH DATA; -- Recreate index on MV CREATE INDEX idx_weekly_report_mv_project_year_week ON weekly_report_mv(project_id, report_year, report_week); COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project using ISOYEAR for correct week calculations at year boundaries. Includes all projects via LEFT JOIN. Adds normalized_total_score using weekly_goals.max_score snapshot. Contains only historical data (excludes current week). Uses nodes.created_date (denormalized) instead of entries.created_date.';