极具特色的位置运算

集合在计算机中一般都存储为数组形式,其成员天然会有个位置。数据表本质上是记录的集合,也会被存储成数组,作为成员的记录也有位置的概念。而实际应用中确实有很多分析计算都是位置相关的,但 SQL 把数据表处理成无序集合,就会很不方便。

esProc SPL 保留了数组的有序性,并提供了丰富的位置运算方法,可以用简洁易懂的代码轻松实现这类计算需求。

1. 位置序号

在有序数据集中,记录的位置本身就具有业务意义。比如我们要取某股票 2025 年第 5、10、15、20...交易日的数据。简单的思路是在交易日有序的股票数据中,过滤出 2025 年的记录集合,从中取第 5、10、15、20...条记录就可以了。

无序的 SQL 要用窗口函数临时造出个序号才能完成这样的任务,思路有点"绕":

sql 复制代码
SELECT *
FROM (
    SELECT *,
        ROW_NUMBER() OVER (ORDER BY date) AS rn
    FROM 
        stock
    WHERE 
        YEAR(date) = 2025
)
WHERE 
   rn % 5 = 0;

SPL 支持对记录的位置序号进行计算,可以按照上述简单思路轻松解题:

csharp 复制代码
stock.select(year(date)==2025).select(# % 5==0)

在对数据集循环计算时,# 是当前记录的位置序号。第二个 select 的条件是记录序号 #能被 5 整除,过滤结果自然就是第 5、10、15、20...条记录了。

Python 的源头是常规程序语言,也继承了数组成员位置的概念,相应的代码也较为简洁:

ini 复制代码
stock_2025 = stock[stock['date'].dt.year == 2025].reset_index(drop=True)
selected_stock = stock_2025[stock_2025.index % 5 == 4]

index 是 Dataframe 对象的默认索引,和 SPL 的 #不同,它是从 0 开始的整数。这个索引是个对象,会占用内存空间。

index 是生成 stock 时自动计算产生的,stock 按年份过滤后必须重置 index,对新的集合重新生成索引,否则 stock_2025.index 得到的还是年份过滤前的位置序号。

而 SPL 中的 #是天然序号,不需要额外的存储空间,也没有重置动作,更加简洁轻便。

Python 还提供了 iloc 函数,能直接用位置取数据:

ini 复制代码
stock_2025 = stock[stock['date'].dt.year == 2025].reset_index(drop=True)
selected_stock = stock_2025.iloc[4::5]

iloc4::5 从整数位置 4(也就是第 5 行)开始,每隔 5 行选取一条数据。

SPL 也可以用类似的方式计算:

vbnet 复制代码
stock.select(year(date)==2025).step(5,5)

2. 按条件取位置

条件过滤是常见运算,也就是取得满足一定条件的成员。集合成员有序时,我们有时候还会关心这些满足条件的成员的位置。

比如求某股票上市后,经过多少个交易日涨到了 100 元以上。简单思路是在交易日有序的股票数据中,找出第一个超过 100 元记录的位置序号,就是想要的结果。

SQL 要造个序号,再计算收盘价在 100 元以上记录对应的序号的最小值,思路还是"绕":

sql 复制代码
SELECT MIN(rn) 
FROM (
    SELECT price,
        ROW_NUMBER() OVER ( ORDER BY date ) rn 
    FROM stock)
WHERE price>100

SPL 则轻松完成任务:

scss 复制代码
stock.pselect(price > 100)

pselect 是 SPL 基于天然位置序号提供的定位函数,用于获得满足条件成员的位置,这里就是第一个大于 100 元记录的位置序号。

Python 使用 Dataframe 索引写出的代码:

css 复制代码
stock [stock['price'] > 100].index[0] + 1

相当于常规过滤后取出索引,略有些繁琐。

单纯取得位置经常并不是目标,我们还要用这个位置做进一步的计算。比如:求股价第一次涨到 100 元以上时的涨幅。自然的思路是:找到第一条收盘价大于 100 元的记录位置,取出这个位置和前一个位置上记录的收盘价,两者相减就可以了。

但是,用 SQL 实现就会"绕"得多了:

sql 复制代码
WITH T AS (
    SELECT price,ROW_NUMBER() OVER ( ORDER BY date ) rn,
           price - LAG(price) OVER ( ORDER BY date) rising
    FROM Stock)
SELECT Rising FROM T WHERE NO = ( SELECT MIN(rn) FROM T WHERE price>100 )

这要把每天的涨幅都计算出来,CTE 语法生成的中间表也要被遍历两次。

SPL 可以按照自然思路实现,代码非常简捷:

scss 复制代码
p=stock.pselect(price > 4.5)
stock(p).price-stock(p-1).price

SPL 提供了定位计算函数 calc,可以进一步简化这个代码:

scss 复制代码
stock.calc(stock.pselect(price>100),price-price[-1])

calc 函数针对 stock.pselect(price>100) 得到的位置,计算表达式 price-price-1。SPL 支持跨行计算,price-1 表示上一行的收盘价。

Python 有位置概念,也能写出类似的代码:

css 复制代码
first_index = stock[stock['price'] > 100].index[0]
result =stock.loc[first_index,'price']- stock.loc[first_index - 1,'price']

不过 Python 没有提供类似 calc 的函数,就只能将 loc 写两次。

3. 取最值位置

有序数据集中,最大、最小值记录所在位置也很有业务意义。

比如求股价第一次达到最高价那天的涨幅。简单思路是在有序的股票数据中,找到第一个达到最高价的记录位置,就很容易解题了。

SQL 写起来还是很麻烦:

sql 复制代码
WITH T AS (
    SELECT 
        price,
        ROW_NUMBER() OVER (ORDER BY date) rn,
        price - LAG(price) OVER (ORDER BY date) AS rising
    FROM 
        Stock
),
SELECT rising
FROM T
WHERE price = (SELECT MAX(price) FROM T)
ORDER BY rn
LIMIT 1

窗口函数多,遍历次数多,最后还要先排序再取第一条记录。

SPL 提供了返回最大、最小值出现位置的定位函数 pmax 和 pmin,可以写出非常简捷的代码:

scss 复制代码
stock.calc(stock.pmax(price ), price - price[-1])

pmax 默认是从前向后找第一个最大值位置序号。

最大值成员可能有多个,假设要找所有最大值对应的涨幅,SPL 这样写:

less 复制代码
stock.calc(stock.pmax@a(price ), price - price[-1])

@a 选项可以返回所有这些成员的位置。

Python 的函数 idxmax 也可以返回最大值对应的第一条记录位置:

ini 复制代码
max_idx = stock['price'].idxmax()
increase = stock.loc[max_idx,'price']- stock.loc[max_idx - 1,'price']

像前面代码一样,需要写两次 loc。

如果取所有最大值对应涨幅,Python 的写法就比较啰嗦了:

ini 复制代码
max_price = stock['price'].max()
max_idxs = stock[stock['price'] == max_price].index
increase=max_price - stock.loc[max_idxs-1,'price'].values

Python 没有提供返回所有最大值位置的函数,只能先求出最大值,过滤出最大值所在记录,最后用 index 求这些记录的位置。

小结一下:SQL 对付有序集合的位置计算非常麻烦,要临时造序号,思路很绕、代码繁琐。Python 有整数索引,也可以按位置取成员,比 SQL 要方便很多,但索引不够自动化,常常要重置,位置运算也不够丰富,类似任务的实现方法也不一致,较复杂些的任务仍显啰嗦,理解难度也大。SPL 位置运算很独特,有简洁的天然序号,和丰富的定位函数,可以轻松实现此类任务。

SPL已开源免费,下载试用~

源码地址~

相关推荐
程序员cxuan23 分钟前
速度太快了!本地可以跑 DeepSeek-V4-Flash 了
人工智能·后端·程序员
阿祖zu1 小时前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
不才不才不不才2 小时前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
IT_陈寒2 小时前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
搜狐技术产品小编20232 小时前
告别“黑盒”与误判:如何用“多智能体对抗辩论”重构内容安全审核系统
后端·多agent
DantyWei2 小时前
controller注册及调用时机
后端
edwarddamon3 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
码事漫谈3 小时前
我用 Seed Evolving 做了个 AI 小说写作工具
后端
Raas1003 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway
计算机魔术师4 小时前
传统基础架构 - 微服务
大数据·后端·微服务·架构·开发工具·编程语言