当前位置: 首页 > Web前端 > JavaScript

SQL入门

时间:2023-03-27 18:39:11 JavaScript

本系列是SQL系列的开篇,介绍了一些宏观和基础的内容。什么是SQL?SQL是一种用于管理关系数据库的结构化查询语言。我们接触的90%都是查询语法,但实际上它包括了完整的增删改查、查询和事务处理功能。声明式特性SQL是一种声明式编程语言,而现代通用编程语言一般都是命令式的。但是不要盲目崇拜声明式语言。比如将来会取代低级的命令式语言,因为声明式语言本身也有它的缺点,它和命令式语言也有相似之处。为什么我们认为声明式编程语言更高级?因为声明式语言具有更高的抽象度,比如select*fromtable1只描述了从table1中查询数据,而根本没有提到查询的具体步骤。这背后可能还有复杂的索引优化和锁定机制,但我们无需关心,这简直就是编程的最高境界。那么为什么现在所有常见的业务代码都是命令式的呢?因为命令式风格给了我们描述具体实现的机会,而通用领域的编程需要基于严谨的实现细节。比如在验证用户权限这件事上,即使AI编程提供了将“登录用户只能访问授权资源”转化为代码的能力,我们也不知道资源具体指的是什么,资源所有权在权限转移给谁的过程。SQL之所以能够保留声明式的特性,完全是因为它锁定了关系型数据管理的特定领域,而正是对这个领域的要求是标准化和可枚举的,才使得声明式成为可能。声明式功能也可以基于命令式语言得到充分扩展。例如,许多ORM提供类似select({}).from({}).where({})的语法,甚至login()函数也是声明性的。编程的体现,因为调用者不需要关心如何登录,总之一次调用完成登录,这不就是declarative的全部本质吗?语法分类SQL作为一种关系型数据库管理工具,需要对数据进行定义、操作和控制。数据定义是指修改数据库和表级结构。这些是数据结构,或者说数据元数据信息,不代表具体的数据,而是描述数据的属性。数据操作是指逐行修改特定的数据,增删改查。数据管控是指对交易和用户权限的管控。数据定义DDL(DataDefinitionLanguage)数据定义,包括CREATEDROPALTER方法。DataManipulationDML(DataManipulationLanguage)数据操作,包括SELECTINSERTUPDATEDELETE方法。数据控制DCL(DataControlLanguage)数据控制,包括COMMIT、ROLLBACK等。所有的SQL操作都围绕着这三种类型进行,其中数据操作几乎占据了90%的代码量。毕竟,数据查询的吸引力远大于写作。数据写入对应于数据采集,数据查询对应于数据分析。数据分析领域能玩的模式远远多于数据收集。PS:在某些情况下,最重要的SELECT会引用到DQL(DataQueryLanguage)类别,这样类别就变成了四个。集合操作SQL世界的第一公民是集合,就像JAVA世界的第一公民是对象一样。只有从集合的角度来看SQL,才能更好地理解它。什么是集合视角,即所有的查询和运算都在一个二维数据结构中进行,而不是小学算术中单个数之间的加减乘除关系。集合运算一般包括UNION并集、EXCEPT差集、INTERSECT交集。这些都是以行为单位的操作,而各种JOIN语句是以列为单位的集合操作,也就是后面说到的连接查询。只要我们站在二维数据结构中思考,操作无非就是横向或纵向的操作。数据范式数据范式分为五层,每一层的要求都比上一层更严格,因此是一个可以循序渐进的范式。数据范式要求数据越来越解耦和冗余。例如,第一范式要求每一列都是原子的,即是不可分割的最小数据单元。如果某列在数据采集时存储为字符串,并以“|”分隔代表省市,那就不是原子的了。当然,实际的生产过程往往不遵循这个标准,因为表不是孤立的。在数据处理流程中,可能会在某一点将列原子化,合并原始数据,压缩体积。我希望违反范式的不仅仅是底层表。在当前的大数据处理场景中,越来越多的业务采用大而宽的表结构,甚至刻意进行数据冗余以提高查询效率。列存储引擎就是为这种场景设计的,所以数据范式在大数据场景下是灵活的,但是还是值得学习的。聚合在使用GROUPBY对数据进行分组聚合时,如果要对聚合值进行过滤,则不能使用WHERE来限制条件,因为WHERE是基于行过滤的,而不是用于组合的。(GROUPBY对数据进行分组,我们称这些组为“组合”),因此需要对组合使用过滤语句HAVING:SELECTSUM(pv)FROMtableGROUPBYcityHAVINGAVG(uv)>100在这个例子中,如果HAVING改变了它使用WHERE是没有意义的,因为在WHERE中添加聚合条件时,需要合并所有的数据,不符合当前视图的详细程度。(关于视图的详细层次,之前写过,精读《什么是 LOD 表达式》有详细说明)。聚合之所以重要,是因为我们要从高层次的角度去分析数据,详细的数据是看不到趋势的。复杂的需求往往伴随着聚合过滤条件,所以了解SQL是如何支持的非常重要。CASE表达式CASE表达式分为简单CASE表达式和搜索CASE表达式,简单表达式:SELECTCASEpvWHEN1THEN'low'ELSE'high'ENDASquality上面的例子使用CASE简单表达式形成一个新的字段,这种模式是等价的生成一个业务定义的临时字段,在处理当前表的数据时非常有用。搜索CASE表达式的能力完全覆盖了简单的CASE表达式:SELECTCASEWHENpv<100THEN'low'ELSE'high'ENDASquality可以看到,搜索CASE表达式可以使用“表达式”来描述条件,这可以轻松完成复杂的任务,甚至可以在表达式中使用子查询、聚合等手段。这些都是手写SQL的惯用技巧,所以CASE表达式值得深入学习。复杂查询SELECT是SQL中最复杂的部分,它包含三种复杂查询模式,即连接查询和子查询。Join查询是指JOIN查询,如LEFTJOIN、RIGHTJOIN、INNERJOIN。在介绍聚合的时候,我们提到过join查询本质上就是扩列,不会无缘无故将两张表合并,所以必须要有一个外键作为关系键:SELECTA.pv,B.uvFROMtable1ast1LEFTJOINtable2ASPt2ONt1.productId=t2.productId连接查询不仅对列进行了扩展,对行也进行了相应的扩展,扩展方式与连接查询的类型有关。除了连接查询其他表,还可以自己连接查询,例如:SELECTt1.pvASpv1,P2.pvASpv2FROMttt1,ttt2这个子连接查询的结果是笛卡尔积本身和自身,可以通过WHEREDeduplication过滤,后面会有专门的文章介绍。子查询和视图子查询是SELECT中的SELECT。一般来说,一个SELECT会由内向外执行。只有在关联子查询模式下,才会由外向内执行。而如果子查询被保存,它就是一个视图。这个视图不是实体表,所以很灵活,数据会随原表数据变化:CREATEVIEWcountryGDP(country,gdp)ASSELECTcountry,SUM(gdp)FROMttGROUPBYcountry后,countryGDP视图可以用作临时表。这种模式实际上违背了SQL的声明式特性,因为定义视图类似于定义变量。如果一直写下去,难免会形成一定的命令式思维逻辑,但这是无法避免的。事务SQL在执行一系列操作时,在执行完成之前难免会遇到脏数据的问题,因此事务可以保证操作的原子性。一般来说,每一个DML操作都是一个内置的事务,而SQL提供的STARTTRANSACTION可以让我们自定义事务的范围,从而可以将一系列的业务操作打包在一起,成为一个原子操作。对于SQL来说,原子操作是非常安全的,即失败了不留痕迹,成功的都是成功的,不会有中间状态。OLAPOLAP(OnLineAnalyticalProcessing)即实时数据分析,是BI工具背后的计算引擎实现的基础。现在越来越多的SQL数据库支持窗口函数的实现,用于实现业务中的runningSum或者runningAvg等函数,在数据分析中很常见。以runningSum为例。比如双十一实时表的数据就是以分钟为单位的实时GMV。如果我们要做一个累计到当前时间的GMV汇总折线图,Y轴需要支持running_sum(GMV)公式的表达,这背后可以通过window函数来实现。当然,并不是所有的业务功能都是SQL直接提供的。业务层还需要实现大量的内存功能,其中一些在JAVA层计算时需要下推到SQL执行。形成我们在BI工具中看到的复杂的计算场效应。总结SQL是一种声明性语言。一条看似简单的查询语句,往往对应着复杂的引擎层实现。这就是SQL如此重要又如此流行的原因。SQL虽然简单易用,但是要系统地理解它,还是需要从结构化数据和集合的概念上换个思路。不要低估CASE语法。它不仅与编程语言的CASE语法相混淆,而且还结合表达式判断条件分支。是很多数据分析师在日常工作中使用时间最长的套路。现在使用简单SQL的应用场景越来越少,但是在BI场景中,基于SQL增强表达的场景越来越多。我创建这个系列的目的是了解BI场景中的查询表达式。我希望能够学以致用。讨论地址为:Jingdu《SQL 入门》·Issue#398·ascoders/weekly想参与讨论的请点这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)