这是@ Erwin对Efficient time series querying in Postgres的回答的后续问题。
为了简单起见,我将使用与该问题相同的表结构
id | widget_id | for_date | score |
最初的问题是为某个范围内的每个日期获取每个小部件的分数。如果日期中没有窗口小部件的条目,则显示该窗口小部件的上一个条目的分数。如果所有数据都包含在您查询的范围内,则使用交叉连接和窗口函数的解决方案运行良好。我的问题是,即使它位于我们正在查看的日期范围之外,我也希望得到之前的分数。
示例数据:
INSERT INTO score (id, widget_id, for_date, score) values
(1, 1337, '2012-04-07', 52),
(2, 2222, '2012-05-05', 99),
(3, 1337, '2012-05-07', 112),
(4, 2222, '2012-05-07', 101);
当我查询2012年5月5日至5月10日的范围(即generate_series('2012-05-05'::date, '2012-05-10'::date, '1d')
)时,我想获得以下内容:
DAY WIDGET_ID SCORE
May, 05 2012 1337 52
May, 05 2012 2222 99
May, 06 2012 1337 52
May, 06 2012 2222 99
May, 07 2012 1337 112
May, 07 2012 2222 101
May, 08 2012 1337 112
May, 08 2012 2222 101
May, 09 2012 1337 112
May, 09 2012 2222 101
May, 10 2012 1337 112
May, 10 2012 2222 101
到目前为止(也是@Erwin)的最佳解决方案是:
SELECT a.day, a.widget_id, s.score
FROM (
SELECT d.day, w.widget_id
,max(s.for_date) OVER (PARTITION BY w.widget_id ORDER BY d.day) AS effective_date
FROM (SELECT generate_series('2012-05-05'::date, '2012-05-10'::date, '1d')::date AS day) d
CROSS JOIN (SELECT DISTINCT widget_id FROM score) AS w
LEFT JOIN score s ON s.for_date = d.day AND s.widget_id = w.widget_id
) a
LEFT JOIN score s ON s.for_date = a.effective_date AND s.widget_id = a.widget_id
ORDER BY a.day, a.widget_id;
但正如您在此SQL Fiddle中所看到的,它会在前两天为小部件1337生成空分。我希望看到第1排的早期得分为52分。
是否有可能以有效的方式做到这一点?
答案 0 :(得分:1)
就像你写的那样,你应该找到匹配的分数,但如果有差距 - 用最接近的早期分数填写。在SQL中它将是:
SELECT d.day, w.widget_id,
coalesce(s.score, (select s2.score from score s2
where s2.for_date<d.day and s2.widget_id=w.widget_id order by s2.for_date desc limit 1)) as score
from (select distinct widget_id FROM score) AS w
cross join (SELECT generate_series('2012-05-05'::date, '2012-05-10'::date, '1d')::date AS day) d
left join score s ON (s.for_date = d.day AND s.widget_id = w.widget_id)
order by d.day, w.widget_id;
在这种情况下合并意味着“如果存在差距”。
答案 1 :(得分:1)
您可以在PostgreSQL中使用distinct on
语法
with cte_d as (
select generate_series('2012-05-05'::date, '2012-05-10'::date, '1d')::date as day
), cte_w as (
select distinct widget_id from score
)
select distinct on (d.day, w.widget_id)
d.day, w.widget_id, s.score
from cte_d as d
cross join cte_w as w
left outer join score as s on s.widget_id = w.widget_id and s.for_date <= d.day
order by d.day, w.widget_id, s.for_date desc;
或通过子查询得到最大日期:
with cte_d as (
select generate_series('2012-05-05'::date, '2012-05-10'::date, '1d')::date as day
), cte_w as (
select distinct widget_id from score
)
select
d.day, w.widget_id, s.score
from cte_d as d
cross join cte_w as w
left outer join score as s on s.widget_id = w.widget_id
where
exists (
select 1
from score as tt
where tt.widget_id = w.widget_id and tt.for_date <= d.day
having max(tt.for_date) = s.for_date
)
order by d.day, w.widget_id;
性能实际上取决于您在桌面上的索引(如果可能,则为唯一widget_id, for_date
)。我想如果每个widget_id
有很多行,那么第二行会更有效率,但你必须对你的数据进行测试。
<强>&GT;&GT; sql fiddle demo &lt;&lt;
答案 2 :(得分:1)
作为@Roman mentioned,DISTINCT ON
可以解决此问题。这个相关答案的细节:
子查询通常比CTE快一点:
SELECT DISTINCT ON (d.day, w.widget_id)
d.day, w.widget_id, s.score
FROM generate_series('2012-05-05'::date, '2012-05-10'::date, '1d') d(day)
CROSS JOIN (SELECT DISTINCT widget_id FROM score) AS w
LEFT JOIN score s ON s.widget_id = w.widget_id AND s.for_date <= d.day
ORDER BY d.day, w.widget_id, s.for_date DESC;
您可以使用FROM
列表中的表格返回函数。
一个multicolumn index应该是绩效的关键:
CREATE INDEX score_multi_idx ON score (widget_id, for_date, score)
仅包含第三列score
以使其成为covering index in Postgres 9.2 or later。您不会在早期版本中包含它。
当然,如果你有许多小部件和广泛的日子,CROSS JOIN
会产生很多行,这些行有价格标签。只选择您实际需要的小部件和日期。