1、插入语句

INSERT语句用来向表、分区或视图中添加行。可以向单个表或者多个表中添加数据行。单表插入将会向一个表中插入一行数据,这行数据可以显示地列出插入值也可以通过一个子查询来获取。多表插入将会向一个或多个表中插入行,并且会通过子查询获取值来计算所插入行的值。

1.1、INSERT

如果你要插入表中所定义的所有字段的值,那么字段的列表是可选的。但是如果你只想提供部分字段的值,则必须在字段的列表中指明所需的字段名。建议即便要插入所有字段的值,也明确给出字段列表。语法:

INSERTINTOtarget_table(column1,column2,...column_n)VALUES(expression1,expression2,...expression_n);

INSERTINTOtarget_table(column1,column2,...column_n)SELECTexpression1,expression2,...expression_nFROMsource_table[WHEREconditions];

示例:

--向t_staff表添加一条员工数据INSERTINTOdemo.t_staff(staff_name,dept_code,gender,birthday,edu_bg,base_salary,post_salary,post_code)VALUES('李阳','010101',1,TO_DATE('1989-01-14','yyyy-mm-dd'),2,2500,8000,'P50');

1.2、INSERT ALL

语法:

INSERT[ALL/FIRST]WHEN<expression>THENINTOtarget_table1(column1,column2,column_n)WHEN<expression>THENINTOtarget_table2(column1,column2,column_n)ELSEINTOtarget_table3(column1,column2,column_n)SELECT<columnlist>FROMsource_table;

INSERTALLINTOtarget_table1(column1,column2,column_n)VALUES(expr1,expr2,expr_n)INTOtarget_table2(column1,column2,column_n)VALUES(expr1,expr2,expr_n)INTOtarget_table3(column1,column2,column_n)VALUES(expr1,expr2,expr_n)SELECT*FROMDUAL;

示例:

--向t_field_enum表中添加一条部门数据,同时向t_staff表添加一条员工数据INSERTALLINTOdemo.t_field_enum(field_code,enum_code,enum_name,enum_level,enum_order)VALUES('DEPT','010203','实施三部',3,3)INTOdemo.t_staff(staff_name,dept_code,gender,birthday,edu_bg,base_salary,post_salary,post_code)VALUES('徐来','010202',1,TO_DATE('1991-04-01','yyyy-mm-dd'),1,2500,5200,'P50')SELECT*FROMDUAL;

同步插入:有时候我们需要把一张表中的数据分别插入到另外两张表中,如果不加事物仅写两条INSERT语句分别添加是有问题的,因为表中数据完全有可能在第一条语句执行之后第二条语句执行之前发生了变化。如要将t_staff表中月薪达到 8000 的 90 员工分别添加到t_staff_hight_staff_young两个表中,示例:

INSERTALLINTOdemo.t_staff_high(staff_id,staff_name,dept_code,gender)INTOdemo.t_staff_young(staff_id,staff_name,dept_code,gender)SELECTt.staff_id,t.staff_name,t.dept_code,t.genderFROMdemo.t_stafftWHEREt.birthday>=TO_DATE('1990-01-01','yyyy-mm-dd')AND(t.base_salary+t.post_salary)>=8000;

有条件的插入:使用INSERT ALL语句还可以根据条件将数据插入到不同的表中。示例:

INSERTALLWHENtest_value<8THENINTOdemo.t_staff_high(staff_id,staff_name,dept_code,gender)WHENtest_value<7THENINTOdemo.t_staff_low(staff_id,staff_name,dept_code,gender)ELSEINTOdemo.t_staff_copy(staff_id,staff_name,dept_code,gender)SELECTt.staff_id,t.staff_name,t.dept_code,t.genderFROMdemo.t_stafftWHEREt.post_salary>=5000;

在上面的语句中,如果test_value=6,岗位工资达到 5000 的员工会被插入到t_staff_hight_staff_low两张表中;如果test_value=7,岗位工资达到 5000 的员工会被插入到t_staff_low表中;如果test_value=8,岗位工资达到 5000 的员工会被插入到t_staff_copy表中。

假如test_value=6,也就是前两个条件都满足时,优先执行第一个插入,当第一个条件不满足时,再来判断是否要执行第二个条件的插入,这种情况只需把ALL换成FIRST即可实现。

2、删除语句

2.1、DELETE

DELETE语句用来从表中移除数据行。该语句的语法结构由 3 部分组成:DELETE、FROM 和 WHERE。DELETE 关键字是单独列出的,没有其它选项与 DELETE 关键字相结合(hint除外)。FROM 子句用来指定要从那个表中删除数据行。WHERE 子句提供筛选条件有助于确定那些行是要删除的。语法:

DELETEFROMtarget_table[WHEREconditions];

示例:

--把t_staff_high表中名字叫“韩三”的职员删除DELETEFROMdemo.t_staff_hightWHEREt.staff_name='韩三';

2.2、TRUNCATE

TRUNCATE语句是用来清空表中数据的。从表面上看它的效果与不带WHERE条件的DELETE语句效果是一样的。它有 4 个特点,分别是:

1、删除表全部内容,但保留表结构;

2、速度快,但不可回滚,这意味着数据删除后无法再恢复;

3、触发器中不能使用,因为TRUNCATE语句不会触发任何操作;

4、DELETE语句每删除一行数据都是一个事务,会产生若干“日志”。但TRUNCATE语句是释放整个数据页(一个页 8K)。

TRUNCATE TABLE target_table
[ PRESERVE MATERIALIZED VIEW LOG | PURGE MATERIALIZED VIEW LOG ][ DROP STORAGE | REUSE STORAGE ];

如要把t_staff_high表中所有数据全部删除,示例:

TRUNCATETABLEt_staff_high;

3、更新语句

3.1、UPDATE

UPDATE语句的作用是改变表中原有行的字段值。这个语句的语法由3部分组成:UPDATE、SET 和 WHERE。UPDATE 子句用来指定要更新的表,SET 子句用来指明那些字段要被改变以及改变后的值,WHERE 子句用来指定要更新的行。语法:

UPDATEtarget_tableSETcolumn1=expression1,column2=expression2,...column_n=expression_n[WHEREconditions];

示例:

--给所有女性职工的工资加100UPDATEdemo.t_stafftSETt.post_salary=t.post_salary+100WHEREt.gender=0;

从语法的角度来说,UPDATE语句完全可以不带条件,但实际需求中基本上都有条件。我曾维护过某医院的 EMR 系统,有一天医生反馈说,他们科室所有患者的年龄显示都一样,我仔细检查之后发现是因为修改患者信息的 SQL 语句没有WHERE条件。后来我问一个老员工相关代码怎么会那么烂,他说当初这块功能是让一帮还没毕业的研究生做的,可能他们不太懂 SQL 语句。我只想说,这种低级错误即便是专科实习生也不应该犯,因为会犯这种错误至少能说明3个问题,首先这人根本没搞明白需求或业务逻辑太差,需求是要改某一个患者的信息;其次 SQL 基础也很差;再次他可能太过自信或不负责任,发布后的代码中还有这么严重的 Bug 说明他自己没有认真测试过。当然了,那个公司也没有测试人员,不过这是另一个层面的问题了。

3.2、带子查询的 UPDATE

UPDATE语句内部可以包含子查询,常见的有两种情况,一种是仅WHERE子句中带有查询,这种情况和带子查询的SELECT语句类似,可参考下文;另一种是SET子句中带有查询,本节主要介绍这种情况。语法:

UPDATEtarget_tableSETcolumn1=(SELECTexpression1FROMsource_tableWHEREconditions)[WHEREconditions];

尽管这个语法看起来很简单,但初次接触 Oracle 的开发人员极易写错,本人在工作中发现不少工龄超过 5 年的开发人员依然会用错这个语法(没写最后那个可选的WHERE条件,结果导致表中不符合查询条件的数据全都被改成了 NULL),当我口头指出这个问题的时候他们往往还不以为然,直到我把测试结果亮出来他们才恍然大悟。

我分析之所以会这样主要有两方面原因,一方面很多人会习惯性的认为,查询语句的WHERE条件里已经和要更新的目标表关联过了,那更新的时候自然就会只更新WHERE条件限定的这部分数据呀!其实这只是一厢情愿的想法,因为他们忽略了这个查询其实是个行间查询。另一方面,实际开发中必须用这个语法的情况很少,而低频应用开发中偶尔用一次,即便有问题只要当时没发现,被坑的也是下一个人(我就是那个被坑了 N 多次的人)。所以会有很多工作多年的开发人员依然不太理解这个语法。

假如发现t_staff_salary表中研发一部的员工固定工资计算错了,需要重新计算一下,示例:

UPDATEdemo.t_staff_salaryt1SETt1.fixed_salary=(SELECTt2.base_salary+t2.post_salaryFROMdemo.t_stafft2WHEREt2.staff_id=t1.staff_idANDt2.dept_code='010101')WHEREt1.dept_code='010101';

如果把上述语句中最后一个WHERE条件去掉,再次执行就会把除研发一部以外的其它部门员工固定工资都改成 NULL。

3.3、一次更新多个字段的 UPDATE

一次更新多个字段,这个说法看上去没有什么稀奇之处,最普通的UPDATE语句的SET子句后面本身就可以写多个字段。如果特殊点,其中一个字段的值来自另一张表的某个字段呢?上一节已经说过,写个子查询就能搞定。但如果再特殊一点,其中两个甚至多个字段来自另一张甚至多张表呢?语法:

UPDATEtarget_tableSET(column1,column2,column3...)=(SELECTexpression1,expression2,expression3...FROMsource_tableWHEREconditions)[WHEREconditions];

举个例子,假如公司人员异动和调薪后,员工表t_staff中的数据已经更新,但t_staff_salary表中的部门和工资字段尚未同步更新,示例:

UPDATEdemo.t_staff_salaryt1SET(t1.dept_code,t1.fixed_salary)=(SELECTt2.dept_code,t2.base_salary+t2.post_salaryFROMdemo.t_stafft2WHEREt1.staff_id=t2.staff_id);

当然,有些朋友可能会想到下面这种更新方式,我在不知道上述语法时也经常用下面这种方式,示例:

TRUNCATETABLEdemo.t_staff_salary;INSERTINTOdemo.t_staff_salary(staff_id,staff_name,dept_code,fixed_salary)SELECTt.staff_id,t.staff_name,t.dept_code,t.base_salary+t.post_salaryFROMdemo.t_stafft;

这种方式一般也是行的,但毕竟要更新整张表,某些时候表中部分字段里的数据其实是不能删的,那就不能用这种方式了。

4、查询语句

SELECT语句用来从一个或多个表中或者其它数据库对象中提取数据。要想写出符合语法规则的高效 SQL 语句,需要理解 SQL 语句是如何执行的。一个查询语句在逻辑上的处理方式可能会与实际物理处理过程大相径庭。Oracle 基于查询成本的优化器(cost-based optimizer,CBO)用来产生实际的执行计划。

4.1、SELECT

语法:

SELECTexpressionsFROMtarget_table[WHEREconditions];

示例一:

--查询所有90后女员工的姓名SELECTt.staff_nameFROMdemo.t_stafftWHEREt.gender=0ANDt.birthday>=TO_DATE('1990-01-01','yyyy-mm-dd');

示例二:

--查询所有90后女员工的姓名和部门SELECTt1.staff_name,t2.enum_namedept_nameFROMdemo.t_stafft1LEFTJOINdemo.t_field_enumt2ONt1.dept_code=t2.enum_codeANDt2.field_code='DEPT'WHEREt1.gender=0ANDt1.birthday>=TO_DATE('1990-01-01','yyyy-mm-dd');

4.2、带子查询的 SELECT

WHERE 子句带子查询:如要查询开发部所有员工的姓名和生日。示例:

SELECTt1.staff_name,t1.birthdayFROMdemo.t_stafft1WHEREt1.dept_codeIN(SELECTt3.enum_codeFROMdemo.t_field_enumt3WHEREt3.parent_enum_id=2);

FROM 子句带子查询:如要查询所有部门的岗位工资之和。示例:

SELECTDISTINCTt2.enum_namedept_name,t3.total_post_salaryFROMdemo.t_stafft1LEFTJOINdemo.t_field_enumt2ONt1.dept_code=t2.enum_codeANDt2.field_code='DEPT'LEFTJOIN(SELECTs1.dept_code,SUM(s1.post_salary)total_post_salaryFROMdemo.t_staffs1GROUPBYs1.dept_code)t3ONt1.dept_code=t3.dept_code;

SELECT 子句带子查询:如要查询所有部门的人数。示例:

SELECTDISTINCTt2.enum_namedept_name,(SELECTCOUNT(1)FROMdemo.t_staffs1WHEREs1.dept_code=t1.dept_code)total_staffFROMdemo.t_stafft1LEFTJOINdemo.t_field_enumt2ONt1.dept_code=t2.enum_codeANDt2.field_code='DEPT';

4.3、查询语句的各子句详解

查询语句完整语法:

SELECTDISTINCT<selectlist>FROM<leftsourceobject><jointype>JOIN<rightsourceobject>ON<onpredicates>WHERE<wherepredicates>GROUPBY<groupbylist>HAVING<havingpredicates>ORDERBY<orderbylist>;

1>、FROM 子句列出了所查询数据的源对象。这个子句可以包含表、视图、物化视图、分区或子分区,或者你可以建立一个子查询来生成子对象。如果使用了多个源对象,其逻辑处理阶段也将会应用到每一个联结类型以及谓语 ON。

2>、WHERE 子句提供了一种方法,可以按照条件来限制查询最终返回结果集的行数。每个条件或者谓语都是以两个值或表达式相比较的形式出现的。比较的结果要么是匹配(值为TRUE)要么是不匹配(值为FALSE)。如果比较的结果是 FALSE,那么相应的行不会被包含在最终结果集中。WHERE 子句的目的是限制或者减少结果集。你所使用的限制条件越少,最终返回结果集中包含的数据就会越多。你需要返回的数据越多,执行查询的时间也就越长。事实上,SQL 中逻辑比较的可能结果是 TRUE、FALSE 以及未知。当其中包含空值(NULL)的时候比较的结果就会是未知。空值与任何值比较或者用在表达式中都会得到空值,或者是未知。一个空值代表一个相应值的缺失,并且可能因为 SQL 语言中的不同部分对空值的处理不同而令人费解。

3>、GROUP BY 子句将执行 FROM 和 WHERE 子句后得到的经过筛选后的结果集进行聚合。查询出来的结果按照 GROUP BY 子句中列出的表达式进行分组,来为每一个分组得出一行汇总结果。你可以按照 FROM 子句中所列出对象的任意字段进行分组,即使你并不想在输出结果中显示该列。相反,SELECT 列表中任何非聚合字段都必须包括在 GROUP BY 表达式中。GROUP BY 子句中还可以包含两个附加的运算:ROLLUP和 CUBE。ROLLUP 运算用来产生部分求和值,CUBE 运算用来求得交互分类值。

4>、HAVING 子句将分组汇总后的查询结果限定为只有该子句中的条件为真的数据行。事实上,GROUP BY 子句和 HAVING 子句的位置是可以互换的。从本质上来说,HAVING 子句是在 GROUP BY 子句执行后用来筛选汇总值的第二个 WHERE 子句。

5>、SELECT 列表列出查询的返回最终结果集中需要显示那些列。这些列可以是数据表中的一个实际的列、一个表达式,或者甚至是一个 SELECT 语句的结果。当使用另外一个 SELECT 语句来产生结果中的一列的值的时候,这个查询必须只能返回一行一列的值。这种类型的子查询被称为标量子查询。尽管这可能是一个非常有用的语法,但需要牢记于心的是标量查询在结果集中的每一行结果产生时都要执行一遍。在某些情况下可以进行优化以减少标量子查询的重复执行,但更糟糕的场景是每一行都需要标量子查询执行。你可以想象如果你的结果集中有几千行甚至上百万行数据的时候所需要付出的查询代价!

6>、DISTINCT 子句用于从结果集中删除重复项。DISTINCT 子句只能与 SELECT 语句一起使用。

7>、ORDER BY 子句用来对查询最终返回的结果集进行排序。当输出结果需要排序的时候,Oracle 必须在其他所有子句都执行完之后按照指定的顺序对最终结果集进行排序。需要排序的数据量大小是非常重要的。这里所说的大小是指结果集中所包含的总字节数。可以通过用行数乘以每一行的字节数来估计数据集的大小。每行所包含的字节数通过将选择列表中包含的每一列的平均长度相加来确定。较小的排序会完全在内存中来实现,而较大的排序将不得不使用临时磁盘空间来完成。如你可能推断的那样,在内存中完成排序比使用磁盘的排序要快。因此,当优化器估算排序数据的影响时,它必须要考虑排序数据集的大小,以此来调整如何能够以有效的方法来获得查询的结果。一般来说,排序是查询过程中开销相当大的一个处理步骤,尤其是当返回结果集很大的时候。