热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

可编程的SQL是什么样的?

背景如果你使用传统编程语言,比如Python,那么恭喜你,你可

背景

如果你使用传统编程语言,比如Python,那么恭喜你,你可能需要解决大部分你不需要解决的问题,用Python你相当于拿到了零部件,而不是一辆能跑的汽车。你花了大量时间去组装汽车,而不是去操控汽车去抵达自己的目的地。大部分非计算机专业的同学核心要解决的是数据操作问题,无论你是摆地摊,开餐馆,或者在办公室做个小职员,在政府机构做工作,你都需要基本的数据处理能力,这本质上是信息处理能力。 但是在操作数据前,你必须要学习诸如变量,函数,线程,分布式等等各种仅仅和语言自身相关的特性,这就变得很没有必要了。操作数据我们也可以使用 Excel(以及类似的软件),但是Excel有Excel的限制,譬如你各种点点点,还是有点低效的,有很多较为复杂的逻辑也不太好做,数据规模也有限。那什么交互最快,可扩展性最好?语言。你和计算机系统约定好的一个语言,有了语言交流,总是比点点点更高效的。这个语言是啥呢?就是SQL。

但是SQL也有些毛病,首先他最早为了关系型数据库设计的,适合查询而非ETL,但是现在人们慢慢把他扩展到ETL, 流式处理,甚至AI上,他就有点吃力了。 第二个问题是,他是声明式的,导致缺乏可编程性。所谓可编程性是指,我们应该具备创建小型、可理解、可重用的逻辑片段,并且这些逻辑片段还要被测试、被命名、被组织成包,而这些包之后可以用来构造更多有用的逻辑片段,这样的工作流程才是合理又便捷的。更进一步的,这些“高阶”能力应该是可选的,我们总是希望用户一开始去使用最简单的方式来完成手头的工作而不是显摆一些高阶技巧。

所以最后的结论是,我们希望:

  1. 保留SQL的所有原有优势,简洁易懂,上手就可以干活。
  2. 允许用户进阶,提供更多可编程能力,但是以一种SQL Style的方式提供。

保留原有SQL精髓

我们仅仅对SQL做了丢丢调整,在每条SQL 语句结尾增加了一个表名,也就是任何一条SQL语句的结果集都可以命名为一张新的表。

load hive.`raw.stripe_discounts` as discounts;
load hive.`raw.stripe_invoice_items` as invoice_items;
select
invoice_items.*,
case
when discounts.discount_type = 'percent'
then amount * (1.0 - discounts.discount_value::float / 100)
else amount - discounts.discount_value
end as discounted_amount
from invoice_items
left outer join discounts
on invoice_items.customer_id = discounts.customer_id
and invoice_items.invoice_date > discounts.discount_start
and (invoice_items.invoice_date < discounts.discount_end
or discounts.discount_end is null) as joined;
select
id,
invoice_id,
customer_id,
coalesce(discounted_amount, amount) as discounted_amount,
currency,
description,
created_at,
deleted_at
from joined as final;
select * from final as output;

大家看到,每条SQL的执行结果都被取名为一张新表,然后下一条SQL可以引用前面SQL产生的表,相比传统我们需要insert 然后再读取,会简单很多,也更自然,速度更快。而且对于数据处理,我们也无需在一条SQL语句里写复杂的嵌套子查询和Join了,我们可以将SQL展开来书写,校本化,更加易于阅读和使用。

支持更多数据源

传统SQL是假定你在一个数据源中的,因为你只能按库表方式去使用,在普通Web开发里,是你配置的数据库。而在大数据里,一般是数据仓库或者数据湖。 但是随着联邦查询越来越多,越来越普及,我们希望给SQL提供更多的加载和保存多种数据源的能力。我们通过提供load语句来完成。

load excel.`./example-data/excel/hello_world.xlsx`
where header="true"
as hello_world;
select hello from hello_world as output;

在上面的示例可以看到,我们加载了一个excel文件,然后映射成一张表,之后可以用标准的SQL进行处理。
如果要将结果保存到数仓也很简单:

save overwrite hello_word as hive.`tmp.excel_table`;

变量

变量是一个编程语言里,一般你会接触到的第一个概念。我们也给SQL增加了这种能力。比如:

-- It takes effect since the declaration in the same cell.
set world="world";
select "hello ${world}" as title
as output;

在可编程SQL中,变量支持多种类型,诸如sql,shell,conf,defaultParam等等去满足各种需求和场景。下面是一个典型的例子:

set date=`select date_sub(CAST(current_timestamp() as DATE), 1) as dt`
where type="sql";
select "${date}" as dt as output;

后面我们会有更多变量的介绍。

调用外部模块的代码

传统编程语言如Java,Python,他们的生态都是靠第三方模块来提供的。第三方模块会被打包成诸如如Jar ,Pip 然后让其他项目引用。 原生的SQL是很难复用的,所以没有形成类似的机制,更多的是随用随写。 但是随着SQL能力的扩展,在流,在批,在机器学习上的应用越来越多,能写越来越复杂的逻辑,也慢慢有了更多的可复用诉求。

我们通过引入include 关键字,可以引入本项目或者github上的SQL代码。https://github.com/allwefantasy/lib-core 是我们使用可编程SQL写的一个第三方模块。假设我们要引用里面定义的一个UDF 函数 hello,第一步是引入模块:

include lib.`github.com/allwefantasy/lib-core`
where
-- libMirror="gitee.com" and -- 配置代理
-- commit="" and -- 配置commit点
alias="libCore";

第二步就是引入相应的udf包,然后在SQL中使用:

include local.`libCore.udf.hello`;
select hello() as name as output;

是不是很酷?

宏函数

函数是代码复用的基础。几乎任何语言都有函数的概念。我们在SQL中也引入的宏函数的概念。但这个宏函数和 原生的SQL中的函数比如 split, concat 等等是不一样的。他是SQL语言级别的函数。我们来看看示例:

set loadExcel = '''
load excel.`{0}`
where header="true"
as {1}
''';
!loadExcel ./example-data/excel/hello_world.xlsx helloTable;

在这段代码中,

  1. 我们申明了一个变量 loadExcel,并且给他设置了一段代码。
  2. loadExcel 有诸如 {0}, {1}的占位符。这些会被后续调用时的参数动态替换。
  3. 使用功能 ! 将loadExcel变量转化为宏函数进行调用。参数传递类似命令行。

我们也支持命名参数:

set loadExcel = '''
load excel.`${path}`
where header="true"
as ${tableName}
''';
!loadExcel _ -path ./example-data/excel/hello_world.xlsx -tableName helloTable;

原生SQL函数的动态扩展

像传统关系型数据库,几乎无法扩展SQL的内置函数。在Hive/Spark中,通常需要以Jar包形式提供,可能涉及到重启应用,比较繁琐,比较重。 现在,我们把SQL UDF 书写变成和书写SQL一样。 我们来看一个例子:

register ScriptUDF.`` as arrayLast
where lang="scala"
and code='''def apply(a:Seq[String])={
a.last
}'''
and udfType="udf";
select arrayLast(array("a","b")) as lastChar as output;

在上面的代码中,我们通过register语法注册了一个函数叫 arrayLast,功能是拿到数组的最后一个值。 我们使用scala代码书写这段逻辑。之后我们可以立马在SQL中使用功能这个函数。是不是随写随用?

当然,通过模块的能力,你也可以把这些函数集中在一起,然后通过include引入。

分支语法

SQL最大的欠缺就是没有分支语句,这导致了一个啥问题呢?他需要寄生在其他语言之中,利用其他语言的分支语句。现在,我们原生的给SQL 加上了这个能力。 看如下代码:

set a = "wow,jack";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;

在分支语句中的条件表达式中,你可以使用一切内置、或者我们扩展的原生函数。比如在上面的例子里,我们在if 语句中使用了 split函数。
还有一个大家用得非常多的场景,就是我先查一张表,根据条件决定接着执行什么样的逻辑。这个有了分支语法以后也会变得很简单,比如:

select 1 as a as mockTable;
set b_count=`select count(*) from mockTable ` where type="sql" and mode="runtime";
!if ''':b_count > 11 ''';

select 1 as a from b as final_table;
!else;
select 2 as a from b as final_table;
!fi;
select * from final_table as output;

在上面的代码示例中,我们先查询 mockTable里有多少数据,如果大于11条,执行 A语句,否则执行B 语句,执行完成后的结果继续被后面的SQL 处理。

机器学习(内置算法)

SQL表达机器学习其实是比较困难的。但是别忘了我们是可编程的SQL呀。我们来看看示例,第一步我们准备一些数据:

include project.`./src/common/mock_data.mlsql`;
-- create mock/validate/test dataset.
select vec_dense(features) as features, label as label from mock_data as mock_data;
select * from mock_data as mock_data_validate;
select * from mock_data as mock_data_test;

接着我们就可以引入一个内置的算法来完成模型的训练。

train mock_data as RandomForest.`/tmp/models/randomforest` where
keepVersion="true"
and evaluateTable="mock_data_validate"
and `fitParam.0.labelCol`="label"
and `fitParam.0.featuresCol`="features"
and `fitParam.0.maxDepth`="2"
;

这个语句表达的含义是什么呢? 对mock_data表的数据使用RandomForest进行训练,训练时的参数来自where语句中,训练后的模型保存在路径/tmp/models/randomforest 里。是不是非常naive!

之后你马上可以进行批量预测:

predict mock_data_test as RandomForest.`/tmp/models/randomforest` as predicted_table;

或者将模型注册成UDF函数,使用Select语句进行预测:

register RandomForest.`/tmp/models/randomforest` as model_predict;
select vec_array(model_predict(features)) as predicted_value from mock_data as output;

Python脚本支持

在可编程SQL里, SQL是一等公民, Python只是一些字符串片段。下面是一段示例代码:

select 1 as a as mockTable;
!python conf "schema=st(field(a,long))";
run command as Ray.`` where
inputTable="mockTable"
and outputTable="newMockTable"
and code='''
from pyjava.api.mlsql import RayContext
ray_context = RayContext.connect(globals(),None)
newrows = []
for row in ray_context.collect():
row["a"] = 2
newrows.append(row)

context.build_result(newrows)
''';
select * from newMockTable as output;

这段代码,我们使用功能Ray 模块执行Python脚本,这段Python脚本会对 mockTable表加工,把a字段从1修改为2,然后处理的结果可以继续被SQL处理。是不是很酷?随时随地写Python处理数据或者做机器学习,数据获取和加工则是标准的SQL来完成。

插件

可编程SQL无论语法还是内核功能应该是可以扩展的。 比如我需要一个可以产生测试数据的功能。我只要执行如下指令就可以安装具有这个功能的插件:

!plugin app add - "mlsql-mllib-3.0";

然后我就获得了一个叫SampleDatasetExt的工具,他可以产生大量的测试数据:

run command as SampleDatasetExt.``
where columns="id,features,label"
and size="100000"
and featuresSize="100"
and labelSize="2"
as mockData;
select * from mockData as output;

在上面的示例代码中,我们通过SampleDatasetExt 产生了一个具有三列的表,表的记录数为100000, 其中feature字段数组大小为100, label字段的数组大小为2。之后我们可以使用select语句进行查询进一步加工。

更多编程小trick

比如下面一段代码在实际生产里是常态:

select SUM( case when `id` is null or `id`='' then 1 else 0 end ) as id,
SUM( case when `diagnosis` is null or `diagnosis`='' then 1 else 0 end ) as diagnosis,
SUM( case when `radius_mean` is null or `radius_mean`='' then 1 else 0 end ) as radius_mean,
SUM( case when `texture_mean` is null or `texture_mean`='' then 1 else 0 end ) as texture_mean,
SUM( case when `perimeter_mean` is null or `perimeter_mean`='' then 1 else 0 end ) as perimeter_mean,
SUM( case when `area_mean` is null or `area_mean`='' then 1 else 0 end ) as area_mean,
SUM( case when `smoothness_mean` is null or `smoothness_mean`='' then 1 else 0 end ) as smoothness_mean,
SUM( case when `compactness_mean` is null or `compactness_mean`='' then 1 else 0 end ) as compactness_mean,
SUM( case when `concavity_mean` is null or `concavity_mean`='' then 1 else 0 end ) as concavity_mean,
SUM( case when `concave points_mean` is null or `concave points_mean`='' then 1 else 0 end ) as concave_points_mean,
SUM( case when `symmetry_mean` is null or `symmetry_mean`='' then 1 else 0 end ) as symmetry_mean,
SUM( case when `fractal_dimension_mean` is null or `fractal_dimension_mean`='' then 1 else 0 end ) as fractal_dimension_mean,
SUM( case when `radius_se` is null or `radius_se`='' then 1 else 0 end ) as radius_se,
SUM( case when `texture_se` is null or `texture_se`='' then 1 else 0 end ) as texture_se,
SUM( case when `perimeter_se` is null or `perimeter_se`='' then 1 else 0 end ) as perimeter_se,
SUM( case when `area_se` is null or `area_se`='' then 1 else 0 end ) as area_se,
SUM( case when `smoothness_se` is null or `smoothness_se`='' then 1 else 0 end ) as smoothness_se,
SUM( case when `compactness_se` is null or `compactness_se`='' then 1 else 0 end ) as compactness_se,
SUM( case when `concavity_se` is null or `concavity_se`='' then 1 else 0 end ) as concavity_se,
SUM( case when `concave points_se` is null or `concave points_se`='' then 1 else 0 end ) as concave_points_se,
SUM( case when `symmetry_se` is null or `symmetry_se`='' then 1 else 0 end ) as symmetry_se,
SUM( case when `fractal_dimension_se` is null or `fractal_dimension_se`='' then 1 else 0 end ) as fractal_dimension_se,
SUM( case when `radius_worst` is null or `radius_worst`='' then 1 else 0 end ) as radius_worst,
SUM( case when `texture_worst` is null or `texture_worst`='' then 1 else 0 end ) as texture_worst,
SUM( case when `perimeter_worst` is null or `perimeter_worst`='' then 1 else 0 end ) as perimeter_worst,
SUM( case when `area_worst` is null or `area_worst`='' then 1 else 0 end ) as area_worst,
SUM( case when `smoothness_worst` is null or `smoothness_worst`='' then 1 else 0 end ) as smoothness_worst,
SUM( case when `compactness_worst` is null or `compactness_worst`='' then 1 else 0 end ) as compactness_worst,
SUM( case when `concavity_worst` is null or `concavity_worst`='' then 1 else 0 end ) as concavity_worst,
SUM( case when `concave points_worst` is null or `concave points_worst`='' then 1 else 0 end ) as concave_points_worst,
SUM( case when `symmetry_worst` is null or `symmetry_worst`='' then 1 else 0 end ) as symmetry_worst,
SUM( case when `fractal_dimension_worst` is null or `fractal_dimension_worst`='' then 1 else 0 end ) as fractal_dimension_worst,
SUM( case when `_c32` is null or `_c32`='' then 1 else 0 end ) as _c32
from data as data_id;

写的手累?那有么有办法简化呢?当然有啦。 我们毕竟是可编程是SQL呀。

一个有意思的解决方法如下:

select
#set($colums=["id","diagnosis","fractal_dimension_worst"])
#foreach( $column in $colums )
SUM( case when `$column` is null or `$column`='' then 1 else 0 end ) as $column,
#end
1 as a from newTable as output;

我们可以使用内置的 #foreach 循环。先通过set设置所有字段名称,然后通过foreach循环来生成sum语句。

这就完了?就如同茴字有好多写法,我们还有其他的玩法。

set sum_tpl = '''
SUM( case when `{0}` is null or `{0}`='' then 1 else 0 end ) as {0}
''';
select ${template.get("sum_tpl","diagnosis")},
${template.get("sum_tpl","radius_mean")},
${template.get("sum_tpl","texture_mean")},
from data as output;

我们可以通过set 进行模板设置,然后在sql语句里通过template.get( 语句进行模板渲染。 对于一个很复杂的SQL 语句,里面可能存在多个类似sum /case when的重复语句,那么我们就可以使用这种方式了。而且可以做到一处修改,处处生效。不然万一你 sum里的1要改成2,那可是要改好几十个语句的。

恩,除了这些,还有非常多的好玩的玩法等待你的挖掘,SQL 再也不Boring了。

不是最后的最后

可以看到,我们给原生SQL扩展了变量,函数,多数据源支持,第三方模块,原生SQL ,原生函数动态扩展,分支语法,机器学习,python脚本支持,插件等等诸多功能。就像TypeScript给Javascript的增强一样,大家也可以只用最基础的SQL语法。但是一旦你有需要,你就可以使用更多高阶功能满足自己的诉求。

最后

这个可编程的SQL是还在梦想中么?当然不是! 它就在这里: https://mlsql.tech 我们提供了桌面版和在线试用版本。还不快来感受下。

真的最后了

MLSQL目前支持Web版,桌面版,包括Script,Notebook等多种交互模式。参考 MLSQL 2.1.0版本的技术白皮书

现在,让我们看一段赏心悦目的MLSQL代码

  1. 下载图片tar包,并且解压
  2. 设置python环境
  3. 加载图片目录为表
  4. 使用python进行分布式图片处理
  5. 对文件名进行处理
  6. 将表以二进制图片包保存到对象存储目录中

在这里插入图片描述


版权声明:本文为allwefantasy原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/allwefantasy/article/details/120996679
推荐阅读
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
author-avatar
guoqiuping98_376
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有