我有数据以环形结构(或
circular buffer)排列,也就是说它可以表示为循环的序列:…- 1-2-3-4-5-1-2-3 -….查看此图片以了解5部分戒指:
我想创建一个窗口查询,可以将滞后和铅项目组合成一个三点数组,但我无法弄明白.例如,在5部分环的第1部分,滞后/超前序列是5-1-2,或者部分4是3-4-5.
以下是两个具有不同数量部件的环的示例表(每个环总是多于三个):
create table rp (ring int,part int); insert into rp(ring,part) values(1,generate_series(1,5)); insert into rp(ring,part) values(2,7));
这是一个几乎成功的查询:
SELECT ring,part,array[ lag(part,1,NULL) over (partition by ring),lead(part,1) over (partition by ring) ] AS neighbours FROM rp; ring | part | neighbours ------+------+------------ 1 | 1 | {NULL,2} 1 | 2 | {1,2,3} 1 | 3 | {2,3,4} 1 | 4 | {3,4,5} 1 | 5 | {4,5,1} 2 | 1 | {NULL,2} 2 | 2 | {1,3} 2 | 3 | {2,4} 2 | 4 | {3,5} 2 | 5 | {4,6} 2 | 6 | {5,6,7} 2 | 7 | {6,7,1} (12 rows)
我唯一需要做的就是用每个环的终点替换NULL,这是最后一个值.现在,随着lag
and lead
window functions,there is a last_value
function这将是理想的.但是,这些不能嵌套:
SELECT ring,last_value(part) over (partition by ring)) over (partition by ring),1) over (partition by ring) ] AS neighbours FROM rp; ERROR: window function calls cannot be nested LINE 2: lag(part,last_value(part) over (partition by ring)) ...
更新.感谢@ Justin建议使用coalesce来避免嵌套窗口函数.此外,许多人已经指出,第一个/最后一个值需要通过环序列的明确顺序,这恰好是该示例的一部分.所以稍微随机化输入数据:
create table rp (ring int,part) select 1,5) order by random(); insert into rp(ring,part) select 2,7) order by random();
解决方法
>像
@Justin provided一样使用
>使用first_value()/ last_value(),您需要向窗口定义添加ORDER BY子句,或者未定义顺序.你刚才在这个例子中很幸运,因为在创建虚拟表之后,这些行恰好按顺序排列.
添加ORDER BY后,默认窗口框架将在当前行结束,您需要特殊情况下调用last_value() – 或者在窗口框架中恢复排序顺序,如我的第一个示例中所示.
>多次重用窗口定义时,显式的WINDOW子句简化了语法:
COALESCE
.
>使用first_value()/ last_value(),您需要向窗口定义添加ORDER BY子句,或者未定义顺序.你刚才在这个例子中很幸运,因为在创建虚拟表之后,这些行恰好按顺序排列.
添加ORDER BY后,默认窗口框架将在当前行结束,您需要特殊情况下调用last_value() – 或者在窗口框架中恢复排序顺序,如我的第一个示例中所示.
>多次重用窗口定义时,显式的WINDOW子句简化了语法:
SELECT ring,ARRAY[ coalesce( lag(part) OVER w,first_value(part) OVER (PARTITION BY ring ORDER BY part DESC)),coalesce( lead(part) OVER w,first_value(part) OVER w) ] AS neighbours FROM rp WINDOW w AS (PARTITION BY ring ORDER BY part);
更好的是,重用相同的窗口定义,因此Postgres可以在单次扫描中计算所有值.为此,我们需要定义一个自定义窗口框架:
SELECT ring,last_value(part) OVER w),first_value(part) OVER w) ] AS neighbours FROM rp WINDOW w AS (PARTITION BY ring ORDER BY part RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) ORDER BY 1,2;
SELECT ring,last_value(part) OVER (w RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)),first_value(part) OVER w) ] AS neighbours FROM rp WINDOW w AS (PARTITION BY ring ORDER BY part) ORDER BY 1,2;
对于具有许多部件的环,可能会更快.你必须测试.
SQL Fiddle展示了所有三个改进的测试用例.考虑查询计划.
有关窗框定义的更多信息:
> In the manual.
> PostgreSQL window function: partition by comparison
> PostgreSQL query with max and min date plus associated id per row