“A good star schema without indexes is like a library without a catalog.”
Logical บอกว่าเก็บอะไร — Physical บอกว่าเก็บอย่างไรให้เร็วและถูก
fact_sales, dim_product)fact_sales(date_key, product_key, amount) — Physical เลือก partition by date_key + index on product_keyเก็บทั้งแถวใน 8KB page — ดีกับ OLTP (อ่าน/เขียนทีละแถว)
Heap + TOAST สำหรับค่าขนาดใหญ่
ClickHouse, Snowflake micro-partitions, BigQuery Capacitor — อ่านเฉพาะคอลัมน์ที่ SELECT, บีบอัดสูง
เหมาะกับ OLAP: SELECT SUM(amount) WHERE year=2024 ข้ามคอลัมน์อื่นทั้งหมด
Star schema สังเคราะห์สำหรับห้องแล็บ — ดาวน์โหลดและรันได้ทันที
dim_product(100 rows)
dim_customer(500 rows)
dim_date(731 rows, 2023-2024)
fact_sales(50k rows)
sale_id PK, date_key FK, product_key FK,
customer_key FK, quantity, amount
| sale_id | date_key | product | qty | amount |
|---|---|---|---|---|
| 1 | 523 | 42 | 3 | 872.10 |
| 2 | 112 | 7 | 8 | 214.50 |
| 3 | 701 | 89 | 1 | 45.20 |
| … | … | … | … | … |
สร้างด้วย gen_wk08.py — seed 42, reproducible
Copy-paste: psql -f data/wk08/wk08_demo.sql หรือดู code ถัดไป
บิตเวกเตอร์ต่อค่าต่าง → ดีกับ low-cardinality (tier, region)
-- Postgres จำลองด้วย B-Tree + bitmap scan
-- Oracle มี native bitmap
SELECT * FROM dim_customer
WHERE tier='Gold'; -- BitmapAnd/Or
Block Range Index — สรุป min/max ต่อ block range, เล็กมาก
CREATE INDEX idx_brin
ON fact_sales USING BRIN(date_key)
WITH (pages_per_range=128);
ดีกับ append-only fact เรียงตามเวลา
ลำดับสำคัญ: คอลัมน์กรองบ่อยก่อน
CREATE INDEX idx_fact_cover
ON fact_sales(date_key, product_key);
-- ใช้ได้กับ WHERE date_key=... AND product_key=...
-- หรือ WHERE date_key=... (leftmost prefix)
Index-only scan — ไม่ต้องแตะ heap
CREATE INDEX idx_cover
ON fact_sales(date_key, product_key)
INCLUDE (amount, quantity);
-- SELECT amount WHERE date_key=... อ่านแค่ index
fact_sales สร้าง composite บน (date_key, product_key, customer_key) ตาม query pattern ยอดนิยมของคุณ — วัดด้วย pg_stat_statements
อ้างอิง: Kimball Star Schema • Use The Index, Luke!
EXPLAIN ANALYZE
SELECT SUM(amount) FROM fact_sales
WHERE date_key BETWEEN 400 AND 430;
-- Seq Scan on fact_sales
-- Filter: (date_key >=400 AND <=430)
-- Rows: 2134 Time: 18.4 ms
ต้องอ่านทุก page
CREATE INDEX idx_fact_sales_date
ON fact_sales(date_key);
ANALYZE fact_sales;
EXPLAIN ANALYZE
SELECT SUM(amount) FROM fact_sales
WHERE date_key BETWEEN 400 AND 430;
-- Index Scan using idx_fact_sales_date
-- Buffers: shared hit=45
-- Time: 1.2 ms (15x faster)
-- Copy-paste: วัดขนาด index
SELECT relname, pg_size_pretty(pg_relation_size(indexrelid)) FROM pg_stat_user_indexes WHERE relname LIKE 'idx_fact%';
Partitioning แบ่งตารางใหญ่เป็นชิ้นเล็ก — query อ่านเฉพาะชิ้นที่เกี่ยวข้อง (partition pruning)
DETACH old partition แทน DELETE ล้านแถว
VACUUM / REINDEX ทีละ partition — ลด lock
ตามช่วงต่อเนื่อง — เช่น date_key, order_date
PARTITION BY RANGE (date_key)
FOR VALUES FROM (1) TO (91) -- Q1
Cloud: Snowflake clustering • BigQuery partitioned tables • ClickHouse partitioning
Copy-paste (PostgreSQL 15+):
CREATE TABLE fact_sales_part (
sale_id INT NOT NULL,
date_key INT NOT NULL,
product_key INT NOT NULL,
customer_key INT NOT NULL,
quantity INT NOT NULL,
amount NUMERIC(12,2) NOT NULL,
PRIMARY KEY (sale_id, date_key)
) PARTITION BY RANGE (date_key);
-- 8 quarterly partitions (731 days ~ 8 quarters)
CREATE TABLE fact_sales_part_q1 PARTITION OF fact_sales_part
FOR VALUES FROM (1) TO (92);
CREATE TABLE fact_sales_part_q2 PARTITION OF fact_sales_part
FOR VALUES FROM (92) TO (183);
-- ... q3 .. q8
INSERT INTO fact_sales_part SELECT * FROM fact_sales;
-- Verify pruning
EXPLAIN SELECT SUM(amount) FROM fact_sales_part
WHERE date_key BETWEEN 400 AND 430;
-- Append -> Seq Scan on fact_sales_part_q5 only
เต็มไฟล์: wk08_demo.sql • Docs →
DETACH PARTITION แทน DELETEALTER TABLE fact_sales_part
DETACH PARTITION fact_sales_part_q1;
-- เร็วกว่า DELETE WHERE date_key <92
ANALYZE หลัง load-- Check partition sizes
SELECT relname,
pg_size_pretty(pg_relation_size(relid))
FROM pg_partition_tree('fact_sales_part');
ดูเพิ่ม: Partition maintenance docs • Snowflake micro-partitions
รวมพลัง: Pruning ตัด partition ที่ไม่เกี่ยว • Index scan ใน partition ที่เหลือ
Lab ถัดไป: รันโค้ดจริงบน 50k แถว, วัด EXPLAIN, เปรียบเทียบเวลา
psql -f data/wk08/wk08_demo.sql (สร้างตาราง + โหลด CSV)EXPLAIN ANALYZE SELECT d.year, p.category, SUM(f.amount)
FROM fact_sales f JOIN dim_date d ON f.date_key=d.date_key
JOIN dim_product p ON f.product_key=p.product_key
WHERE d.year=2024 AND p.category='Electronics'
GROUP BY 1,2;
CREATE INDEX idx_fact_sales_date ON fact_sales(date_key); ANALYZE fact_sales;fact_sales_part ตามไฟล์ wk08_demo.sqlEXPLAIN ANALYZE อีกครั้งบน partitioned + indexed table, เปรียบเทียบ Buffers/Time