我希望“左连接”一个表,以便不仅将值连接到匹配的行,还连接到任何后续的不匹配行,直到下一个匹配的行。换句话说,我想用前一个非空值填充空值。
样本数据和所需结果:
表x
:
id
----
1
2
3
4
5
表y
:
id | val
----+-----
1 | a
4 | b
select x.id, y.val from x left join y on x.id=y.id order by x.id;
的结果:
id | val
----+-----
1 | a
2 |
3 |
4 | b
5 |
期望的结果:
id | val
----+-----
1 | a
2 | a
3 | a
4 | b
5 | b
答案 0 :(得分:6)
在x.id
和y.id
上创建索引 - 如果这些是您的主键,您可能已经拥有索引。
多列索引也可能有所帮助,尤其是第9 + 2行中的index only scans:
CREATE INDEX y_mult_idx ON y (id DESC, val)
但是,在我的测试中,最初并未使用此索引。不得不向val
添加(否则无意义)ORDER BY
以说服查询计划程序排序顺序匹配。请参阅查询 3 。
该索引在此合成设置中几乎没有差别。但对于包含更多列的表格,从表格中检索val
变得越来越昂贵,使“覆盖”索引更具吸引力。
SELECT DISTINCT ON (x.id)
x.id, y.val
FROM x
JOIN y ON y.id <= x.id
ORDER BY x.id, y.id DESC;
在此相关答案中使用DISTINCT
的技术的更多解释:
我进行了一些测试,因为我怀疑第一个查询不能很好地扩展。它有一个小桌子很快,但没有更大的桌子。 Postgres不会优化计划,而是以(有限的)交叉加入开始,费用为O(N²)
。
此查询仍然相当简单,并且可以很好地扩展:
SELECT x.id, y.val
FROM x
JOIN (SELECT *, lead(id, 1, 2147483647) OVER (ORDER BY id) AS next_id FROM y) y
ON x.id >= y.id
AND x.id < y.next_id
ORDER BY 1;
窗口函数lead()
是有用的。我使用该选项提供默认值来覆盖最后一行的角落情况:2147483647
是biggest possible integer。适应您的数据类型。
SELECT x.id
,(SELECT val FROM y WHERE id <= x.id ORDER BY id DESC, val LIMIT 1) AS val
FROM x;
通常,相关子查询往往很慢。但是这个可以从(覆盖)指数中选择一个值,否则它就会变得如此简单以至于它可以竞争。
额外的ORDER BY
项val
(大胆强调)似乎毫无意义。但添加它会使查询计划程序相信可以使用上面的多列索引y_mult_idx
,因为排序顺序匹配。注意
<{1>}输出中的仅使用y_mult_idx进行扫描..
。
经过热烈的辩论和多次更新后,我收集了到目前为止发布的所有查询,并制作了一个快速概述的测试用例。我只使用1000行,因此SQLfiddle没有超时查询速度。但是前四名(Erwin 2,Clodoaldo,a_horse,Erwin 3)在我所有的本地测试中都是线性缩放的。 再次更新以包含我的最新添加内容,现在按性能改进格式和顺序:
答案 1 :(得分:4)
select id,
first_value(t.val) over (partition by group_flag order by t.id) as val
from (
select x.id,
y.val,
sum(case
when y.val is null then 0
else 1
end) over (order by x.id) as group_flag
from x
left join y on x.id=y.id
) t
order by id;
SQLFiddle示例:http://sqlfiddle.com/#!12/38903/1
答案 2 :(得分:4)
select
id,
first_value(val) over(partition by g order by id) val
from (
select
x.id, val, count(val) over(order by x.id) g
from
x
left join
y on x.id=y.id
) s
order by id
答案 3 :(得分:2)
SELECT
m.id,
y.val
FROM (
SELECT
x.id,
MAX(y.id) id_y
FROM
x INNER JOIN y ON x.id >= y.id
GROUP BY
x.id
) m INNER JOIN y ON m.id_y = y.id
ORDER BY
m.id
请参阅小提琴here。
答案 4 :(得分:2)
我喜欢用(NOT)EXISTS表示集合函数MIN(),MAX()或closer_to()
。
SELECT x.id, y.val
FROM x
JOIN y ON y.id <= x.id
WHERE NOT EXISTS (SELECT *
FROM y y2
WHERE y2.id <= x.id -- same condition AS main query
AND y2.id > y.id -- but closer to x.id
)
;
我的直觉是,这将产生与Erwin的答案完全相同的查询计划。
答案 5 :(得分:1)
使用COALESCE和子查询作为逻辑。
子查询将检索最后一个val值。
试试这个:
SELECT x1.id,
coalesce(y1.val, (SELECT val
FROM y
WHERE id = (SELECT Max(x2.id)
FROM x AS x2
JOIN y AS y2
ON x2.id = y2.id
WHERE x2.id < x1.id)))
FROM x AS x1
LEFT JOIN y AS y1
ON x1.id = y1.id
ORDER BY x1.id;
sqlfiddle:http://www.sqlfiddle.com/#!12/42526/1
答案 6 :(得分:0)
我不确定如何使用单个存储过程实现此目的。类似于以下的逻辑将返回所需的结果
create PROCEDURE GetData
AS
BEGIN
Declare @resultTable TABLE(
id int,
value varchar(10))
Declare @id int
Declare @previousValue varchar(10)
Declare @currentValue varchar(10)
DECLARE x_cursor CURSOR
FOR SELECT id FROM x order by id
OPEN x_cursor
FETCH NEXT FROM x_cursor into @id;
WHILE (@@FETCH_STATUS = 0)
BEGIN
select @currentValue = isnull(val,@previousValue) from Y where id = @id
insert into @resultTable values(@id,@currentValue)
set @previousValue = @currentValue
FETCH NEXT FROM x_cursor into @id;
END
Close x_cursor
Deallocate x_cursor
select * from @resultTable
END
GO