Optic访问计划中op:reduce()后使用修饰函数及关联优化问询
MarkLogic Optic API 分组取首行优化方案
场景回顾
通过TDE存储含生效日期的数据,查询时过滤生效状态行后,同一parentUuid存在多行有效数据,业务要求取生效日期降序后的首行。现有方案通过op:reduce()标记首行再转字面量聚合,存在性能瓶颈且无法直接衔接后续Optic操作。
疑问1:关联时仅取右计划每个parent的首行(或关联前过滤)
完全可以通过**窗口函数op:rank()**实现,无需提前触发结果计算。在关联前先对右计划做分组排名过滤,从根源减少关联数据量:
(: 先处理右计划:按parentUuid分组,生效日期降序排名,仅保留每组首行 :) let $rightPlan := op:from-view("your-schema", "your-right-view") => op:where(op:eq(op:col("isValid"), fn:true())) (: 过滤生效行 :) => op:window( op:partition-by(op:col("parentUuid")), op:order-by(op:desc(op:col("effectiveDate"))), op:rank("rowRank") ) => op:where(op:eq(op:col("rowRank"), 1)) (: 关联主计划与处理后的右计划 :) let $mainPlan := op:from-view("your-schema", "your-main-view") => op:join-inner($rightPlan, op:on(op:col("parentUuid"), op:col("parentUuid")))
这种方式让右计划在关联前就只保留每个parent的最新生效行,避免全量关联后再过滤冗余数据,性能远优于原方案。
疑问2:调整op:reduce()使用,保持Optic计划可用性
你当前方案调用op:result()将计划转为内存行集,再用op:from-literals()重构计划,这会断开Optic的查询优化链,导致后续操作无法利用MarkLogic的索引和分布式优化能力。
最优替代:用窗口函数替代op:reduce()
窗口函数是Optic原生支持的操作,全程保持计划可优化,后续可直接链式调用op:group-by()、op:where()等修饰函数:
let $optimizedPlan := $mainPlan => op:where(op:eq(op:col("isValid"), fn:true())) (: 过滤生效行 :) => op:window( op:partition-by(op:col("parentUuid")), op:order-by(op:desc(op:col("effectiveDate"))), op:rank("rowRank") ) => op:where(op:eq(op:col("rowRank"), 1)) (: 保留每组首行 :) => op:group-by( op:col("parentUuid"), op:count("total", op:col("id")), op:max("latestDate", op:col("effectiveDate")) (: 直接在这里添加其他聚合操作 :) ) => op:result() return $optimizedPlan
若必须保留op:reduce()(不推荐)
如果因特殊场景需使用op:reduce(),不要调用op:result(),直接在Optic计划链中使用,后续仍可继续链式调用Optic函数:
let $reducePlan := $mainPlan => op:where(op:eq(op:col("isValid"), fn:true())) => op:order-by(op:col("parentUuid"), op:desc(op:col("effectiveDate"))) => op:reduce(function($previous as map:map*, $row as map:map) as map:map* { let $prevUuid := if (fn:exists($previous)) then map:get($previous[fn:last()], "parentUuid") else () let $currUuid := map:get($row, "parentUuid") let $isFirst := fn:not($prevUuid eq $currUuid) let $_ := map:put($row, "isFirst", $isFirst) return ($previous, $row) }, () (: 初始值用空序列,避免多余空map :) ) => op:where(op:eq(op:col("isFirst"), fn:true())) => op:group-by(op:col("parentUuid"), op:count("count", op:col("id"))) => op:result() return $reducePlan
但这种方式性能远不如窗口函数,因为op:reduce()是行级遍历,无法利用MarkLogic的分布式计算优化,数据量大时差距明显。
关键结论
- 优先用**窗口函数
op:rank()**实现分组取首行,无论是关联前过滤还是主计划内过滤,都能保持Optic计划的可优化性,性能最优。 - 避免提前调用
op:result()中断计划链,确保后续操作能利用MarkLogic的索引和查询优化能力。
内容的提问来源于stack exchange,提问作者nobleb
相关产品推荐
相关产品推荐

