我在 QuestDB 上运行此查询,其中我得到最大、最小和平均价格:
select approx_percentile(price, 0.5), max(price), min(price), avg(price)
from trades
where symbol = 'BTC-USDT' and timestamp in yesterday();
这工作正常,但现在我想计算中位数。我检查了文档,找不到任何相关函数,但由于中位数相当于第 50 个百分位数,而且我看到 QuestDB 支持
approx_percentile
,我尝试了这个:
select approx_percentile(price, 0.5,1), approx_percentile(price, 0.5,5), max(price), min(price), avg(price)
from trades
where symbol = 'BTC-USDT' and timestamp in yesterday();
它工作得很好(不同的精度值给出了不同的近似值,但这是可以预料的)。如果表中存在任何负值,我的问题就会出现,因为我收到此错误:
直方图超出范围,当前覆盖范围 [6.668014432879854E240, 1.3656093558537942E244) 无法扩展任何 更远。原因:[-1]无法记录负值
解决方法可以是通过将数据集上的最低价格添加到每个其他价格来添加值,然后计算百分位数,然后通过减去 min_price 向后移动来移动值。我尝试过(下面的查询以供参考)并且它有效,但这很糟糕并且会导致非直观的查询。
不知道大家有没有更好的建议。
WITH
min_price AS (
select ABS(min(price)) as min_price from trades
where symbol = 'BTC-USDT' and timestamp in yesterday()
),
shifted_data AS (
SELECT price + min_price AS shifted_price
FROM trades cross join min_price
where symbol = 'BTC-USDT' and timestamp in yesterday()
)
SELECT approx_percentile(shifted_price, 0.5, 5) - min_price AS shifted_median
FROM shifted_data cross join min_price;
目前 QuestDB 没有均值函数,并且 approx_percentile 实现基于 HdrHistogram 库,作为无法处理负值的权衡,该库非常有效。虽然 QuestDB 核心工程师在 Slack 频道上确认这个问题将在未来得到解决,但他们无法提供任何截止日期。
作为另一种解决方法,我们可以进行稍微简单的查询。数据集的平均值是位于正中间的那个值,其前后的行数相同。有了这些信息,如果我们可以按目标列对数据集进行排序,计算总行数,然后获取精确中间位置的行的值,我们就得到了中位数。
with prices_and_stats AS (
select price, row_number OVER(order by price) as row_num, count(price) OVER() as total_rows
from trades
where symbol = 'BTC-USDT' and timestamp in yesterday()
)
SELECT price from prices_and_stats where row_num::double IN (
(total_rows + 1) / 2, -- For odd rows
(total_rows + 2) / 2 -- For even rows
);
查询使用
row_number
和 count
窗口函数,因此我们可以在单个查询中获取两个值(与第一个解决方法相反,它必须首先找到最小值,然后交叉连接),然后在主查询我们只需要找到哪一行是中间行。我们将总行数 + 1(或偶数行加上 2)除以二,然后我们得到该位置的行。
这并不理想,但在 QuestDB 支持带有负值的中位数或百分位数之前,这就能达到目的并且性能相当好。