带日期范围的表连接最优方案技术咨询
Hey there, let's tackle this problem head-on. The core issue here is that storing every daily record in Table B creates unnecessary data bloat, but you still need to retrieve the DaysToDate values quickly for date range queries. Here are three actionable solutions, ordered by their effectiveness:
1. Replace Table B with a Static Date Table + Dynamic Calculations
This is the best long-term fix—you’ll eliminate redundant data entirely while keeping query speeds fast.
Step 1: Create a Static Date Dimension Table
First, build a small dim_date table that stores every date for the next 100 years (only ~36,500 rows—tiny for any database). Example schema:
CREATE TABLE dim_date ( date DATE PRIMARY KEY );
Populate it once with all dates you need (you can use a recursive CTE or simple script to generate the dates in minutes).
Step 2: Store the Initial DaysToDate Value in Table A
Looking at your example, DaysToDate starts at 10 for ID_A=1 and decreases by 1 each day, and starts at 7 for ID_A=2. Add an initial_days column to Table A to store this starting value:
| ID_A | DateStart | DateEnd | no_days | initial_days |
|---|---|---|---|---|
| 1 | 2019-02-01 | 2019-02-04 | 3 | 10 |
| 2 | 2019-02-04 | 2019-02-06 | 2 | 7 |
Step 3: Query Dynamically
Now you can join Table A with dim_date and calculate DaysToDate on the fly. This query will return exactly the result you expect, without needing Table B at all:
SELECT a.ID_A, a.DateStart, a.DateEnd, a.no_days, -- Generate ID_B if you still need it (optional) ROW_NUMBER() OVER (ORDER BY a.ID_A, d.date) AS ID_B, d.date AS Date, a.initial_days - DATEDIFF(day, a.DateStart, d.date) AS DaysToDate FROM TableA a JOIN dim_date d ON d.date >= a.DateStart AND d.date < a.DateEnd -- Matches your example where DateEnd is exclusive WHERE a.DateStart BETWEEN '2019-02-01' AND '2019-02-05' ORDER BY a.ID_A, d.date;
Why this works: The dim_date table is tiny and indexed (primary key on date), so joins are lightning fast. You avoid storing thousands/millions of redundant rows in Table B, and calculations are trivial for the database to handle.
2. Optimize Table B If You Must Keep It
If you can’t eliminate Table B (e.g., legacy system constraints), use these tweaks to keep performance high:
- Partition Table B by Date: Split the table into partitions by year or month. When users query a date range, the database only scans the relevant partitions instead of the entire table.
- Add a Covering Composite Index: Create an index like
(ID_A, Date) INCLUDE (DaysToDate). This lets the database answer your join query directly from the index without needing to look up rows in the main table. - Archive Old Data: Move records older than a certain threshold (e.g., 2 years) to an archive table. This keeps the active dataset small and fast to query.
3. Use a Materialized View (If Your Database Supports It)
For databases like PostgreSQL, Oracle, or SQL Server, a materialized view acts like a pre-computed table that you can refresh on demand. It gives you the convenience of Table B without manual daily data generation:
-- Example for PostgreSQL CREATE MATERIALIZED VIEW mv_table_b AS SELECT a.ID_A, a.DateStart, a.DateEnd, a.no_days, ROW_NUMBER() OVER (ORDER BY a.ID_A, d.date) AS ID_B, d.date AS Date, a.initial_days - DATEDIFF(day, a.DateStart, d.date) AS DaysToDate FROM TableA a JOIN dim_date d ON d.date >= a.DateStart AND d.date < a.DateEnd; -- Add indexes for fast queries CREATE UNIQUE INDEX idx_mv_table_b_idb ON mv_table_b(ID_B); CREATE INDEX idx_mv_table_b_date ON mv_table_b(Date);
You can refresh the view periodically (e.g., nightly) or when Table A is updated. Queries against the materialized view will be just as fast as querying Table B, but you avoid the ongoing data bloat of inserting daily rows.
内容的提问来源于stack exchange,提问作者D3stroyah

