PAYATHON 2026

如何在 kdb+/q 中高效筛选并展开 c4 嵌套表?

数据小许

可以先筛选子表,再根据匹配数量生成父行索引。这种写法仍需遍历各个子表,但可以集中复制父列并完成最终合并,减少为每个父行构造临时结果的操作。能否明显缩短现有的 12 秒耗时,需要用实际数据测量。

先处理 enlist 带来的嵌套层级

给出的数据中,c4 的每个单元格都是 enlist table,因此筛选前应通过 first 取出完整子表。如果实际数据的单元格已经直接保存 table,就应省略这一步;对 table 使用 first 会取出第一行。

只展开 c5 属于 `x`a`b`m 的记录,没有匹配记录的父行不进入结果。

按匹配数量批量展开父行

下面的函数适用于示例中的非空父表,要求各个子表具有一致的列结构和类型,且子表列名与保留的父列名不重叠。

flattenC4:{[t]
  / Each c4 cell contains one enlisted table
  children:{[v]
    s:first v;
    select from s where c5 in `x`a`b`m
  } each t`c4;

  / Repeat each parent index by its matching child count
  n:count each children;
  idx:raze n#'til count t;

  / Expand parent columns, then combine column dictionaries
  parents:(delete c4 from t)[idx];
  flip (flip parents),flip raze children
 };

result:flattenC4 tstT1;

这里的 #' 对两侧对应的元素分别执行 Take:左侧是匹配数量,右侧是父行编号。对于给出的数据,逻辑上的中间结果为:

/ Matching child counts
1 2 0 1

/ Expanded parent indices
0 1 1 3

因此,AAPL 保留一行,GOOG 复制为两行,MSFT 不保留,AMZN 保留一行。展开后的子记录依次为 x、a、b、m,顺序与父行索引一致。

raze children 把筛选后的子表纵向合并。raze 每次折叠一层可连接的结构,其语义见 KX 的 raze 文档。最后通过 flip 将两侧转为列字典,合并后再转回表,得到 c1、c2、c3、c5、c6。

为什么值得与原写法比较

原写法对每个父行构造重复的父记录,将其与筛选后的子表合并,再汇总所有结果。上面的写法将这部分工作改为一次生成索引和一次父表取行。

子表仍需筛选。如果主要耗时来自大量小表的筛选,收益可能有限。仅凭问题中的耗时,无法确定真正的瓶颈,也不能保证某个固定加速倍数。

如果先展开全部子记录再筛选,可能生成大量最终不会保留的中间行。筛选命中率较低时,通常更值得尝试先筛选,再复制父列。

核对结果并测量耗时

可以用父表已有的时间戳构造预期结果,避免 .z.p 随运行时间变化造成误判:

expected:([]
  c1:`AAPL`GOOG`GOOG`AMZN;
  c2:101.25 2845.6 2845.6 178.9;
  c3:tstT1[`c3][0 1 1 3];
  c5:`x`a`b`m;
  c6:10 1 2 7
 );

result~expected

然后在同一份实际数据上比较两种实现,并将筛选、索引生成和合并全部计入耗时:

\ts result:flattenC4 tstT1

这些代码尚未在此处的 q 运行环境中实测。生产使用前,还应核对全部不匹配、空子表和空父表的处理;上面的函数以非空父表为前提。数据量很大时,children、索引和输出表也会占用内存,可以按父行分批处理,并保持批次顺序。

备注:内容仅供参考。