Mysql 从具有“生效日期”的表构建每日视图
我有一个使用开始日期或生效日期的表。表中的值从开始日期起生效,直到它被同一表中具有更晚开始日期的另一个条目覆盖 我的模式和示例数据:Mysql 从具有“生效日期”的表构建每日视图,mysql,sql,query-optimization,Mysql,Sql,Query Optimization,我有一个使用开始日期或生效日期的表。表中的值从开始日期起生效,直到它被同一表中具有更晚开始日期的另一个条目覆盖 我的模式和示例数据: 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), ('
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);
我创建了一个用于模拟问题的
尽管SQLFiddle提供了正确的结果,但我不认为这是最好的方法。我不得不使用的查询有点复杂。我最终希望为这个表创建一个视图,该视图包含每天的正确值,而不管它是否在开始日期,就像Fiddle生成的输出一样,以便更容易地连接到此表。显然,我想确保这一观点是尽可能快。因此,我的问题是,如何改进和优化此查询,以便在这样的视图中使用?我将通过两个步骤来实现这一点 首先,您需要将时段结束时间带到每条记录,这将使您的行从事件变为时段:
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)
对于这种类型的视图,您需要非常小心。编写一个视图很容易,该视图擅长给出每个记录的有效日期,但在询问哪条记录在某个特定日期有效时速度较慢 因为回答第二个问题需要先回答每个日期的第一个问题,然后放弃失败 以下是确定日期并返回在该日期有效的行的合理方法
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'
编辑:
左联接的另一种替代方法是将相关子查询嵌入到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'
如果你真的需要这个,那么在你的数据库中添加一个日历表。似乎你能改进你的查询的唯一方法就是把日期表逻辑带到别处。您可以创建一个表,其中包含域日期维度所需的所有日期。它将简化查询您如何处理结果集?我将使用结果集构建一个视图,该视图将在日期加入到其他查询中。类似于创建视图。。。选择日期时,值为。。。。我需要优化此视图以加入日期字段。在查询特定日期的视图时,这将导致性能非常差。从另一个方向来说,从记录开始是好的,这个记录在哪一天有效?但是,当询问以日期开始并确定哪些记录对该日期有效时,有许多不可搜索的语句。我添加了基于子查询的选项,该子查询使用聚合来查找下一个值,而不是不存在的值;此视图封装了您不想在其他地方处理的逻辑。但这本身就带来了间接费用。没有办法使这样的视图像在自己的查询中嵌入连接条件那样有效。至少是因为它涉及到日历表上的联接,否则就不需要。你似乎有选择;SQL开发人员需要学习如何编写这些相关的子查询联接,或者需要不同的数据模型,或者需要ETL过程来创建持久化的每日事实表,或者需要接受性能影响。
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'
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'
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'