## 1 引言

![](https://cdn-images-1.medium.com/max/1600/1*UF_zemLVnvMmksAN5dgZDg.png)

对时序数据的处理有两种方式，如图所示，右边是 SQL，左边是自定义查询语言，也称为 NoSQL，处于中间地带的称为 SQL-LIKE 语言。

本文通过对比 SQL 阵营的 TimescaleDB 与 NoSQL 阵营的 InfluxDB，试图给出一些对比。

## 2 概述

### TimescaleDB

TimescaleDB 完全接受了 SQL 语法，因此几乎没有什么学习门槛，更通过可视化操作优化了使用方式。

### InfluxDB

InfluxDB 创造了一种新的查询语言，这里是 [Flux 文法](https://github.com/influxdata/platform/blob/master/query/docs/SPEC.md).（了解更多文法相关知识，可以移步 [精读《手写 SQL 编译器 - 文法介绍》](https://github.com/dt-fe/weekly/blob/master/65.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%89%8B%E5%86%99%20SQL%20%E7%BC%96%E8%AF%91%E5%99%A8%20-%20%E6%96%87%E6%B3%95%E4%BB%8B%E7%BB%8D%E3%80%8B.md)）

### InfluxDB 为什么创造 Flux 语法

InfluxDB 之所以创造 Flux 语法，而不使用 SQL，主要有两个原因：

1. 更强的查询功能：SQL 无法轻松完成时序查询。
2. 时间序列的查询需要基于流的函数模型，而不是 SQL 的代数模型。

所谓流模型，就类似 JS 函数式编程中类似概念：

```typescript
source.pipe(
  map(x => x + x),
  mergeMap(...),
  filter(...)
)
```

### 更强的查询功能？

InfluxDB 拿下面例子举例：

Flux:

```flux
from(db:"telegraf")
  |> range(start:-1h)
  |> filter(fn: (r) => r._measurement == "foo")
  |> exponentialMovingAverage(size:-10s)
```

SQL:

```sql
select id,
       temp,
       avg(temp) over (partition by group_nr order by time_read) as rolling_avg
from (
  select id,
         temp,
         time_read,
         interval_group,
         id - row_number() over (partition by interval_group order by time_read) as group_nr
  from (
     select id,
            time_read,
            'epoch'::timestamp + '900 seconds'::interval * (extract(epoch from time_read)::int4 / 900) as interval_group,
            temp
     from readings
  ) t1
) t2
order by time_read;
```

虽然看上去 SQL 写法比 Flux 长了不少，但其实 Flux 代码的核心在于实现了自定义函数 `exponentialMovingAverage`，而 PostgreSQL 也有 [创建函数](https://stackoverflow.com/questions/8871426/how-to-calculate-an-exponential-moving-average-on-postgres#8879118) 的能力。

通过 SQL 定义一个自定义函数：

```sql
CREATE OR REPLACE FUNCTION exponential_moving_average_sfunc
(state numeric, next_value numeric, alpha numeric)
RETURNS numeric LANGUAGE SQL AS
$$
SELECT
         CASE
                WHEN state IS NULL THEN next_value
                ELSE alpha * next_value + (1-alpha) * state
         END
$$;
CREATE AGGREGATE exponential_moving_average(numeric, numeric)
(sfunc = exponential_moving_average_sfunc, stype = numeric);
```

之后可以像 Flux 函数一样的调用：

```sql
SELECT time,
       exponential_moving_average(value, 0.5) OVER (ORDER BY time)
FROM telegraph
WHERE measurement = 'foo' and time > now() - '1 hour';
```

可见从函数定义上也和 Flux 打成平手，作者认为既然功能相同，而基于 SQL 的语言学习成本更低，所以不需要创造一个新的语言。

### 关于语法糖与 SQL 标准

作者认为，虽然有观点认为，Flux 的语法糖比 SQL 更简洁，但代码的可维护性并不是行数越少越好，而是是否容易被人类理解。

对于创造一个函数标准可能破坏 SQL 的可移植性，作者认为那也比完全创造一个新语法要强。

### 基于流的函数模型强于 SQL 代数模型？

诚然，从功能角度来看，当然函数模型强于代数模型，因为代数模型只是在描述事物，而不能精准控制执行的每一步。

但我们要弄清楚 SQL 的场景，是通过描述一个无顺序的查询问题，让数据库给出结果。而在查询过程中，数据库可以对 SQL 语句作出一些优化。

反观函数模型，是在用业务代码描述查询请求，这种代码是无法被自动优化的，虽然为用户提供了更底层的控制，但其代价是无法被数据库执行引擎所优化。

如果你更看中查询语言，而不是具体执行逻辑，SQLl 依然是最好的选择。

## 3 总结

之所以制作这一期精读，是为了探索 SQL 与其他查询语言的关系，去理解为什么 SQL 沿用至今。

SQL 与其他函数类查询语言不在一个层面上，如果用语法糖、可操纵性抨击 SQL，只能得出看似正确，实则荒谬的结论。

SQL 是一个查询语言，与普通编程语言相比，它还在上层，最终会转化为关系代数执行，但关系代数会遵循一些等价的转换规律，比如交换律、结合律、过滤条件拆分等等，通过预估每一步的时间开销，将 SQL 执行顺序重新组合，可以提高执行效率。

如果有多个 SQL 同时执行，还可以整合成一个或多个新的 SQL，合并重复的查询请求。

在数据驱动商业的今天，SQL 依然是数据查询最通用的解决方案。

## 4 更多讨论

> 讨论地址是：[精读《SQL vs Flux》 · Issue #96 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/98)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。**
