-- pf.dim_period — create and populate -- Adjust fiscal_start_month: 1=Jan (calendar year), 4=Apr, 6=Jun, 7=Jul, 10=Oct, etc. -- Safe to re-run: ON CONFLICT DO NOTHING, so existing rows are never overwritten. CREATE TABLE IF NOT EXISTS pf.dim_period ( sdat date NOT NULL PRIMARY KEY, edat date NOT NULL, drange daterange NOT NULL, ndays integer NOT NULL, -- calendar cal_year integer NOT NULL, cal_quarter integer NOT NULL, cal_month integer NOT NULL, cal_month_abbr text NOT NULL, -- 01 - Jan cal_month_name text NOT NULL, -- 01 - January cal_label text NOT NULL, -- 2025-01 Jan -- fiscal fisc_year integer NOT NULL, fisc_quarter integer NOT NULL, fisc_quarter_label text NOT NULL, -- FY2025 Q3 fisc_month integer NOT NULL, fisc_month_abbr text NOT NULL, -- 07 - Jan fisc_month_name text NOT NULL, -- 07 - January fisc_label text NOT NULL, -- FY2025 P07 -- sort key period_key text NOT NULL -- 2025.07 (ltree-compatible) ); CREATE INDEX IF NOT EXISTS dim_period_drange_idx ON pf.dim_period USING gist (drange); CREATE INDEX IF NOT EXISTS dim_period_fisc_idx ON pf.dim_period (fisc_year, fisc_month); CREATE INDEX IF NOT EXISTS dim_period_cal_idx ON pf.dim_period (cal_year, cal_month); WITH cfg AS ( SELECT 6 AS fiscal_start_month -- change to match your fiscal year start month ) ,periods AS ( SELECT gs.d::date AS sdat, (gs.d + '1 month'::interval)::date AS edat, extract(year FROM gs.d)::int AS cal_year, extract(month FROM gs.d)::int AS cal_month, extract(quarter FROM gs.d)::int AS cal_quarter, ((extract(month FROM gs.d)::int - cfg.fiscal_start_month + 12) % 12) + 1 AS fisc_month, extract(year FROM gs.d)::int + CASE WHEN cfg.fiscal_start_month > 1 AND extract(month FROM gs.d)::int >= cfg.fiscal_start_month THEN 1 ELSE 0 END AS fisc_year FROM generate_series('2018-01-01'::date, '2035-12-01'::date, '1 month') gs(d) CROSS JOIN cfg ) INSERT INTO pf.dim_period ( sdat, edat, drange, ndays, cal_year, cal_quarter, cal_month, cal_month_abbr, cal_month_name, cal_label, fisc_year, fisc_quarter, fisc_quarter_label, fisc_month, fisc_month_abbr, fisc_month_name, fisc_label, period_key ) SELECT sdat, edat, daterange(sdat, edat) AS drange, edat - sdat AS ndays, cal_year, cal_quarter, cal_month, to_char(cal_month, 'FM00') || ' - ' || to_char(sdat, 'Mon') AS cal_month_abbr, to_char(cal_month, 'FM00') || ' - ' || to_char(sdat, 'Month') AS cal_month_name, to_char(sdat, 'YYYY-MM') || ' ' || to_char(sdat, 'Mon') AS cal_label, fisc_year, ceil(fisc_month / 3.0)::int AS fisc_quarter, 'FY' || fisc_year || ' Q' || ceil(fisc_month / 3.0)::int AS fisc_quarter_label, fisc_month, to_char(fisc_month, 'FM00') || ' - ' || to_char(sdat, 'Mon') AS fisc_month_abbr, to_char(fisc_month, 'FM00') || ' - ' || to_char(sdat, 'Month') AS fisc_month_name, 'FY' || fisc_year || ' P' || to_char(fisc_month, 'FM00') AS fisc_label, to_char(fisc_year, 'FM0000') || '.' || to_char(fisc_month, 'FM00') AS period_key FROM periods ON CONFLICT (sdat) DO NOTHING; -- preview first 24 months SELECT period_key, sdat, edat, ndays, cal_year, cal_quarter, cal_month, cal_month_abbr, fisc_year, fisc_quarter, fisc_month, fisc_month_abbr, fisc_quarter_label, fisc_label, cal_label FROM pf.dim_period ORDER BY sdat LIMIT 24;