前言
本篇文章讲解的主要内容是:怎样对数据组合重新排列并去重的问题、通过如何找到包含最大值和最小值的记录这个问题再次用执行计划给你证明分析函数性能不一定高
【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。
一、排列组合去重
下面介绍一个数据组合去重的问题。数据环境模拟如下:
drop table test purge;
create table test(id,t1,t2,t3) as
select '1','1','3','2' from dual union all
select '2','1','3','2' from dual union all
select '3','3','2','1' from dual union all
select '4','4','2','1' from dual;
上述测试表中前三列tl、t2、t3的数据组合是重复的(都是1、2、3),要求用查询语句找出这些重复的数据,并只保留一行。我们可以用以下步骤达到需求。
1、把tl、t2、t3这三列用列转行合并为一列。
SQL> select * from test
2 unpivot(b2 for b3 in (t1,t2,t3));
ID B3 B2
-- -- --
1 T1 1
1 T2 3
1 T3 2
2 T1 1
2 T2 3
2 T3 2
3 T1 3
3 T2 2
3 T3 1
4 T1 4
4 T2 2
4 T3 1
12 rows selected
unpivot的具体用法将在后面介绍。
2、通过listagg函数对各组字符排序并合并。
SQL> with t as
2 (select * from test unpivot(b2 for b3 in(t1, t2, t3)))
3 select id, listagg(b2, ',') within group(order by b2) as nb2
4 from t
5 group by id;
ID NB2
-- --------------------------------------------------------------------------------
1 1,2,3
2 1,2,3
3 1,2,3
4 1,2,4
3、执行常用的去重语句。
SQL> with t as
2 (select * from test unpivot(b2 for b3 in(t1, t2, t3))),
3 t1 as (
4 select id, listagg(b2, ',') within group(order by b2) as nb2
5 from t
6 group by id
7 )
8 select t1.*,row_number()over(partition by nb2 order by id) as rn
9 from t1;
ID NB2 RN
-- -------------------------------------------------------------------------------- ----------
1 1,2,3 1
2 1,2,3 2
3 1,2,3 3
4 1,2,4 1
SQL>
如上所示,如果要去掉重复的组合数据,只需要保留RN=1的行即可。
二、找到包含最大值和最小值的记录
构建数据如下:
drop table test purge;
create table test as select * from dba_objects;
create index idx_test_object_id on test(object_id);
begin
dbms_stats.gather_table_stats(ownname =>'ZYD' ,tabname => 'TEST',estimate_percent => dbms_stats.AUTO_SAMPLE_SIZE,cascade => TRUE,method_opt =>'FOR ALL COLUMNS SIZE REPEAT');
end;
要求返回最大or最小的object_id及对应的object_name,在有分析函数以前,可以用下面的查询:
SQL> select/*zyd*/ object_name,object_id
2 from test
3 where object_id in(
4 select max(object_id) from test
5 UNION ALL
6 select min(object_id) from test
7 );
OBJECT_NAME OBJECT_ID
-------------------------------------------------------------------------------- ----------
C_OBJ# 2
TEST 1578856
需要对员工表扫描三次。但用如下分析函数只需要对员工表扫描一次:
SQL> select/*zyd*/ object_name, object_id
2 from (select object_name,
3 object_id,
4 min(object_id) over() min_id,
5 max(object_id) over() max_id
6 from test) x
7 where object_id in (min_id, max_id);
OBJECT_NAME OBJECT_ID
-------------------------------------------------------------------------------- ----------
C_OBJ# 2
TEST 1578856
如果大家形成惯性思维,认为分析函数的效率最高,那就错了。我们通过autotrace看下PLAN(可以多执行几次)。
第一个语句的执行计划,如下图所示:
SQL> set timing on;
SQL> set autotrace traceonly;
SQL> alter session set current_schema=zyd;
Session altered.
Elapsed: 00:00:00.01
SQL> select/*zyd*/ object_name,object_id
from test
where object_id in(
select max(object_id) from test
UNION ALL
select min(object_id) from test
); 2 3 4 5 6 7
Elapsed: 00:00:00.00
Execution Plan
----------------------------------------------------------
Plan hash value: 2072175425
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 86 | 8 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 2 | 86 | 8 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 2 | 86 | 8 (0)| 00:00:01 |
| 3 | VIEW | VW_NSO_1 | 2 | 26 | 4 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 2 | 10 | 4 (0)| 00:00:01 |
| 5 | UNION-ALL | | | | | |
| 6 | SORT AGGREGATE | | 1 | 5 | | |
| 7 | INDEX FULL SCAN (MIN/MAX)| IDX_TEST_OBJECT_ID | 1 | 5 | 2 (0)| 00:00:01 |
| 8 | SORT AGGREGATE | | 1 | 5 | | |
| 9 | INDEX FULL SCAN (MIN/MAX)| IDX_TEST_OBJECT_ID | 1 | 5 | 2 (0)| 00:00:01 |
|* 10 | INDEX RANGE SCAN | IDX_TEST_OBJECT_ID | 1 | | 1 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID | TEST | 1 | 30 | 2 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
10 - access("OBJECT_ID"="MAX(OBJECT_ID)")
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
685 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
2 rows processed
可以看到,第一个语句虽然访问了三次,但三次都是用的索引,所以效率并不低。第二个语句执行计划如下图所示:
SQL> select/*zyd*/ object_name, object_id
from (select object_name,
object_id,
min(object_id) over() min_id,
max(object_id) over() max_id
from test) x
where object_id in (min_id, max_id); 2 3 4 5 6 7
Elapsed: 00:00:00.12
Execution Plan
----------------------------------------------------------
Plan hash value: 1093040662
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 115K| 11M| 540 (1)| 00:00:01 |
|* 1 | VIEW | | 115K| 11M| 540 (1)| 00:00:01 |
| 2 | WINDOW BUFFER | | 115K| 3375K| 540 (1)| 00:00:01 |
| 3 | TABLE ACCESS FULL| TEST | 115K| 3375K| 540 (1)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OBJECT_ID"="MIN_ID" OR "OBJECT_ID"="MAX_ID")
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1947 consistent gets
0 physical reads
0 redo size
685 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
2 rows processed
从执行计划可以看出来,走的是全表扫描,因为用的是分析函数,效率反而更低。所以,除语句的改写外,大家还要学会分析PLAN。
总结
本篇文章主要介绍的两个方面,第一个方面曾经有好几个网友和同事问我,第二个问题真的是很多同行的通病,认为分析函数是万金油,一股脑用。实际解决问题中,优化过很多同类问题的sql,这里给大家再次用案例做了分享,感谢观看!