推荐一个写 SQL 的神器

如果你经常需要写大量的 SQL脚本 来进行数据分析工作,那你可能值得拥有这款神器:

https://github.com/taojy123/sqlx

SQLx 意为 SQL Extension,强大的 SQL 语法拓展,目标是打造 "易读易写 方便维护" 的 SQL 脚本。

应用场景

假设有一张商品价目表(product),每天价格变动的商品都会更新报价。

例如,苹果的最新价格为 10 元, 因为苹果最新的一次报价是在 20191211, 当时价格为 10 元。

name(商品名称) price(价格) date(报价日期)
苹果 15 20191208
香蕉 18 20191208
橘子 12 20191208
香蕉 16 20191209
橘子 11 20191209
苹果 11 20191210
橘子 13 20191210
苹果 10 20191211
香蕉 22 20191211
橘子 14 20191212

现在要求通过 sql 统计出 20191212 这天的平均价格 比 20191209 那天涨了多少 ?

正常情况下我们可能会写出这样的 sql

SELECT
    a1.avg_price AS `20191209 平均价格`,
    a2.avg_price AS `20191212 平均价格`,
    (a2.avg_price - a1.avg_price) AS `涨价金额`
FROM
    (
        -- 求出各类别 20191209 前最后一次报价的平均价格
        SELECT
            avg(product.price) AS avg_price
        FROM
            (
                -- 求出各商品在 20191209 前最后一次报价的日期
                SELECT
                    name,
                    max(date) AS max_date
                FROM
                    product
                WHERE
                    date <= '20191209'
                GROUP BY
                    name
            ) AS t1
        LEFT JOIN product
        ON t1.name = product.name AND t1.max_date = product.date
    ) AS a1
LEFT JOIN
    (
        -- 再求出各类别 20191212 前最后一次报价的平均价格
        SELECT
            avg(product.price) AS avg_price
        FROM
            (
                -- 先求出各商品在 20191212 前最后一次报价的日期
                SELECT
                    name,
                    max(date) AS max_date
                FROM
                    product
                WHERE
                    date <= '20191212'
                GROUP BY
                    name
            ) AS t2
        LEFT JOIN product
        ON t2.name = product.name AND t2.max_date = product.date
    ) AS a2
ON true

得到统计结果如下:

20191209 平均价格 20191212 平均价格 涨价金额
14.0000 15.3333 1.3333

传统做法虽然得到的结果是正确的,但同时暴露出以下问题:

  1. 子查询多层嵌套,代码可读性极低
  2. t1 t2 两个子查询内容基本一致,也就说我们要维护两处相同的代码
  3. a1 a2 两个子查询也基本一致,并且其中相同的注释我们要写两遍,感觉太"蠢"了
  4. 这只是个很简单的示例,在实际工作中,针对更复杂的统计需求,代码的复杂度将会以指数形式递增

下面看看如何使用 sqlx 来解决上述问题:

func product_max_date(day)
    -- 子查询: 统计出各个商品在 {day} 前最后一次报价的日期
    (
        SELECT
            name,
            max(date) AS max_date
        FROM
            product
        WHERE
            date <= '{day}'
        GROUP BY
            name
    )
end

func date_avg_price(day):
    -- 子查询: 统计出 {day} 这天各个类别的平均价格
    (
        SELECT
            avg(product.price) AS avg_price
        FROM
            {product_max_date($day)} AS t1
        LEFT JOIN product 
        ON t1.name = product.name AND t1.max_date = product.date
    )
end

SELECT
    a1.avg_price AS `20191209 平均价格`,
    a2.avg_price AS `20191212 平均价格`,
    (a2.avg_price - a1.avg_price) AS `涨价金额`
FROM
    {date_avg_price(20191209)} AS a1
LEFT JOIN 
    {date_avg_price(20191212)} AS a2
ON true

优势非常明显:

  1. 核心代码是一段短小的 SELECT,外加两个子查询的定义就搞定了,代码逻辑清晰,可读性高
  2. a1 a2 使用类似 函数 的概念进行封装,通过传入不同的参数来生成不同的子查询内容
  3. 相同逻辑的代码片段只需要写一遍,大大降低了代码维护的工作量
  4. 使用 sqlx 提供的编译工具或插件,可快速编译成 sql 代码,在数据库中执行结果一致

如何使用

先看一下 sqlx 的基本语法介绍,很简单 5 分钟就看明白学会了。

接下来就开始编写你的 sqlx 脚本吧,保存文件时拓展名设为 .sqlx

然后下载 sqlx 的编译工具,如果你使用 Windows 64位系统 可以直接下载 sqlx.exe 。双击运行,即可将当前目录下的 sqlx 脚本文件一键编译为 sql。

如果你使用 Sublime Text 编辑器,可以搜索下载 Sqlx Builder 插件来使用,更加方便。