第5章 CHAPTER 5 SQL数字的查询与处理 5.1MySQL 8.0的数字 每种语言与数据库都含有针对处理数字的数据类型。 在Java中含有short、int、long、float、double等数据类型,用于处理与数字有关的逻辑与业务。 在MySQL中含有int、bigint、float、double等数据类型,用于处理与数字有关的逻辑与业务。 5.1.1MySQL 8.0中的数字类型 MySQL 8.0中支持的数字类型如表51所示。 表51MySQL 8.0中支持的数字类型 数 字 类 型长度释义 tinyint[(m)]有符号整数: -128~127 无符号整数: 0~255需要1字节存储空间。m是可选的显示宽度,不影响值的范围 smallint[(m)]有符号整数: -32768~32767 无符号整数: 0~65535需要2字节存储空间。m是可选的显示宽度,不影响值的范围 mediumint[(m)]有符号整数: -8388608~8388607 无符号整数: 0~16777215需要3字节存储空间。m是可选的显示宽度,不影响值的范围 int[(m)]有符号整数: -2147483648~2147483647 无符号整数: 0~4294967295需要4字节存储空间。m是可选的显示宽度,不影响值的范围 bigint[(m)]有符号整数: -9223372036854775808~9223372036854775807 无符号整数: 0~18446744073709551615需要8字节存储空间。m是可选的显示宽度,不影响值的范围,但是要注意对较大的值进行代数运算可能会出现错误 float[(m,d)]精确到小数点后7位左右单精度的浮点值,m为最大位数,并可包含小数点后的d位小数。注意许多十进制的数字遇浮点类型会丧失精度,若需要确切的值,则应更改为decimal类型 double[(m,d)]精确到小数点后15位左右m为最大位数(最大位数包含整数+小数),并可包含小数点后的d位小数。注意许多十进制的数字遇浮点类型会丧失精度,若需要确切的值,则应更改为decimal类型 double precision[(m,d)]double的别名与double一致 real[(m,d)]默认为double别名若设置了real_as_float,则为float别名 decimal[(m[,d])]m(精确度): 高达65位 d(小数): 0~30位非浮点的确切十进制类型,其中m为最大位数,并可包含小数点后的d位小数。如有必要,则类型将四舍五入 bit[(m)]m默认值为1,范围为1~64位字段类型,其中m用于指定位数。若输入值位数小于m,则对齐到最后一位。 若要单独命名每位,则可参考set serial范围: 0~18446744073709551615serial是bigint unsigned not null auto_increment unique的别名。 unsigned 指无序号 not null 指不为空 auto_increment指自增 unique 指唯一约束 boolbool是tinyint(1)的别名 booleanboolean是tinyint(1)的别名 dec[(m[,d])]dec是decimal的别名,除了dec之外decimal的别名还有fixed和numeric 5.1.2tinyint类型、bool类型、boolean类型 tinyint类型是MySQL中最小的数字类型,仅需1字节进行存储,可存储有符号整数-128~127,或无符号整数0~255。 创建表时将字段设置为tinyint类型的SQL语句如下: create table test_tinyint(t1 tinyint(1)); 向test_tinyint表中添加测试数据,SQL语句如下: insert into test_tinyint values(10); insert into test_tinyint values(127); insert into test_tinyint values(-128); 查询语句如下: select * from test_tinyint; 运行后,结果集如图51所示。 在MySQL中,bool与boolean本质上就是tinyint字段,可创建字段为bool类型和boolean类型的SQL语句如下: create table test_bool(t1 bool,t2 boolean); 向test_bool表中添加测试数据,SQL语句如下: insert into test_bool values(0,0); insert into test_bool values(1,1); insert into test_bool values(true,true); insert into test_bool values(false,false); 查询语句如下: select * from test_bool; 运行后,结果集如图52所示。 图51查询tinyint字段结果 图52查看boolean字段结果 5.1.3无符号整数类型 在MySQL 8.0中创建数字类型在默认情况下是“含有符号的数字类型”,例如创建含有int数字类型的测试表,SQL语句如下: create table test_int1 (t1 int); 增加测试表之后,可插入列值2147483648,SQL示例语句与报错如下: insert into test_int1 value(2147483648); --报错Out of range value for column 't1' at row 1 报错中说明已经超过t1列值的承受能力了。MySQL 8.0中提供了有符号整数和无符号整数两种写法,默认创建的数字类型皆为有符号整数,创建无符号整数时需要增加关键字unsigned,创建无符号整数的SQL语句如下: --创建无符号整数类型的表test_int2 create table test_int2 (t1 int unsigned); --添加数据 insert into test_int2 value(2147483648); 5.1.4数字类型的精度 创建double类型,将精度设置为2的SQL语句如下: -- 创建float和decimal类型表 create table test_float(t1 float(10,2), t2 decimal(10,2)); 其中float的最大位数为10,代表着整数最多为8位,小数为2位。添加数据的示例SQL语句如下: insert into test_float values(12345678.21, 12345678.12); 若输入以下SQL语句,MySQL则会报错。 insert into test_float values(1234567890, 1234567890); --报错为Out of range value for column 't1' at row 1 图53查看浮点字段结果 在没有输入任何小数时会默认将小数填充为0。添加没有任何小数的SQL语句如下: insert into test_float values(12345678, 12345678); 通过SQL语句查询test_float的结果集如图53所示。 5.2数字常用函数与运算符 MySQL中一共含有37种不同的数字常用函数(Numeric Functions)与运算符,可通过如下SQL语句在MySQL数据库中查询数字常用函数。 //代码位置: 全书代码/5.2 数字常用函数与运算符.sql select * from mysql.help_topic ht where ht.help_category_id = ( select hc.help_category_id from mysql.help_category hc where hc.name = 'Numeric Functions'#数字处理相关函数 ); 在实际工作中对于MySQL数字处理大多数基于加减乘除和百分数,其余函数使用较少,例如正弦函数sin()、余弦函数cos()、正弧线函数asin()、正切函数atan()、余切函数cot(),此处仅列举部分MySQL处理数字的相关常用函数。 5.2.1div()函数 div()将对数字进行整数除法,为数字处理类函数。从除法结果中丢弃任何小数部分,SQL语句如下: select 5 div 2, -5 div 2, 5 div -2, -5 div -2; --返回结果2, -2, -2, 2 5.2.2abs()函数 abs()函数将返回绝对值,为数字处理类函数。若输入内容为null关键字,则会返回null,SQL语句如下: select abs(2);#返回 2 select abs(-32);#返回 32 5.2.3ceiling()函数 ceiling(X)函数将返回不小于X的最小整数值,为数字处理类函数,SQL语句如下: select ceiling(1.23); #返回 2 select ceiling(-1.23);#返回 -1 5.2.4floor()函数 floor(X)函数将返回不大于X的最大整数值,为数字处理类函数,SQL语句如下: select floor(1.23), floor(-1.23);#返回 1, -2 5.2.5pow()函数和power()函数 pow(X,Y)函数将返回X的Y次幂,为数字处理类函数,SQL语句如下: select pow(2,2);#返回 4 select pow(2,-2); #返回 0.25 power(X,Y)函数是pow(X,Y)函数的别名,效果完全相同。 5.2.6rand()函数 rand()返回范围为0≤v<1.0的随机浮点值,v为返回的值,为数字处理类函数。如需返回整数,则需使用以下的公式: FLOOR(i + RAND()* (j - i)) 例如要获得随机整数在范围7≤R<12内,SQL语句如下: select floor(7 + (rand()* 5)); 5.2.7truncate()函数 truncate(X,D)函数返回数字X,截断为D位小数。为数字处理类函数。若D为0,则结果并没有小数点或小数部分。D可以是负数。使值X的小数点左边的D位变为0,SQL语句如下: //代码位置: 全书代码/5.2.7 truncate()函数.sql select truncate(1.223,1);#返回 1.2 select truncate(1.999,1);#返回 1.9 select truncate(1.999,0);#返回 1 select truncate(-1.999,1);#返回 -1.9 select truncate(122,-2);#返回 100 select truncate(10.28*100,0);#返回 1028 5.3聚合函数 MySQL还经常运用聚合函数和修饰符(Aggregate Functions and Modifiers)类型的函数。在MySQL数据库中查询聚合函数和修饰符的SQL语句如下。 //5.3 聚合函数.sql select * from mysql.help_topic ht where ht.help_category_id = ( select hc.help_category_id from mysql.help_category hc where hc.name = 'Aggregate Functions and Modifiers' #聚合函数和修饰符 ); 聚合函数和修饰符是MySQL中比较特殊的函数类型,聚合函数的特点是可以根据group by关键字进行分组聚合。 group by分组必须跟随在聚合函数之后,但聚合函数的使用不一定非得含有group by分组。 5.3.1count(distinct)函数 count(distinct)函数可进行去重统计总行数,SQL语句如下: select count(distinct sal) from emp;#返回11 select count(sal) from emp;#返回14 5.3.2查询每个部门的平均薪资 例如公司表(emp表)中含有薪资列(sal列),但是当直接使用avg()函数对emp表的sal列进行聚合计算平均值时会算出所有人员的薪资平均值。若想按照公司的不同部门进行不同平均值计算,则需使用group by 对emp表的部门进行分组。 因为聚合函数将会运行在group by关键字之后,所以可以进行分组聚合,SQL语句如下: //5.3 聚合函数.sql ---查询每个部门的平均薪资 select deptno, avg(sal) from emp group by deptno; 聚合函数中包括的返回列最大值max()函数、返回列最小值min()函数、返回列平均值avg()函数、返回列总和sum()函数、返回列平均值avg()函数、返回列总行数count()函数、返回列总行数且去重count(distinct)函数等。 聚合函数只需在函数中输入列名便可以使用,针对列名进行聚合计算。 所有聚合函数皆可使用group by进行分组。 5.3.3查询每个部门的薪资最高与最低的人(携带提成) 在emp表中含有comm字段代表提成,正常情况下只需使用sal+comm便可以获得携带提成的薪资,但此张表含有问题,即comm字段可能为null。若使用了1+null的方式,则会返回null,SQL语句如下: select 1 + null;#返回null 因为提成中含有null值,所以此刻不能直接使用sal+comm的方式获取携带提成的薪资。 图54coalesce转换后的结果 因为此时需要排除null字段,但若使用where语句排除,则可能会把不含提成的人连月薪都不进行统计了,所以此处需要使用前文提过的coalesce()函数将null值转换为0,SQL语句如下: select sal+coalesce(comm,0) from emp; 运行后,结果集如图54所示。 此时就可把工资与提成加到一起了,并且没有遗漏任何人,增加group by针对部门进行分组即可实现“每个部门的薪资最高与最低的人(携带提成)”的需求,SQL语句如下: //5.3 聚合函数.sql select deptno as '部门编号', max(sal + coalesce(comm, 0)) as '部门最高薪资(携带提成)', min(sal + coalesce(comm, 0)) as '部门最低薪资(携带提成)' from emp group by deptno; 运行后,结果集如图55所示。 图55最终结果 5.3.4查询每个部门的薪资总额 MySQL使用sum()函数记录相加后的总和,并且可通过group by针对emp表进行分组,这样便可获得每个部门的薪资总额,SQL语句如下: //5.3 聚合函数.sql select deptno as '部门编号', sum(sal) as '部门的薪资总额' from emp group by deptno; 运行后,结果集如图56所示。 图56查询每个部门的薪资总额结果集 5.3.5查询每个部门有多少人 MySQL使用count()记录总行数,通过group by针对emp表进行分组,即可获得每个部门有多少人,SQL语句如下: //5.3 聚合函数.sql select deptno as '部门编号', count(sal) as '总人数' from emp group by deptno; 运行后,结果集如图57所示。 图57查询每个部门有多少人的结果集 5.3.6查询每个部门有多少人没有提成 首先需要判断没有提成的人,即emp表中comm字段为空,然后因为需求中提到了需要根据每个部门进行分组,所以需要使用group by,最后需要使用count()函数统计人数,SQL语句如下: //5.3 聚合函数.sql select deptno as '部门名称', count(ename) as '人数' from emp e where e.comm is null or e.comm = 0 group by deptno; 运行后,结果集如图58所示。 图58查询每个部门有多少人没提成的结果集 5.3.7查询某个部门薪资占全公司的百分比 查询部门编号为10的部门,其薪资总额占全公司薪资总额的百分比,SQL语句如下: //5.3 聚合函数.sql select ( select sum(e.sal) from emp e where e.deptno = 10 ) / ( select sum(e.sal) from emp e ) * 100 as result; 运行后,结果集如图59所示。 图59查询编号为10的部门薪资占全公司薪资百分比的结果集 当然此需求还有其他写法,例如使用后文将会提到的case when方式可以使用一条SQL语句就查询出来结果,SQL语句如下: //5.3 聚合函数.sql select ( sum(case when deptno = 10 then sal end) / sum(sal) ) * 100 as pct from emp; 5.4窗口函数 在MySQL 8.0中推出了新的函数类型,即窗口类函数(Window Functions),窗口函数可对结果集中的每行进行计算,或使用与该行相关的行进行计算。 5.4.1窗口函数的语法 窗口函数的基本语法如下: //5.4 窗口函数.sql <窗口函数> over ( partition by <用于分区的列名> order by <用于排序的列名> frame_clause <窗口大小> ) 如下SQL语句可在MySQL数据库中查询非聚合类窗口函数。之所以称Window Functions为非聚合类窗口函数,是因为在MySQL中聚合函数也可以作为窗口函数使用。 //5.4 窗口函数.sql select * from mysql.help_topic ht where ht.help_category_id = ( select hc.help_category_id from mysql.help_category hc where hc.name = 'Window Functions'#窗口类型函数 ); MySQL中的非聚合类窗口函数统计如表52所示。 表52MySQL非聚合类窗口函数统计 函 数 名 称功能简易释义 cume_dist()cume_dist()函数返回值的累计分布值。该值小于或等于行的值除以总行数 dense_rank()分区内当前行的排名,无间隔 first_value()允许使用window frame获取分区或结果集第1行的值 lag()返回当前行之前的值 last_value()选择数据中的最后一行 lead()向前看多行并从当前行访问行的数据 nth_value()从有序行集中的第n行获取值 ntile()排序分区中的行并划分为特定数量的组。每个组分配一个分组号 percent_rank()百分比排名值 rank()分区内当前行的排名,有间隔 row_number()分区内的当前行数 5.4.2初步使用窗口函数 row_number()函数是最常用的窗口函数,row_number()函数可以给任意结果集增加序号和排序。查询emp全表的SQL语句如下: //5.4 窗口函数.sql select e.ename, e.job, e.deptno, e.sal from emp e; 运行后,结果集如图510所示。 图510初步使用窗口函数的结果集 此时可以通过row_number()函数增加行号列,SQL语句如下: //5.4 窗口函数.sql select row_number() over() as row_num, e.ename, e.job, e.deptno, e.sal from emp e; 运行后,结果集如图511所示。在该结果集中增加了row_num行号列。 图511增加row_num列的结果集 5.4.3partition by关键字 partition by用于将数据行拆分成多个分区,窗口函数基于每行数据所在的区进行计算并返回结果,partition by的作用类似于group by分组。若省略了 partition by关键字,则所有的数据作为一个组进行计算。 在增加行号的例子中,此时可以增加分区关键字,让行号根据部门编号(deptno)进行分区,SQL语句如下: //5.4.3 partition by关键字.sql select row_number() over( partition by e.deptno ) as row_num, e.ename, e.job, e.deptno, e.sal from emp e; 运行后,结果集如图512所示。可以看到此时结果集根据部门编号进行了分区,以部门编号为10、20、30分为不同的区。每个区含有自身不同的行号。 图512根据部门编号进行分区的结果集 5.4.4order by关键字 order by排序用于指定分区内的排序方式,与select中的order by子句的作用类似,通常用于数据的排名分析。 在以上例子中,虽然使用了partition by关键字作为分区处理,但是因为分区之后每个部门并不是根据薪资(sal)高低进行排序的,所以此刻可以增加order by关键字,针对部门分区后进行排序,SQL语句如下: //5.4.4 order by关键字.sql select row_number() over( partition by e.deptno order by e.sal desc ) as row_num, e.ename, e.job, e.deptno, e.sal from emp e; 运行后,结果集如图513所示。 图513增加order by关键字的结果集 5.4.5rank()函数 rank()是一个窗口函数,rank()函数为分区或结果集中的每行分配排名,而排名值含有间隔。 此时可查询emp表中每名员工的薪资,SQL语句如下: //5.4.5 rank()函数.sql select e.deptno, e.ename, e.sal from emp e 运行后,结果集如图514所示。 图514查询emp表中每名员工的薪资的结果集 可通过rank()函数查询每个部门薪资的排序,SQL语句如下: //5.4.5 rank()函数.sql select rank()over( partition by e.deptno order by sal desc ) as rank_num, e.deptno, e.ename, e.sal from emp e 图515通过rank()函数查询每个部门 薪资排序的结果集 运行后,结果集如图515所示。 此时可看出每个部门含有最高薪资的人,例如部门编号为10的薪资最高的人为王十二,部门编号为20的共有3人薪资最高,其薪资均为3000元。部门编号为20的薪资排行,没有第2名和第3名,只有第1名和第4名以后。此种排名方式被称为“含有间隔”排名。 rank()函数只能作为排序所存在,并不能直接获取rank_num列等于1的表达式,如想只获取rank_num薪资最高者,则需要在外再嵌套一层select查询语句,SQL语句如下: //5.4.5 rank()函数.sql select * from ( select rank()over( partition by e.deptno order by sal desc ) as rank_num, e.deptno, e.ename, e.sal from emp e ) la where la.rank_num = 1; 运行后,结果集如图516所示。 图516获取每个部门薪资最高者的结果集 5.4.6dense_rank()函数 dense_rank()是一个窗口函数,dense_rank()函数为分区或结果集中的每行分配排名,而排名值没有间隔。 可以使用dense_rank()函数对部门进行分区以获取数据,查询每个部门的薪资排名,SQL语句如下: //5.4.6 dense_rank()函数.sql select e.deptno, e.sal, dense_rank()over( partition by e.deptno order by e.sal desc ) as rnk from emp e; 运行后,结果集如图517所示。部门编号为20的组员薪资并列第1名的,rnk值皆为1,其低于第1名的为第2名。该排名方式被称作“没有间隔”。 图517体验“没有间隔”特性的结果集 之前的rank()函数,部门编号为20的组员工资并列第1名的,其rnk值皆为1,此时低于第1名的为第4名。该排名方式被称作“含有间隔”。 5.4.7percent_rank()函数 percent_rank()函数将会计算分区或结果集中行的百分位排名。例如此时可以使用percent_rank()函数查询每名员工占全公司薪资的百分位排名,SQL语句如下: //5.4.7 percent_rank()函数.sql select e.ename, e.sal, percent_rank() over ( order by e.sal asc ) as '百分位排名' from emp e; 运行后,结果集如图518所示。 图518每名员工占全公司薪资的百分位排名的结果集 如果使用asc排序方式,则会让薪资最高的人排在最后,若使用desc排序方式,则会让薪资最低的人排在最后。 因为张十六的薪资在公司约77%的人之上,全公司共14人,所以张十六的薪资处于全公司约10名以上(14人×77%)。 由于赵六、寅十一、张十六的薪资并列,所以此3人的百分位均约为77%。 若嫌弃百分位小数过多,则可使用round()函数精确小数。本示例将百分位精确至2位小数即可,SQL语句如下: //5.4.7 percent_rank()函数.sql select e.ename, e.sal, round( percent_rank()over ( order by e.sal asc ), 2 ) as '百分位排名' from emp e; 运行后,结果集如图519所示。 图519使用round()函数精确小数的结果集 5.4.8ntile()函数 ntile()函数可以辅助用户在分组后给每个分组增加一个编号,方便后续获取。ntile()函数的括号中需要输入一个正整数值n,在分组后,序号将会将n设置为最大值。 若使用了最大值的n之后还有分组没被增加编号的情况,则将会从1重新计数,即假设含有3个分组,但n被设置成了2,则含有分组编号为分组1、分组2、分组1。 若使用了一部分n之后没有分组需要使用编号了,则与正常展示无异。 例如,此时使用ntile()函数针对每个部门薪资总额分组后进行编号,SQL语句如下: //5.4.8 ntile()函数.sql select e.deptno, sum(e.sal) sum_num, ntile(3) over ( order by e.deptno ) group_no from emp e group by e.deptno 运行后,结果集如图520所示。 图520给分组增加编号的结果集 5.5聚合函数窗口化 所有的聚合函数均支持窗口化的使用方式,其中包括前文提到过的max()、min()、sum()、group_concat()等函数。只需在常规聚合函数之后增加over()子句。 窗口函数是MySQL中最特殊的函数形式,其特殊性在于窗口操作不会将查询行组折叠成单个输出行,相反窗口函数会为每行产生结果。 该特性在5.4节有所体现,此处可以使用两种sum()函数分别进行演示,分别为使用聚合函数和聚合函数窗口化的方式。 使用聚合函数查询全公司总月薪的SQL语句如下: select sum(sal) from emp; 运行后,结果集如图521所示。 使用聚合函数窗口化的方式查询全公司总月薪的SQL语句如下: select sum(sal) over() from emp; 运行后,结果集如图522所示。因为含有14个员工,所以其数字不会折叠,会展示出14行。 图521使用聚合函数查询全公司总月薪的结果集 图522聚合函数窗口化使数字不折叠的结果集 使用聚合函数查询公司内每个部门的平均薪资,SQL语句如下: select deptno, avg(sal) from emp group by deptno; 运行后,结果集如图523所示。 使用聚合函数窗口化的方式查询公司内每个部门的平均薪资,SQL语句如下: select deptno, avg(sal) over(partition by deptno) as 'avg(sal)' from emp; 运行后,结果集如图524所示。 图523使用聚合函数查询公司内每个部门的平均薪资的结果集 图524以聚合函数窗口化的方式查询公司内每个部门的平均薪资的结果集 5.6MySQL 8.0处理数字相关的复杂查询 以下为有关MySQL处理数字相关的复杂查询与详解。 10min 5.6.1计算众数 1. 问题 众数的数学定义是“在给定数据集中出现频率最高的元素”,例如需要找出20号部门的员工薪水众数,SQL语句如下: select sal from emp where deptno = 20 order by sal; 运行后,计算众数之前的结果集如图525所示。计算众数之后的结果集如图526所示。 图525计算众数之前 图526计算众数之后 2. SQL演化 首先求出20号部门的各个阶层薪资及各阶层薪资的人数,SQL语句如下: //5.6.1 计算众数.sql select sal, count(*) as cnt from emp where deptno = 20 group by sal 运行后,结果集如图527所示。 图52720号部门的各个阶层薪资及各阶层薪资的人数的结果集 可以看出,月薪一共含有3个等级,分别是800、3000、1100,其中3000占的人数最多,含有3人,而后可根据各阶层薪资的人数cnt进行排序,SQL语句如下: //5.6.1 计算众数.sql select sal, dense_rank() over( order by cnt desc ) as rnk from ( select sal, count(*) as cnt from emp where deptno = 20 group by sal ) x; 运行后,结果集如图528所示,即薪资为3000等级的人数排第1位,800与1100等级的并列第2位。 图528根据分级薪资人数排序的结果集 最终获取排序rnk为1的数据即可,这就是部门编号为20的薪资里哪种薪资类型为最高,也就是找出了20号部门的员工薪资众数,SQL语句如下: //5.6.1 计算众数.sql select sal from ( select sal, dense_rank() over( order by cnt desc ) as rnk from ( select sal, count(*) as cnt from emp where deptno = 20 group by sal ) x ) y where rnk = 1; 6min 5.6.2计算中值 1. 问题 中值的数学定义是: “中值(又称中位数)是指将统计总体中的各个变量值按大小顺序排列起来,形成一个数列,处于变量数列中间位置的变量值就称为中位数”,此时需要查出部门编号为20的员工薪资的中值是多少。 为计算中值,按照数学定义将部门编号为20的员工薪资的变量值按大小顺序排列起来,形成一个数列,SQL语句如下: //5.6.2 计算中值.sql select row_number() over ( order by e.sal ) row_num, e.sal from emp e where e.deptno = '20' 运行后,计算中值之前的结果集如图529所示。计算中值之后的结果集如图530所示。 图529计算中值之前的结果集 图530计算中值之后的结果集 2. SQL演化 获取部门编号为20的员工总数,并除以2获取中值的位置,SQL语句如下: //5.6.2 计算中值.sql select floor(count(e.sal) / 2) + 1 from emp e where e.deptno = '20' 运行后,结果集如图531所示。 图531获取中值位置的结果集 拼接两条SQL语句。根据排列后的数列进行获取,只需获取row_num为3的薪资数目便可以查出部门编号为20的员工薪资的中值是多少,SQL语句如下: //5.6.2 计算中值.sql select * from ( select row_number() over ( order by e.sal ) row_num, e.sal from emp e where e.deptno = '20' ) as rum where rum.row_num = ( select floor(count(e.sal) / 2) + 1 from emp e where e.deptno = '20' );