8

我想“左连接”一个表,以便一个值不仅连接到匹配的行,而且连接到任何后续的非匹配行,直到下一个匹配行。换句话说,我想用之前的非空值填充空值。

样本数据和预期结果:

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
4

7 回答 7

6

指数

x.id在和上创建索引y.id- 如果它们是您的主键,您可能已经拥有这些索引。
多列索引也可能有所帮助,尤其是在 pg 9.2+ 中仅扫描索引时:

CREATE INDEX y_mult_idx ON y (id DESC, val)

但是,在我的测试中,最初并没有使用这个索引。必须添加(否则毫无意义)valORDER BY使查询计划者相信排序顺序匹配。见查询3

该指数在这种合成设置中几乎没有什么区别。但是对于具有更多列的表,从表中检索val变得越来越昂贵,使得“覆盖”索引更具吸引力。

查询

1) 简单

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;

SQL小提琴。

DISTINCT在这个相关答案中对该技术的更多解释:

我进行了一些测试,因为我怀疑第一个查询不能很好地扩展。小桌子很快,但大桌子不好。Postgres 没有优化计划,而是从(有限的)交叉连接开始,成本为O(N²).

2) 快速

这个查询仍然相当简单并且可以很好地扩展:

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最大可能的整数。适应您的数据类型。

3)非常简单,几乎一样快

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上面的多列索引,因为排序顺序匹配。注意

仅索引扫描使用 y_mult_idx ..

EXPLAIN输出中。

测试用例

经过热烈的辩论和多次更新,我收集了迄今为止发布的所有查询,并制作了一个测试用例以进行快速概述。我只使用 1000 行,因此 SQLfiddle 不会因较慢的查询而超时。但是前 4 名(Erwin 2、Clodoaldo、a_horse、Erwin 3)在我所有的本地测试中都是线性扩展的。再次更新以包括我的最新添加,现在按性能改进格式和排序:

Big SQL Fiddle比较性能。

于 2013-04-07T12:53:27.370 回答
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

于 2013-04-07T11:05:17.177 回答
4

SQL小提琴

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
于 2013-04-07T11:08:15.780 回答
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

在此处查看小提琴。

于 2013-04-07T11:12:34.210 回答
2

closer_to()我喜欢用 (NOT) EXISTS来表达聚合函数 MIN()、MAX() 。

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 的答案完全相同的查询计划。

于 2013-04-07T14:27:23.197 回答
1

使用COALESCE和 Subquery 作为逻辑。

子查询将检索最后一个 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

于 2013-04-07T11:03:30.490 回答
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
于 2013-04-07T11:44:06.070 回答