我有一张表使用"开始日期"或有效日期。表中的值从开始日期开始生效,直到被同一个表中具有较晚开始日期的另一个条目覆盖。
我的架构和示例数据:
CREATE TABLE VALUE_DATA (
`start_date` DATE,
`value` FLOAT
);
INSERT INTO VALUE_DATA (start_date, value) VALUES
('2015-01-01', 10),
('2015-01-03', 20),
('2015-01-08', 30),
('2015-01-09', 15);
产生所需结果的查询:
SELECT date, value
FROM(
SELECT date, MAX(start_date) as max_start
FROM (
select curdate() - INTERVAL (ones.digit + (10 * tens.digit) + (100 * hundreds.digit)) DAY as date
from (select 0 as digit union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as ones
cross join (select 0 as digit union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as tens
cross join (select 0 as digit union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as hundreds
) DATE_TABLE
LEFT JOIN VALUE_DATA ON (DATE_TABLE.date >= VALUE_DATA.start_date)
WHERE DATE_TABLE.date between '2015-01-01' and '2015-01-10'
GROUP BY date
) START_DATES LEFT JOIN VALUE_DATA ON (START_DATES.max_start = VALUE_DATA.start_date);
我已经创建了this Phing ticket来模拟问题。
虽然SQL Fiddle有效(给出了正确的结果),但我并不相信它是最好的方法。我必须使用的查询有点复杂。我最终想为这个表创建一个视图,其中包含每天的正确值,无论它是否落在开始日期(如小提琴产生的输出),以便更容易加入到此表。显然,我想确保这个观点尽可能快。所以我的问题是,如何在这样的视图中改进(优化)此查询?
答案 0 :(得分:1)
我会分两步来解决这个问题。
首先,您需要将句点的结尾带到每条记录,这会将您的行从事件转换为句点:
SELECT
v1.start_date,
v2.start_date as next_start_date,
v1.value
FROM
VALUE_DATA v1 LEFT JOIN
VALUE_DATA v2 ON
v1.start_date < v2.start_date AND
NOT EXISTS
(SELECT * FROM VALUE_DATA
WHERE start_date > v1.start_date and start_date < v2.start_date)
现在您需要将date dimension添加到架构中。
获得日期维度后,可以轻松将其加入以前的查询:
SELECT
d.date, v1.value
FROM
VALUE_DATA v1 LEFT JOIN
VALUE_DATA v2 ON
v1.start_date < v2.start_date AND
NOT EXISTS
(SELECT * FROM VALUE_DATA
WHERE start_date > v1.start_date and start_date < v2.start_date)
INNER JOIN DATE_DIMENSION d ON
d.date >= v1.start_date AND d.date < COALESCE(v2.start_date, CURDATE())
以下查询可能在MySQL中更快,它有点来自前两个,而不是使用JOIN
在子查询中找到下一个start_date
:
SELECT
d.date, v1.value
FROM
VALUE_DATA v1
INNER JOIN DATE_DIMENSION d ON
d.date >= v1.start_date AND
d.date < (SELECT COALESCE(MIN(v.start_date), CURDATE())
FROM VALUE_DATA v
WHERE v.start_date > v1.start_date);
答案 1 :(得分:1)
您需要非常小心这种类型的视图。编写一个擅长给出每个记录有效的所有单独日期的视图会很容易,但在询问哪个记录在某个特定日期有效时会很慢。
(因为回答第二个问题涉及首先回答每个和每个日期的第一个问题,然后丢弃失败。)
以下是合理的,在约会时返回有效的行。
CREATE VIEW DAILY_VALUE_DATA AS (
SELECT
DATE_TABLE.date,
VALUE_TABLE.value
FROM
DATE_TABLE
LEFT JOIN
VALUE_DATA
ON VALUE_DATA.start_date = (SELECT MAX(lookup.start_date)
FROM VALUE_DATA lookup
WHERE lookup.start_date <= DATE_TABLE.date
)
);
SELECT * FROM DAILY_VALUE_DATA WHERE date = '2015-08-11'
注意:这假设DateTable是一个真正的持久性物化表,而不是您使用的内联视图,使用它将极大地影响性能。
它还假定VALUE_DATA
将start_date
编入索引。
<强> 编辑: 强>
我还发现你的价值表可能还有其他列。我们假设它是每人的值。也许是他们在任何特定日期的地址。
要扩展上面的查询,您还需要加入person
表...
CREATE VIEW DAILY_VALUE_DATA AS (
SELECT
PERSON.id AS person_id,
DATE_TABLE.date,
VALUE_TABLE.value
FROM
PERSON
INNER JOIN
DATE_TABLE
ON DATE_TABLE.date >= PERSON.date_of_birth
AND DATE_TABLE.date < COALESCE(PERSON.date_of_death, CURDATE() + 1)
LEFT JOIN
VALUE_DATA
ON VALUE_DATA.start_date = (SELECT MAX(lookup.start_date)
FROM VALUE_DATA lookup
WHERE lookup.start_date <= DATE_TABLE.date
AND lookup.person_id = PERSON.id
)
);
SELECT * FROM DAILY_VALUE_DATA WHERE person_id = 1 AND date = '2015-08-11'
<强> 编辑: 强>
LEFT JOIN
的另一种替代方法是将相关的子查询嵌入SELECT
块中。当您只从目标表中提取一个值时,这是有效的,但如果需要从目标表中提取多个值,则效果较差...
CREATE VIEW DAILY_VALUE_DATA AS (
SELECT
PERSON.id AS person_id,
DATE_TABLE.date,
(SELECT VALUE_DATA.value
FROM VALUE_DATA
WHERE VALUE_DATA.start_date <= DATE_TABLE.date
AND VALUE_DATA.person_id = PERSON.id
ORDER BY VALUE_DATA.start_date DESC
LIMIT 1
) AS value
FROM
PERSON
INNER JOIN
DATE_TABLE
ON DATE_TABLE.date >= PERSON.date_of_birth
AND DATE_TABLE.date < COALESCE(PERSON.date_of_death, CURDATE() + 1)
);
SELECT * FROM DAILY_VALUE_DATA WHERE person_id = 1 AND date = '2015-08-11'