我与SQL的纠葛好几年前就开始了,在不断炒冷饭的过程中终于接了几个不错的项目,把冷饭变成了温饭。今天就借此机会,与大家分享一下自己在项目中的心路历程,从中学习到的SQL在数据工程中的运用,代码分享,代码设计对运算时间以及数据库容量的影响,以及一些学习技巧。

一点小背景数据工程(data engineering), 不同行业定义略有差异,界限也比较模糊,在数据开发领域,运用比较广的是对于海量数据进行预处理 (比如合并不同的表格数据,对不同列的数值进行合并增减运算),根据不同的数据需求把商业逻辑转换成sql语言,从而对原始数据进行提取转换,生成一个或多个符合要求的数据表,为下一步的数据分析建模以及数据可视化奠定基础。这一系列的预处理需求以背后的商业逻辑为依托,可以通过不同的途径达到。如果从写代码角度,比较常见的有sql和python, 当然现在市面上也出现了很多强大的数据预处理软件,比如Alteryx, Tableau prep, 具体运用哪一种还是要取决于公司的数据基础设施架构,预算以及具体需求。那些年,我与SQL的纠葛在SQL学习的路上,你是否有和我类似的心路历程呢?这部分有点唠叨,只对干货有兴趣的小伙伴们请自行跳转到下一趴。SQL,最早接触还是在学校实习期间。当时学习方法可谓东一榔头西一棒槌,网上大咖的帖子都说要以数据库的书作为基础,于是我便追随前人的经验总结,看了些讲数据库的书,以建立自己对于数据库理解的框架。之后同事推荐了我一些在线SQL小习题的练习,我便开启了每日刷几道题的日子。怎么说呢,这样的流程让我对数据库有了基本的概念,教会了我SQL是什么,然后就是select from where这样的基本语句。但是题一难,我就写不对,题一简单,我就总写select from join where练习打字速度。没多久,就到达了“瓶颈”,知道自己水平就是网上小习题的中低水平,但又不知道该怎么提高,再之后,这方面的练习就搁置了。入职之后,因项目原因,两年时间做ppt做的炉火纯青两眼发光,并没有机会接触任何data engineering - sql的项目。再后来有零星的十万火急的sql活儿找,自己的水平又无法短时间上手去处理成百上千行sql与背后复杂的商业逻辑,于是也失去了一些机会。就这么几轮下来,我对SQL可以说是“闻风丧胆”,只要听说有大型数据预处理且需要“高超”SQL技能的活儿,我就寝食难安。想上这样的项目去锻炼,但一些项目需要一个人单枪匹马与客户合作,对于没有任何实战经验的我,感觉自己是特别地不堪一击。

今年幸运地接手了几个SQL项目,有用少量数据模拟一些计算流程的;也有比较复杂一些的,比如帮助机器学习的建模预处理一些数据的,这方面数据量会大一些,平均一张表几百个G的数据,几千万行。还记得第一个SQL项目接手时压力比较大,虽然大家都说sql非常简单,但当时毕竟不太懂也没什么经验,周末打电话给同学进行sql云请教,吃饭就看教学视频,睡觉关灯就想逻辑,想不通就开灯查谷歌,一天对着电脑白天黑夜的弄,被朋友戏称sql中毒。如果你对SQL有一些了解,可能会想,不是都说SQL上手很快吗?不就是select from join where这样一些语句来回倒腾吗?为什么还有这么多内心戏给自己加?哈哈,我最开始也是这么想的。但既然数据工程师 (data engineer) 可以作为一个工种存在,而SQL又是很多数据工程师的基础语言,那么肯定是有点儿道道。走过几个项目现在回头看,SQL基础的代码句式(syntax)上手是很快,但在真实环境中,对于商业逻辑的理解,对于背后数据库的理解,同一结果不同逻辑对于运算时间的影响,这些都是需要计入考虑的。下面,我就详细分享一下自己对于SQL的一步步理解,以及一些我觉得有用的技巧。

Ready? Go!数据预处理中两个需要了解的sql概念提到数据预处理,首先必须来说说数据预处理的环境。我们以传统关系型数据库为例:一般其后端都会有一个服务器(server),然后前端有可以方便编写代码处理数据的界面,这样我们可以通过前端知道有哪些数据在这个服务器中,做数据预处理与提取。例如SQL server的常见前端为SQL server management studio

MySQL的前端为MySQL workbench

如果小伙伴们想自行在本地计算机上练习,那么下载一个server, 再下载一个前端即可。提到sql在数据预处理中的运用,存储过程(store procedure) 是一个比较重要的概念。简单来说,store procedure就像行李箱,或者行李箱中一个个整理袋,它把很多散落的sql语句变成一个个包裹,系统的有逻辑地整理在一起。这样在数据预处理的时候,我们就不需要东拿一个sql语句,西拿一个sql语句,而是直接拎出打包好的“袋子", 或者直接推出”行李箱“,输入一行代码(如下),放在后台去运行,这样处理数据的后台就可以按照我们整理袋中的语句顺序做自动处理。

execute store_procedure_name;

还是有点神秘?那么这个行李箱或者整理袋长什么样呢?

其实这个整理袋有它固定的首尾句式,就像整理袋行李箱有外壳一样。不同的操作环境这个外壳长的不一样。比如:在sql server里,这个外壳是这样的:

USE [Database_name]GOSET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE PROC store_procedure_name AS/*这里是正文,请输入任意你想打包的sql语句 */GO

在MySQL中,这个外壳是这样的:

USE [Database_name]DELIMITER $$CREATE PROCEDURE store_procedure_name ()BEGIN/*这里是正文,请输入任意你想打包的sql语句 */END$$DELIMITER ;

这个外壳还可以定制,在之前外壳的基础上加一些小零部件就可以了。比如我们想让这个store procedure在运行的时候,如果有错误,返回一个表格记录错误名称,那么可以在上面的外壳基础上加一个error handler,以mysql为例:

USE [Database_name]DELIMITER $$CREATE PROCEDURE store_procedure_name ()BEGIN/*下面这部分就叫 error handler */DECLARE exit handler for SQLEXCEPTION BEGIN  GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE,    @errno = MYSQL_ERRNO, @text = MESSAGE_TEXT;  SET @full_error = CONCAT("ERROR ", @errno, " (", @sqlstate, "): ", @text);  SELECT @full_error; END;/*这里是正文,请输入任意你想打包的sql语句 */END$$DELIMITER ;

其实不要被上面那些代码的样子吓到,都是固定句式,网上复制粘贴,重复利用的。

那么,怎么保存这个store procedure呢?很简单,保留外壳,加上内容,打开sql studio/workbench/其它前端,ctrl + A 全选我们编写的代码,点击前端界面的运行键(execute),刷新一下前端界面左边store procedure folder, 就可以啦!工作中经常使用的sql代码句式(syntax)说完了行李外壳,那让我们一起来看看行李里面的内容吧!不同环境中SQL语句的写法会有不同,但万变不离其宗。这里我们以sql server为例。

在数据预处理中,我经常遇到以下几种类型的任务:

# 任务类型 经常运用到的sql语言
(以sql server syntax为例)
1 原始表格的导入与清理 ltrim(rtrim), cast, convert
2 多表格合并 left join, inner join etc.
3 根据某些运算逻辑额外加列或者赋值 case when
4 基本的运算 isnull, nullif, datediff(month, start date, end date), getdate(), year()
5 行转列或列转行 pivot / unpivot
6 定义变量以方便携带进行后续运算 declare @variable
7 获得唯一id下的唯一行值 ROW_NUMBER() OVER(PARTITIONBY [ID] ORDER BY [date] DESC as rk
8 合并同一id下不同列的值 stuff () for xml path

下面我们逐一进行一下解释:task 1 - 数据格式的清理数据预处理有的时候会从原始数据开始,那么也就意味着每一列的数据格式或者数据本身不符合我们接下来的运算规则。例如有一些数值开头或结尾有额外的空格;本来某一列是数值,但那一列的数据类型却是文本;数据有的时候是空白格,有的时候是null value。如果不前期清理干净,诸如此类这样那样的差别会导致数据跨表合并的失败,或者运算失败。如果我们的处理是从原始数据开始,那么保险起见,还是要过一遍清理流程,将数据类型或者数据值变的统一。ltrim(rtrim) 意在去掉数据开头和结尾的额外空白,那么利用case when我们可以对新建的这列 [market price final] 进行重新赋值,将所有空白格变成null value, 有利于后续运算及建模,convert/cast可以改变数据类型,方便后续运算。

select id,case when ltrim(rtrim([market price]))='' then NULL else convert(float,ltrim(rtrim([market price]))) end as [market price final]into #temporary_tablefrom table_1

task 2 - joinleft join/inner join等一系列join语句,是跨表合并的常见操作。
syntax就不多说了,基本属于学习sql前一两步就会接触到的语句。在大型的数据转换中,我运用left join稍多一些,即先得到一个数据表作为基准,再left join一些额外的列,得到想要的数据。这样做的好处是比较方便查错,因为真实环境中的数据比较复杂,例如公司的财报数据,同一公司同一年可能会有多份财报,每一份财报可能有多个版本,每个版本的币种也会有不同,导致一个公司有多行数值出现。如果我们一个公司一年内只想要一行财报数据,那么就说明,如果left join的基准表有100行,我们left join之后也只能有100行,如果多于100行,那么可能某处逻辑不严密,就需要回去查错。task 3在task 1那里说了,那么下面直接来说 task 4 - 运算isnull 或者 nullif常见于null value与正常数值的转换。在数据分析中,0和null value代表不同的意思,可能需要互相转换或者区别对待。举个例子,如果我们想把null value 全部转换成0, 那么则需要

select ISNULL(NULL, 0);

在表格中做一些加减乘除法,一些单元格可能因为数据缺失的问题,其数值是0,不能作为除法的分母,则我们要把分母的0转换为null value。如果不在分母写nullif, 很容易出现 ’divide by zero error encountered‘ 的错误提示。

select [product price 2020]/NULLIF([product price 2019],0)

除了对于数字的运算,对于日期的运算也是数据预处理中常见的,如果想计算两个日期之间的差,那么可以用datediff();如果想得到今天的日期,则用getdate(), 如果想得到某个日期的所在年份,可以用year()。task 5, pivot and unpivot列转行行转列的操作有时候也会用到,我遇到的需要行列转换的情况一般两种:

  • 原始数据在存储的时候把数据的维度存在一列,把数据的值存在另一列,数据维度可能很不同,数据值的单位可能也有差异。这样的原始数据不适合做运算

    原始是这样:

    公司ID 数据维度 数据值
    001 产品A的单价 10
    001 产品A的利润 8
    001 产品A的成本 2

    我们就需要转换成这样(pivot)

    公司ID 产品A单价 产品A数量 产品A成本
    001 10 100 2
  • 最后的数据结果需要做数据可视化,有的时候把数据维度储存在一列,数据值储存在另一列,更适合数据可视化的软件(比如这样适合Tableau做筛选器filter)

    还是上面那个例子,如果我们想在可视化的时候把单价利润和成本做成filter, 那么把这三个维度放在一列,会更适合后面可视化的进行。

    如果原始是这样的

    公司ID 产品A单价 产品A数量 产品A成本
    001 10 100 2

    我们就需要转换成这样(unpivot)

    公司ID 数据维度 数据值
    001 产品A的单价 10
    001 产品A的利润 8
    001 产品A的成本 2

针对第一种情况,我们需要pivot

select piv.* from (select company_id,data_metrics_name,data_metrics_value from table1) srcpivot(sum(data_metrics_value) for data_metrics_name in ([unit price],[profit],[cost])) piv

针对第二种情况,我们需要unpivot

select company_id    ,data_metrics_name    ,data_metrics_valuefrom table1unpivot(data_metrics_value  for data_metrics_name in   ([unit price],[profit],[cost])) unpiv;

task 6 - 变量

定义变量的syntax其实并不复杂,比如下面的例子,就是定义了一个变量a, 使它的数值为表格1总行数的10倍。

这样做的好处是,在接下来的运算中,我们可以在整个store procedure中一直携带变量a运算,不需要一遍一遍的重复 (select count(*) from table_1)*10这个步骤

  declare @variable_a int  set @variable_a = (select count(*) from table_1)*10

task7 - 行数值

row_number这个公式,目前我在工作中常用它来选择某一维度下的单一值。

举个最简化的例子,一个公司曾经多次改名,那么在数据库中同一公司id下可能会有多个公司名称,而我们需要选择一个唯一的公司名,我们可以把每个公司的多种公司名排序,赋予行数值12345,之后选择行数值为1的那一行作为公司名称。

select t.* from   (select company_id, company_name,  ROW_NUMBER() OVER(PARTITIONBY [company_id] ORDER BY [company_name] DESC) as row_num)where t.row_num =1

上述简化的例子只是为了说明row_number的逻辑,在简单的情况下,用row_number可能会把简单问题复杂化,但是如果数据维度一多,利用赋予行值的方法,利用某些逻辑去寻找单一值,不失为一种好用的方法。

task8 - 数值的合并之stuff()

这个公式主要用于把同一id下的不同数值合并到一个单元格,进行后续运算。

举个例子,一个公司横跨好几个行业,把这些行业合并到一个单元格会更有利于后续数据运算或者可视化。

原始数据是这样的

company_id industry

001

零售
001 电商

我们想变成这样

company_id combined_industry
001 零售,电商

于是就需要

select [company_id],  stuff((SELECT distinct ', ' + cast(industry as varchar(100))    -- combine all the industry of a company to a single cell           FROM table_1 t2           where t2.[company_id] = t1.[company_id]           FOR XML PATH('')),1,1,'') as combined_industry  from table_1 t1  group by [company_id];

数据量上来后,数据预处理的速度以及数据量的力量不容忽视呀,看了上面的总结,是不是把sql常用语句练熟就万事大吉了呢?现实就是:

我在项目中遇到的比较棘手的问题反而不是找不到对应的syntax去写sql(谷歌或者百度可以解决大部分syntax问题), 而是忽视了数据量变大之后,其对sql语句运算时间以及数据库(包括临时数据库)容量的挑战。有那么连续的一周,每段sql都要运行几十分钟,当它们组合成一个store procedure, 就是至少几个小时的运算时间。当我满怀欣喜的改完逻辑,手抖的按下运行键,一会儿看看后台,一会儿看看后台,几个小时甚至十几小时后,接到一个error message

而且每次error不重样的心情是崩溃的

对不起 - DB provider for linked server 002 returned message - query timeout expired对不起 - could not allocate a new page for database TEMPDB because of insufficient disk space in filegroup 'DEFAULT'对不起- could not allocate a new page for database 'database' because of insufficient disk space in filegroup 'PRIMARY'

当我们看到有关磁盘空间(disk space)的错误信息,大家的第一反应是否和曾经的我类似?都上升到磁盘空间的高度了,那估计是IT/数据库管理员的锅。

我们不排除有这样的可能性,比如数据库本身设计时容量不够,或者remote server一些设置需要调整。但是

也有可能是我们自己的锅 —— sql语句在设计的时候忽略了运算时间和数据库容量这些问题。比如,用A方式写sql和B方式写sql,都可以拿到最后想要的数据,得到想要的结果,在数据量小的时候,A和B在运算时可能是几秒的差别;但数据量变大后,A方式和B方式运算时间可能就会有八九个小时的差距,如果中间带入大量的临时表格,很可能把临时数据库(sql server一般为500G)填满,导致整个store procedure运转失败。运算时间之remote server什么叫remote server呢?比如我们自己sql数据库的环境是在server 001, 然后我们要去拿 server 002的数据,server 002就叫remote server。query remote server的数据是极其费时间的,当数据量有几千万行+, 如果在remote server上做大量的运算,少说几个小时就过去了。如果我们运算过程中涉及到的在remote server的table比较少,那么就不建议频繁的query remote server的内容。否则就是

一个比较好的解决方法是第一步就把remote server那张表格以select *的方式放入我们自己server的临时表格(temporary table)中,然后在本地利用新建立的临时表格进行运算。以千万行数据为基准,我遇到过可以节省75-80%时间的状况。试想,原本两小时的query不到半小时就可以跑完,是不是很爽呢。运算时间之逻辑上减少中间步骤携带的数据量比如在写sql语句前,不要着急下键盘,而是根据商业逻辑分析一下每一块需要的数据与逻辑是怎样的。有一些条件如果可以在语句初始就限制,那么对于运算时间就会有质的飞越。论如何在本地电脑上闭关修炼经历了各种SQL学习道路上的来来回回,个人感觉SQL syntax只是达到数据预处理结果的手段与途径,中间最重要的还是如何根据商业逻辑去设计运算的逻辑,让数据开发预处理的过程更准确更高效。分享一些实践后自觉行之有效的方法:

  • 首先了解数据库的基本概念(我看的是database management system这本书,网上也有很多很好的帖子资料,这里就不多赘述啦)

  • 选择你觉得合适的sql种类,比如sql server, mysql, postgre sql等等,然后上网通过小习题 (online quiz) 以熟练基本的syntax。我之前有用 SQL exercise (www.sql-ex.ru), 我也有朋友推荐 Leetcode,找到适合自己的就好啦

  • 在本地电脑上,从建立server+前端开始,设立自己的工作环境

  • 在网上找一些开源数据,下载到本地电脑,从把表格导入到数据库开始实践数据格式转化与清理

  • 根据自己找到的数据,创造一些合理的需求或者运算逻辑,在自己设立的本地环境下练习 select from join case when等等语句

  • 如果你会其它的语言,比如R,Python,SAS等, 其实可以用其它语言中做过的数据预处理为基准,全面在SQL中模仿做出相同的结果,谷歌+百度,一定能给你一个满意的答案

最后的最后,感谢大家这么有耐心的看完全篇。笔者其实觉得这篇文章有点儿长,但好像拆成两篇又有点儿少 =o=

希望此篇可以让你对SQL在数据工程中的运用有新的了解 :)

我也会持续不定期更新自己学习上的心得。我们一起加油!

*本文插图除封面为原创外,均来源于谷歌,如有版权冒犯,还请火速联系作者删除,多多见谅


如果你喜欢这篇文章,欢迎转发与更多人分享

获取更多感悟,欢迎点击标题下[蓝色微信名]

或扫码关注,随时交流

南沐记,微信号:nanmu_ji

sql isnull怎么没用_SQL语言在数据工程(Data Engineering)中的运用(一)相关推荐

  1. as cast float server sql_SQL语言在数据工程(Data Engineering)中的运用(一)

    我与SQL的纠葛好几年前就开始了,在不断炒冷饭的过程中终于接了几个不错的项目,把冷饭变成了温饭.今天就借此机会,与大家分享一下自己在项目中的心路历程,从中学习到的SQL在数据工程中的运用,代码分享,代 ...

  2. R语言data.table导入数据实战:data.table中编写函数并使用SD数据对象

    R语言data.table导入数据实战:data.table中编写函数并使用SD数据对象 目录 R语言data.table导入数据实战:data.table中编写函数并使用SD数据对象 #data.t ...

  3. asp sql ip地址排序_SQL语言基础

    关系数据库的标准语言----------结构化查询语言(Structured Query Language),SQL语言是介于关系代数 和元组演算 之间的一种语言. 一.历史 1986年10月,美国国 ...

  4. R语言基础——数据框(data frame)

    数据框(data frame)   数据框是一种矩阵形式的数据,但数据框中各列可以是不同类型的数据.数据框每列是一个变量,每行是一个观测.数据框可以看成是矩阵的推广,也可看作一种特殊的列表对象,很多高 ...

  5. C语言实现数据文件怎么找,急求如何将下列C语言程序数据存储到文件中?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 求如何改动才能将下列程序的存储输入或输出数据(或两者一起)到指定的文件(或运行时直接创立一个文件)如Arrangement中. #include int ...

  6. c语言程序怎么颠倒数据,急求如何将下列C语言程序数据存储到文件中?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 求如何改动才能将下列程序的存储输入或输出数据(或两者一起)到指定的文件(或运行时直接创立一个文件)如Arrangement中. #include int ...

  7. 如何保存文件为c语言格式,急求如何将下列C语言程序数据存储到文件中?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 求如何改动才能将下列程序的存储输入或输出数据(或两者一起)到指定的文件(或运行时直接创立一个文件)如Arrangement中. #include int ...

  8. c语言保存文件格式如何改回来,急求如何将下列C语言程序数据存储到文件中?...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 求如何改动才能将下列程序的存储输入或输出数据(或两者一起)到指定的文件(或运行时直接创立一个文件)如Arrangement中. #include int ...

  9. c语言中文件如何插入数据,急求如何将下列C语言程序数据存储到文件中?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 求如何改动才能将下列程序的存储输入或输出数据(或两者一起)到指定的文件(或运行时直接创立一个文件)如Arrangement中. #include int ...

最新文章

  1. 多分类任务的混淆矩阵
  2. linux:ubantu中pycharm专业版安装
  3. 【分解质因数】【树状数组】【快速幂】codeforces 2014 ACM-ICPC Vietnam National Second Round E. ACM...
  4. VSS(2005)中如何强行签入文件
  5. appium-在页面点击一下处理(一般处理提示蒙层)
  6. 重新学习的HTTP协议
  7. 结构化程序goto语句_C ++ goto语句| 查找输出程序| 套装1
  8. 计算机运行命令定时关机,电脑定时关机命令 使用系统命令定时关机 - 云骑士一键重装系统...
  9. 免费的中医电子病历系统软件
  10. Windows系统常用网络命令详解及命令示例(全)
  11. 游戏建模教程:肌肉建模丨人体比例及肌肉骨骼介绍
  12. Unity【Live Capture】- 关于人脸捕捉的解决方案(一)
  13. ios13.3 降级13.2.3绕id
  14. 什么软件可以编辑PDF文件?编辑工具分享
  15. HTML+CSS美食静态网页设计——简单牛排美食餐饮(9个页面)公司网站模板企业网站实现
  16. 瀚云轩玉石系统瀚云轩玉石竞拍系统开发玩法与开发源码分享
  17. Python转盘游戏
  18. vue使用v-for循环ABC...英文字母
  19. 上海自考计算机本科考哪些专业吗,上海自考本科有哪些专业
  20. Android最强保活黑科技的最强技术实现!

热门文章

  1. java enumerator_简单介绍java Enumeration
  2. java基盘JavaScript_JavaWeb学习:SSH整合(无障碍整合)
  3. java渐变色字体生成器_java阴影文字效果怎么做?渐变的怎么做?
  4. 三星s轻奢android+p,三星Galaxy S轻奢版5月21日将至:骁龙660处理器+安卓8.0
  5. java请求怎么获取token,如何获取变量token的值
  6. cmyk图像处理matlab,数字图像处理及MATLAB实现 全套课件.pptx
  7. fullcalendar 显示的时间间隔只有四十五分钟_Linux命令行监控程序,还能实时高亮显示差异,我就选它了...
  8. nvm卸载node_nvm-node版本管理工具
  9. 关于mysql设置varchar 字段的默认值''和null的区别,以及varchar和char的区别
  10. debian下安装LNMP环境(二)