PAYATHON 2026

如何在 q 函数中使用 functional select 按动态列名分组?

数据小陈

可以使用 functional select 实现动态分组。先对 groupCol 求值,得到传入的列名,再用它构造分组字典;labels 则作为 symbol 常量数据嵌入筛选表达式。

对于单个 symbol 分组列,写法如下:

c:enlist (in;`label;enlist labels);
b:(enlist groupCol)!enlist groupCol;

以下说明假定 groupCol 是数据表中某列的 symbol 名称,label、priced 和 status 为 symbol 列,qty 为数值列。

为什么原来的写法没有按指定列分组?

原始代码中的两处写法混淆了参数值和查询表达式中的名称:

enlist (in;`label;`labels)
(enlist `groupCol)!enlist `groupCol

在 functional select 的表达式树中,裸 symbol 通常表示需要解析的列名或名称。因此,反引号后的 groupCol 是固定名称,不会先读取函数参数 groupCol,再把参数值当作列名。

同样,反引号后的 labels 也没有把参数中的筛选值直接嵌入表达式。如果这个名称能被解析到其他变量或列,查询可能执行,但含义仍容易偏离预期。

需要区分三种情况:

  • groupCol 是普通 q 表达式中的局部参数,其值可以是某个实际列名。
  • 表达式树中的裸 symbol 引用对应的列或名称。
  • 表达式树中经过 enlist 包装的 symbol 或 symbol 列表作为常量数据使用。

KX 的 Functional qSQL 文档说明了这种区分和分组字典的构造方式。

普通 qSQL 中的 by groupCol 也不会自动把参数值再次解释为列名。如果它解析得到的是标量参数,就可能让所有记录落入同一个分组;如果表中存在同名列,行为又会受到该列影响。因此,动态列名适合在 functional select 中显式构造。

正确构造 where 和 by

筛选条件写成:

c:enlist (in;`label;enlist labels);

两层 enlist 的作用不同:外层把一个条件表达式包装成条件列表,内层把 labels 中的 symbol 值标记为常量数据。

单列分组字典写成:

b:(enlist groupCol)!enlist groupCol;

例如传入:

groupCol:`desk;

得到的分组字典等价于:

(enlist `desk)!enlist `desk

字典的键是结果中的分组列名,值是引用原表 desk 列的表达式。

原先尝试的写法:

(groupCol)!(groupCol)

只有括号,没有把 symbol atom 转换成列表。这里需要用 enlist 构造两侧各含一项的列表。

完整函数示例

可以先保存重复使用的表达式树,再组合聚合字典,避免维护一整行嵌套表达式。

原代码的 volDealt 使用了 DEAlT,其他成交指标使用 DEALT,二者大小写不同。下面假定这些指标都应匹配 DEALT,因此统一使用它;如果它们代表不同业务状态,应保留各自条件。

agg:{[data;groupCol;labels]
  c:enlist (in;`label;enlist labels);
  b:(enlist groupCol)!enlist groupCol;

  p:(=;`priced;enlist `Y);
  d:(in;`status;enlist `DEALT);

  r:(count;(`i;(where;p)));
  v:(sum;(`qty;(where;p)));
  rd:(count;(`i;(where;d)));
  vd:(sum;(`qty;(where;d)));

  a:`req`vol`reqDealt`volDealt`reqPcnt`volPcnt!(
    r;
    (%;v;1e6);
    rd;
    (%;vd;1e6);
    (%;rd;r);
    (%;vd;v)
    );

  ?[data;c;b;a]
  };

调用时,分组列使用 symbol,筛选值使用 symbol 或 symbol 列表。例如,假设表中存在 desk 列,且 label 包含相应值:

agg[data;`desk;`L1`L2]

查询会先筛选 label,再按 desk 的不同取值计算六个指标,返回分组后的 keyed table。

如何缩小排查范围?

如果修改后仍有错误,可以先只计算每组记录数,确认动态分组和筛选条件正常:

checkGroups:{[data;groupCol;labels]
  ?[data;
    enlist (in;`label;enlist labels);
    (enlist groupCol)!enlist groupCol;
    (enlist `rows)!enlist (count;`i)]
  };

确认这一步正常后,再加入原有聚合表达式。也可以检查表结构和入参类型:

meta data
cols data
type groupCol
type labels

这里的 groupCol 应为单个 symbol,其类型为 -11h。字符串列名需要先转换为 symbol。上面的完整函数按单列分组设计,多列分组时应将列名组织成 symbol 列表,再构造相应字典。

聚合口径还需要注意什么?

reqPcnt 和 volPcnt 当前计算的是比例,没有乘以 100。分子只筛选 status,分母只筛选 priced,所以比例不一定小于或等于 1。修改动态分组不会改变这一统计口径。

如果某组没有 priced=Y 的记录,或这些记录的 qty 合计为零,还需要按业务规则处理零分母。

备注:内容仅供参考。