经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » SQL语言 » 查看文章
SQL Thinking
来源:cnblogs  作者:小至尖尖  时间:2024/7/17 11:00:36  对本文有异议

s2下半年我在内部有一次部门级别的技术分享会,以本文内容分享为主。
其实有很多人问过我相同的问题,遇到需要改写的慢sql,不知道怎么改,改好了以后也不知道等不等价?不等价了也不知道错在哪?这个要怎么破?
其实都是因为绝大多数人没有做过开发,看不懂sql,不会写sql,没有sql思维,下面通过几个案例将sql思维给引出来。


1、row_number() over() 逻辑实现

这种排名的窗口函数(row_number()、rank()、dense_rank())据我所知,除了在oracle上大数据量执行性能表现得不错,其他各种数据库(包括列式存储的ap库),性能都表现都很一般。
所以如果在项目上遇到row_number() over() 性能不理想的情况下,可以尝试进行改写优化。
PS:当然可以通过加并行hint来提升效率,但是我们这篇文章主要是讲如何将数据库提供的逻辑通过另外一种方式来实现,提升自己sql代码思维。

  1. -- student 数据量
  2. obclient [YZJ]> select count(1) from student;
  3. +----------+
  4. | COUNT(1) |
  5. +----------+
  6. | 500001 |
  7. +----------+
  8. 1 row in set (0.002 sec)
  9. -- 案例sql
  10. -- S.CLASS : 学科、包含null
  11. -- S.S_DATE: 入学时间
  12. SELECT * FROM (
  13. SELECT
  14. S.*,
  15. row_number() OVER(PARTITION BY S.CLASS ORDER BY S.S_DATE DESC) RN
  16. FROM student S
  17. ) WHERE RN = 1;
  18. 27 rows in set (0.639 sec)
  19. +---------------------------------------------------------------------------------------------------------------------------------------------------------------+
  20. | Query Plan |
  21. +---------------------------------------------------------------------------------------------------------------------------------------------------------------+
  22. | ================================================================ |
  23. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  24. | ---------------------------------------------------------------- |
  25. | |0 |SUBPLAN SCAN |ANONYMOUS_VIEW1|4 |691395 | |
  26. | |1 |└─WINDOW FUNCTION | |500001 |682867 | |
  27. | |2 | └─PARTITION SORT | |500001 |249986 | |
  28. | |3 | └─TABLE FULL SCAN|S |500001 |46352 | |
  29. | ================================================================ |
  30. | Outputs & filters: |
  31. | ------------------------------------- |
  32. | 0 - output([.ID], [.NAME], [.AGE], [.SEX], [.S_DATE], [.CLASS], [.RN]), filter([.RN = 1]), rowset=256 |
  33. | access([.RN], [.ID], [.NAME], [.AGE], [.SEX], [.S_DATE], [.CLASS]) |
  34. | 1 - output([S.ID], [S.NAME], [S.AGE], [S.SEX], [S.S_DATE], [S.CLASS], [T_WIN_FUN_ROW_NUMBER()]), filter(nil), rowset=256 |
  35. | win_expr(T_WIN_FUN_ROW_NUMBER()), partition_by([S.CLASS]), order_by([S.S_DATE, DESC]), window_type(RANGE), upper(UNBOUNDED PRECEDING), lower(UNBOUNDED |
  36. | FOLLOWING) |
  37. | 2 - output([S.ID], [S.NAME], [S.AGE], [S.SEX], [S.S_DATE], [S.CLASS]), filter(nil), rowset=256 |
  38. | sort_keys([HASH(S.CLASS), ASC], [S.CLASS, ASC], [S.S_DATE, DESC]) |
  39. | 3 - output([S.ID], [S.NAME], [S.AGE], [S.SEX], [S.S_DATE], [S.CLASS]), filter(nil), rowset=256 |
  40. | access([S.ID], [S.NAME], [S.AGE], [S.SEX], [S.S_DATE], [S.CLASS]), partitions(p0) |
  41. | is_index_back=false, is_global_index=false, |
  42. | range_key([S.ID]), range(MIN ; MAX)always true |
  43. +---------------------------------------------------------------------------------------------------------------------------------------------------------------+
  44. 21 rows in set (0.004 sec)

1.1、改写方式一

改写窗口函数的逻辑,必须要将表关联2次或者2次+,需要将另外一张关联的表理解成一个“滑动”的窗口。
一般有两种改法:

  • 方法1、相同表 join 两次。
  • 方法2、表量子查询实现,将子查询的那张表理解成一个“滑动”的窗口。
  1. SELECT s.* FROM student s -- 原表
  2. INNER JOIN
  3. (
  4. SELECT
  5. class,
  6. max(s_date) max_date
  7. FROM student
  8. GROUP BY class
  9. ) s1 -- 滑动的窗口表
  10. ON decode(s.class,NULL,'aab',s.class) = decode(s1.class,NULL,'aab',s1.class)
  11. -- decode 处理 s表和s1窗口表的分组列class
  12. AND s.s_date = s1.max_date
  13. -- s表的s_date和窗口表s1的最大的日期去比较
  14. WHERE exists(
  15. SELECT 1 FROM student s2 where s1.max_date = s2.s_date GROUP BY s2.class having MAX(s2.ID) = S.ID
  16. /* exists 这段比较抽象,我也是想了一会才实现逻辑。
  17. 实现 row_number() 唯一排名的数据
  18. */
  19. );
  20. 27 rows in set (0.038 sec)
  21. +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  22. | Query Plan |
  23. +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  24. | ============================================================================================== |
  25. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  26. | ---------------------------------------------------------------------------------------------- |
  27. | |0 |SUBPLAN FILTER | |4 |28745 | |
  28. | |1 |├─NESTED-LOOP JOIN | |8 |28555 | |
  29. | |2 |│ ├─SUBPLAN SCAN |S1 |26 |27712 | |
  30. | |3 |│ └─MERGE GROUP BY | |26 |27712 | |
  31. | |4 |│ └─TABLE FULL SCAN |STUDENT(IDX_CLASS_SDATE_STUDENT)|500001 |14409 | |
  32. | |5 |│ └─DISTRIBUTED TABLE RANGE SCAN |S(IDX_S_DATE_CALSS_ID) |1 |32 | |
  33. | |6 |└─LIMIT | |1 |27 | |
  34. | |7 | └─MERGE GROUP BY | |1 |27 | |
  35. | |8 | └─DISTRIBUTED TABLE RANGE SCAN|S2(IDX_S_DATE_CALSS_ID) |56 |23 | |
  36. | ============================================================================================== |
  37. | Outputs & filters: |
  38. | ------------------------------------- |
  39. | 0 - output([S.ID], [S.NAME], [S.AGE], [S.SEX], [S.S_DATE], [S.CLASS]), filter([(T_OP_EXISTS, subquery(1))]), rowset=256 |
  40. | exec_params_([S1.MAX_DATE(:0)], [S.ID(:1)]), onetime_exprs_(nil), init_plan_idxs_(nil), use_batch=false |
  41. | 1 - output([S1.MAX_DATE], [S.ID], [S.NAME], [S.AGE], [S.SEX], [S.S_DATE], [S.CLASS]), filter(nil), rowset=256 |
  42. | conds(nil), nl_params_([S1.CLASS(:4)], [S1.MAX_DATE(:5)]), use_batch=false |
  43. | 2 - output([S1.CLASS], [S1.MAX_DATE]), filter(nil), rowset=256 |
  44. | access([S1.CLASS], [S1.MAX_DATE]) |
  45. | 3 - output([STUDENT.CLASS], [T_FUN_MAX(STUDENT.S_DATE)]), filter(nil), rowset=256 |
  46. | group([STUDENT.CLASS]), agg_func([T_FUN_MAX(STUDENT.S_DATE)]) |
  47. | 4 - output([STUDENT.CLASS], [STUDENT.S_DATE]), filter(nil), rowset=256 |
  48. | access([STUDENT.CLASS], [STUDENT.S_DATE]), partitions(p0) |
  49. | is_index_back=false, is_global_index=false, |
  50. | range_key([STUDENT.CLASS], [STUDENT.S_DATE], [STUDENT.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true |
  51. | 5 - output([S.ID], [S.CLASS], [S.S_DATE], [S.NAME], [S.AGE], [S.SEX]), filter([ora_decode(cast(S.CLASS, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('aab', |
  52. | VARCHAR2(3 BYTE)), cast(S.CLASS, VARCHAR2(3 BYTE))) = ora_decode(cast(:4, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('aab', VARCHAR2(3 BYTE)), cast(:4, |
  53. | VARCHAR2(3 BYTE)))]), rowset=256 |
  54. | access([S.ID], [S.CLASS], [S.S_DATE], [S.NAME], [S.AGE], [S.SEX]), partitions(p0) |
  55. | is_index_back=true, is_global_index=false, filter_before_indexback[true], |
  56. | range_key([S.S_DATE], [S.CLASS], [S.ID]), range(MIN ; MAX), |
  57. | range_cond([S.S_DATE = :5]) |
  58. | 6 - output([1]), filter(nil), rowset=256 |
  59. | limit(1), offset(nil) |
  60. | 7 - output(nil), filter([T_FUN_MAX(S2.ID) = :1]), rowset=256 |
  61. | group([S2.CLASS]), agg_func([T_FUN_MAX(S2.ID)]) |
  62. | 8 - output([S2.ID], [S2.CLASS]), filter(nil), rowset=256 |
  63. | access([S2.ID], [S2.CLASS]), partitions(p0) |
  64. | is_index_back=false, is_global_index=false, |
  65. | range_key([S2.S_DATE], [S2.CLASS], [S2.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true, |
  66. | range_cond([:0 = S2.S_DATE]) |
  67. +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  68. 43 rows in set (0.008 sec)

1.2、改写方式二

  1. SELECT S1.*
  2. FROM student S1
  3. INNER JOIN
  4. (
  5. SELECT
  6. decode(class,null,'aab',class) class,
  7. MAX(S2.S_DATE) MAX_S_DATE,
  8. MAX(ID) KEEP (DENSE_RANK LAST ORDER BY S_DATE) MAX_ID
  9. FROM student S2
  10. GROUP BY S2.CLASS
  11. /*
  12. 改写逻辑:
  13. 1、decode(class,null,'aab',class) class, 提前处理null值。如果有其他案例谓词过滤条件要转换列名,
  14. 例如:upper(列名) = upper(:1) ,可以提前在先写个select括起来内表处理字段,弄个别名,然后外面再过滤。
  15. select * from (
  16. select upper(col) col1 from tb
  17. ) where col1 = upper(:1);
  18. 2、MAX(S2.S_DATE) MAX_S_DATE 不多说,找最大日期,等价 desc
  19. 3、MAX(ID) KEEP (DENSE_RANK LAST ORDER BY S_DATE) MAX_ID 等价于上个sql 的 exists逻辑,
  20. 利用keep 唯一的特性,取出不同组里面最大的 id + ORDER BY S_DATE LAST 最后的唯一数据。
  21. 相当于 MAX_S_DATE,和 MAX(id) keep 两个列的数据都是一条 ,相当于rn=1。
  22. */
  23. ) S2
  24. ON decode(S1.CLASS,null,'aab',S1.CLASS) = S2.CLASS /* 处理关联列 */
  25. AND S1.ID = S2.MAX_ID ;
  26. -- AND S1.S_DATE = S2.MAX_S_DATE;
  27. /* 其实用S1.ID = S2.MAX_ID 关联就行,
  28. S1.S_DATE = S2.MAX_S_DATE 这个写不写都行,如果想逻辑更严谨点可以写上。
  29. */
  30. 27 rows in set (0.114 sec)
  31. +------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  32. | Query Plan |
  33. +------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  34. | ============================================================================== |
  35. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  36. | ------------------------------------------------------------------------------ |
  37. | |0 |NESTED-LOOP JOIN | |2 |44008 | |
  38. | |1 |├─SUBPLAN SCAN |S2 |26 |43466 | |
  39. | |2 |│ └─MERGE GROUP BY | |26 |43466 | |
  40. | |3 |│ └─TABLE FULL SCAN |S2(IDX_CLASS_SDATE_STUDENT)|500001 |22423 | |
  41. | |4 |└─DISTRIBUTED TABLE GET|S1 |1 |21 | |
  42. | ============================================================================== |
  43. | Outputs & filters: |
  44. | ------------------------------------- |
  45. | 0 - output([S1.ID], [S1.NAME], [S1.AGE], [S1.SEX], [S1.S_DATE], [S1.CLASS]), filter(nil), rowset=256 |
  46. | conds(nil), nl_params_([S2.CLASS(:3)], [S2.MAX_S_DATE(:4)], [S2.MAX_ID(:5)]), use_batch=false |
  47. | 1 - output([S2.CLASS], [S2.MAX_S_DATE], [S2.MAX_ID]), filter(nil), rowset=256 |
  48. | access([S2.CLASS], [S2.MAX_S_DATE], [S2.MAX_ID]) |
  49. | 2 - output([ora_decode(cast(S2.CLASS, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('aab', VARCHAR2(3 BYTE)), cast(S2.CLASS, VARCHAR2(3 BYTE)))], |
  50. | [T_FUN_MAX(S2.S_DATE)], [T_FUN_KEEP_MAX(S2.ID) order_items(S2.S_DATE)]), filter(nil), rowset=256 |
  51. | group([S2.CLASS]), agg_func([T_FUN_MAX(S2.S_DATE)], [T_FUN_KEEP_MAX(S2.ID) order_items(S2.S_DATE)]) |
  52. | 3 - output([S2.ID], [S2.CLASS], [S2.S_DATE]), filter(nil), rowset=256 |
  53. | access([S2.ID], [S2.CLASS], [S2.S_DATE]), partitions(p0) |
  54. | is_index_back=false, is_global_index=false, |
  55. | range_key([S2.CLASS], [S2.S_DATE], [S2.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true |
  56. | 4 - output([S1.ID], [S1.CLASS], [S1.S_DATE], [S1.NAME], [S1.AGE], [S1.SEX]), filter([S1.S_DATE = :4], [ora_decode(cast(S1.CLASS, VARCHAR2(1 BYTE)), cast(NULL, |
  57. | VARCHAR2(0 )), cast('aab', VARCHAR2(3 BYTE)), cast(S1.CLASS, VARCHAR2(3 BYTE))) = :3]), rowset=256 |
  58. | access([S1.ID], [S1.CLASS], [S1.S_DATE], [S1.NAME], [S1.AGE], [S1.SEX]), partitions(p0) |
  59. | is_index_back=false, is_global_index=false, filter_before_indexback[false,false], |
  60. | range_key([S1.ID]), range(MIN ; MAX), |
  61. | range_cond([S1.ID = :5]) |
  62. +------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  63. 28 rows in set (0.011 sec)

row_number() over() 的逻辑还可以用表量子查询来实现,不过性能会更慢,实现起来很麻烦,就不在这里演示。


2、keep() 函数逻辑实现

某些数据库可能不支持keep函数,或者keep函数的性能并不理想,我们也可以尝试通过另外一种方式来实现keep函数的逻辑。

2.1、keep 形式一

  1. SELECT
  2. s1.class,
  3. max(s1.s_date) MAX_S_DATE,
  4. MAX(name) KEEP (DENSE_RANK LAST ORDER BY s1.s_date) max_name,
  5. min(s1.s_date) MIN_S_DATE,
  6. MIN(name) KEEP (DENSE_RANK FIRST ORDER BY s1.s_date) min_name
  7. FROM student s1
  8. GROUP BY
  9. s1.class;
  10. 27 rows in set (0.495 sec)
  11. +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  12. | Query Plan |
  13. +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  14. | =================================================== |
  15. | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
  16. | --------------------------------------------------- |
  17. | |0 |MERGE GROUP BY | |26 |144304 | |
  18. | |1 |└─PARTITION SORT | |500001 |107784 | |
  19. | |2 | └─TABLE FULL SCAN|S1 |500001 |22309 | |
  20. | =================================================== |
  21. | Outputs & filters: |
  22. | ------------------------------------- |
  23. | 0 - output([S1.CLASS], [T_FUN_MAX(S1.S_DATE)], [T_FUN_KEEP_MAX(S1.NAME) order_items(S1.S_DATE)], [T_FUN_MIN(S1.S_DATE)], [T_FUN_KEEP_MIN(S1.NAME) order_items(S1.S_DATE)]), filter(nil), rowset=256 |
  24. | group([S1.CLASS]), agg_func([T_FUN_MAX(S1.S_DATE)], [T_FUN_KEEP_MAX(S1.NAME) order_items(S1.S_DATE)], [T_FUN_MIN(S1.S_DATE)], [T_FUN_KEEP_MIN(S1.NAME) |
  25. | order_items(S1.S_DATE)]) |
  26. | 1 - output([S1.CLASS], [S1.S_DATE], [S1.NAME]), filter(nil), rowset=256 |
  27. | sort_keys([HASH(S1.CLASS), ASC], [S1.CLASS, ASC]) |
  28. | 2 - output([S1.CLASS], [S1.S_DATE], [S1.NAME]), filter(nil), rowset=256 |
  29. | access([S1.CLASS], [S1.S_DATE], [S1.NAME]), partitions(p0) |
  30. | is_index_back=false, is_global_index=false, |
  31. | range_key([S1.ID]), range(MIN ; MAX)always true |
  32. +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  33. 18 rows in set (0.004 sec)

2.2、keep 形式一改写标量子查询

改写逻辑很简单,先分组找出最大、最小的 日期,然后通过日期 + class 外部标量子查询找出最大、最小的name。

  1. SELECT
  2. CLASS,
  3. MAX_S_DATE,
  4. (SELECT max(NAME) FROM student s2 WHERE
  5. decode(s1.CLASS,null,'bb',s1.CLASS) = decode(s2.CLASS,null,'bb',s2.CLASS) AND s2.S_DATE = s1.MAX_S_DATE) max_name,
  6. MIN_S_DATE,
  7. (SELECT min(NAME) FROM student s2 WHERE
  8. decode(s1.CLASS,null,'bb',s1.CLASS) = decode(s2.CLASS,null,'bb',s2.CLASS) AND s2.S_DATE = s1.MIN_S_DATE) min_name
  9. FROM (
  10. SELECT
  11. CLASS,
  12. max(S_DATE) MAX_S_DATE,
  13. min(S_DATE) MIN_S_DATE
  14. FROM student
  15. GROUP BY
  16. CLASS
  17. ) s1 ;
  18. 27 rows in set (0.040 sec)
  19. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  20. | Query Plan |
  21. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  22. | ============================================================================================ |
  23. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  24. | -------------------------------------------------------------------------------------------- |
  25. | |0 |SUBPLAN FILTER | |26 |36802 | |
  26. | |1 |├─MERGE GROUP BY | |26 |35450 | |
  27. | |2 |│ └─TABLE FULL SCAN |STUDENT(IDX_CLASS_SDATE_STUDENT)|500001 |14409 | |
  28. | |3 |├─SCALAR GROUP BY | |1 |26 | |
  29. | |4 |│ └─DISTRIBUTED TABLE RANGE SCAN|S2(IDX_S_DATE_CALSS_ID) |1 |26 | |
  30. | |5 |└─SCALAR GROUP BY | |1 |26 | |
  31. | |6 | └─DISTRIBUTED TABLE RANGE SCAN|S2(IDX_S_DATE_CALSS_ID) |1 |26 | |
  32. | ============================================================================================ |
  33. | Outputs & filters: |
  34. | ------------------------------------- |
  35. | 0 - output([STUDENT.CLASS], [T_FUN_MAX(STUDENT.S_DATE)], [subquery(1)], [T_FUN_MIN(STUDENT.S_DATE)], [subquery(2)]), filter(nil), rowset=256 |
  36. | exec_params_([STUDENT.CLASS(:0)], [T_FUN_MAX(STUDENT.S_DATE)(:1)], [STUDENT.CLASS(:2)], [T_FUN_MIN(STUDENT.S_DATE)(:3)]), onetime_exprs_(nil), init_plan_idxs_(nil), |
  37. | use_batch=false |
  38. | 1 - output([STUDENT.CLASS], [T_FUN_MAX(STUDENT.S_DATE)], [T_FUN_MIN(STUDENT.S_DATE)]), filter(nil), rowset=256 |
  39. | group([STUDENT.CLASS]), agg_func([T_FUN_MAX(STUDENT.S_DATE)], [T_FUN_MIN(STUDENT.S_DATE)]) |
  40. | 2 - output([STUDENT.CLASS], [STUDENT.S_DATE]), filter(nil), rowset=256 |
  41. | access([STUDENT.CLASS], [STUDENT.S_DATE]), partitions(p0) |
  42. | is_index_back=false, is_global_index=false, |
  43. | range_key([STUDENT.CLASS], [STUDENT.S_DATE], [STUDENT.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true |
  44. | 3 - output([T_FUN_MAX(S2.NAME)]), filter(nil), rowset=256 |
  45. | group(nil), agg_func([T_FUN_MAX(S2.NAME)]) |
  46. | 4 - output([S2.NAME]), filter([ora_decode(cast(:0, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('bb', VARCHAR2(2 BYTE)), cast(:0, VARCHAR2(2 BYTE))) |
  47. | = ora_decode(cast(S2.CLASS, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('bb', VARCHAR2(2 BYTE)), cast(S2.CLASS, VARCHAR2(2 BYTE)))]), rowset=256 |
  48. | access([S2.ID], [S2.CLASS], [S2.NAME]), partitions(p0) |
  49. | is_index_back=true, is_global_index=false, filter_before_indexback[true], |
  50. | range_key([S2.S_DATE], [S2.CLASS], [S2.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true, |
  51. | range_cond([S2.S_DATE = :1]) |
  52. | 5 - output([T_FUN_MIN(S2.NAME)]), filter(nil), rowset=256 |
  53. | group(nil), agg_func([T_FUN_MIN(S2.NAME)]) |
  54. | 6 - output([S2.NAME]), filter([ora_decode(cast(:2, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('bb', VARCHAR2(2 BYTE)), cast(:2, VARCHAR2(2 BYTE))) |
  55. | = ora_decode(cast(S2.CLASS, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('bb', VARCHAR2(2 BYTE)), cast(S2.CLASS, VARCHAR2(2 BYTE)))]), rowset=256 |
  56. | access([S2.ID], [S2.CLASS], [S2.NAME]), partitions(p0) |
  57. | is_index_back=true, is_global_index=false, filter_before_indexback[true], |
  58. | range_key([S2.S_DATE], [S2.CLASS], [S2.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true, |
  59. | range_cond([S2.S_DATE = :3]) |
  60. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  61. 38 rows in set (0.008 sec)

2.3、keep 形式一改写left join

改成 left join 也是按照标量的逻辑,都比较简单。

  1. SELECT s1.CLASS,
  2. s1.MAX_S_DATE,
  3. max(CASE WHEN s2.S_DATE = s1.MAX_S_DATE THEN s2.NAME END) max_name,
  4. s1.MIN_S_DATE,
  5. min(CASE WHEN s2.S_DATE = s1.MIN_S_DATE THEN s2.NAME END ) min_name
  6. FROM (
  7. SELECT
  8. CLASS,
  9. max(S_DATE) MAX_S_DATE,
  10. min(S_DATE) MIN_S_DATE
  11. FROM student
  12. GROUP BY
  13. CLASS
  14. )s1 LEFT JOIN student s2 ON decode(s1.CLASS,null,'bb',s1.CLASS) = decode(s2.CLASS,null,'bb',s2.CLASS)
  15. GROUP BY
  16. s1.CLASS,
  17. s1.MAX_S_DATE,
  18. s1.MIN_S_DATE;
  19. 27 rows in set (0.213 sec)
  20. +--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  21. | Query Plan |
  22. +--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  23. | =================================================================================== |
  24. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  25. | ----------------------------------------------------------------------------------- |
  26. | |0 |HASH GROUP BY | |17 |200679 | |
  27. | |1 |└─HASH OUTER JOIN | |65001 |193221 | |
  28. | |2 | ├─SUBPLAN SCAN |S1 |26 |35450 | |
  29. | |3 | └─MERGE GROUP BY | |26 |35450 | |
  30. | |4 | └─TABLE FULL SCAN|STUDENT(IDX_CLASS_SDATE_STUDENT)|500001 |14409 | |
  31. | |5 | └─TABLE FULL SCAN |S2 |500001 |22309 | |
  32. | =================================================================================== |
  33. | Outputs & filters: |
  34. | ------------------------------------- |
  35. | 0 - output([S1.CLASS], [S1.MAX_S_DATE], [T_FUN_MAX(CASE WHEN S2.S_DATE = S1.MAX_S_DATE THEN S2.NAME ELSE NULL END)], [S1.MIN_S_DATE], [T_FUN_MIN(CASE |
  36. | WHEN S2.S_DATE = S1.MIN_S_DATE THEN S2.NAME ELSE NULL END)]), filter(nil), rowset=256 |
  37. | group([S1.CLASS]), agg_func([T_FUN_MAX(CASE WHEN S2.S_DATE = S1.MAX_S_DATE THEN S2.NAME ELSE NULL END)], [T_FUN_MIN(CASE WHEN S2.S_DATE = S1.MIN_S_DATE |
  38. | THEN S2.NAME ELSE NULL END)]) |
  39. | 1 - output([S1.CLASS], [S2.S_DATE], [S2.NAME], [S1.MAX_S_DATE], [S1.MIN_S_DATE]), filter(nil), rowset=256 |
  40. | equal_conds([ora_decode(cast(S1.CLASS, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('bb', VARCHAR2(2 BYTE)), cast(S1.CLASS, VARCHAR2(2 BYTE))) |
  41. | = ora_decode(cast(S2.CLASS, VARCHAR2(1 BYTE)), cast(NULL, VARCHAR2(0 )), cast('bb', VARCHAR2(2 BYTE)), cast(S2.CLASS, VARCHAR2(2 BYTE)))]), other_conds(nil) |
  42. | 2 - output([S1.CLASS], [S1.MAX_S_DATE], [S1.MIN_S_DATE]), filter(nil), rowset=256 |
  43. | access([S1.CLASS], [S1.MAX_S_DATE], [S1.MIN_S_DATE]) |
  44. | 3 - output([STUDENT.CLASS], [T_FUN_MAX(STUDENT.S_DATE)], [T_FUN_MIN(STUDENT.S_DATE)]), filter(nil), rowset=256 |
  45. | group([STUDENT.CLASS]), agg_func([T_FUN_MAX(STUDENT.S_DATE)], [T_FUN_MIN(STUDENT.S_DATE)]) |
  46. | 4 - output([STUDENT.CLASS], [STUDENT.S_DATE]), filter(nil), rowset=256 |
  47. | access([STUDENT.CLASS], [STUDENT.S_DATE]), partitions(p0) |
  48. | is_index_back=false, is_global_index=false, |
  49. | range_key([STUDENT.CLASS], [STUDENT.S_DATE], [STUDENT.ID]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true |
  50. | 5 - output([S2.CLASS], [S2.S_DATE], [S2.NAME]), filter(nil), rowset=256 |
  51. | access([S2.CLASS], [S2.S_DATE], [S2.NAME]), partitions(p0) |
  52. | is_index_back=false, is_global_index=false, |
  53. | range_key([S2.ID]), range(MIN ; MAX)always true |
  54. +--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  55. 31 rows in set (0.005 sec)

2.4、keep 形式二

  1. SELECT
  2. DEPARTMENT_ID,
  3. HIRE_DATE,
  4. MAX(FIRST_NAME) KEEP (DENSE_RANK LAST ORDER BY HIRE_DATE) over(PARTITION BY DEPARTMENT_ID) max_name,
  5. MIN(FIRST_NAME) KEEP (DENSE_RANK FIRST ORDER BY HIRE_DATE) over(PARTITION BY DEPARTMENT_ID) min_name
  6. FROM EMPLOYEES ;
  7. 107 rows in set (0.002 sec)

2.5、keep 形式二改写left join

  1. SELECT
  2. e1.DEPARTMENT_ID,
  3. e1.HIRE_DATE,
  4. e2.max_name,
  5. e2.min_name
  6. FROM EMPLOYEES e1 LEFT JOIN (
  7. SELECT DEPARTMENT_ID,
  8. MAX(CASE WHEN max_rn = 1 THEN FIRST_NAME ELSE NULL END) max_name,
  9. MIN(CASE WHEN min_rn = 1 THEN FIRST_NAME ELSE NULL END) min_name
  10. FROM (
  11. SELECT
  12. FIRST_NAME,
  13. HIRE_DATE,
  14. DEPARTMENT_ID,
  15. DENSE_RANK() OVER(PARTITION BY DEPARTMENT_ID ORDER BY HIRE_DATE DESC) max_rn,
  16. DENSE_RANK() OVER(PARTITION BY DEPARTMENT_ID ORDER BY HIRE_DATE ) min_rn
  17. FROM EMPLOYEES
  18. )
  19. GROUP BY DEPARTMENT_ID
  20. ) e2 ON // 最后关联
  21. decode(e1.DEPARTMENT_ID, null, 'aaa', e1.DEPARTMENT_ID) =
  22. decode(e2.DEPARTMENT_ID, null, 'aaa', e2.DEPARTMENT_ID);
  23. 107 rows in set (0.007 sec)

2.6、keep 形式二改写标量子查询

  1. SELECT DEPARTMENT_ID,
  2. HIRE_DATE,
  3. (SELECT e2.max_name
  4. FROM
  5. (SELECT DEPARTMENT_ID,
  6. max(FIRST_NAME) max_name
  7. FROM
  8. (SELECT x.*,
  9. DENSE_RANK() OVER(PARTITION BY DEPARTMENT_ID
  10. ORDER BY HIRE_DATE DESC) max_rn
  11. FROM EMPLOYEES x )
  12. WHERE max_rn = 1
  13. GROUP BY DEPARTMENT_ID ) e2
  14. WHERE decode(e1.department_id, null, 'aaa', e1.department_id) = decode(e2.department_id, null, 'aaa', e2.department_id) ) max_name,
  15. (SELECT e2.min_name
  16. FROM
  17. (SELECT DEPARTMENT_ID,
  18. min(FIRST_NAME) min_name
  19. FROM
  20. (SELECT x.*,
  21. DENSE_RANK() OVER(PARTITION BY DEPARTMENT_ID
  22. ORDER BY HIRE_DATE ) min_rn
  23. FROM EMPLOYEES x )
  24. WHERE min_rn = 1
  25. GROUP BY DEPARTMENT_ID ) e2
  26. WHERE decode(e1.department_id, null, 'aaa', e1.department_id) = decode(e2.department_id, null, 'aaa', e2.department_id) ) min_name
  27. FROM EMPLOYEES e1;
  28. 107 rows in set (0.030 sec)
  29. /* 改SQL 逻辑等价上面join sql, 如果会写join版本改成表量子查询是更简单的操作 */

3、rank() over() 函数逻辑实现

rank() over() 函数会跳号,如果符合over()逻辑的数据有两条的话,那么这两条会并列第一,然后第二条数据会加二(即显示第三)。
这个函数在业务逻辑上比较少使用到,但是我们也可以尝试使用其他逻辑来实现rank() over() 函数的功能。
**** 注意:以下实现的rank() over() 逻辑的代码性能,会比原来rank() over() 的性能更差,这里只做功能实现,不考虑性能问题 ****

  1. SELECT * FROM (
  2. SELECT
  3. FIRST_NAME,
  4. DEPARTMENT_ID,
  5. HIRE_DATE,
  6. RANK() OVER(PARTITION BY DEPARTMENT_ID ORDER BY HIRE_DATE DESC) MAX_RN
  7. FROM EMPLOYEES ) WHERE DEPARTMENT_ID = 80;
  8. 34 rows in set (0.004 sec)
  9. +--------------------------------------------------------------------------------------------------------------------------------------------------------------+
  10. | Query Plan |
  11. +--------------------------------------------------------------------------------------------------------------------------------------------------------------+
  12. | ======================================================== |
  13. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  14. | -------------------------------------------------------- |
  15. | |0 |WINDOW FUNCTION | |34 |42 | |
  16. | |1 |└─SORT | |34 |16 | |
  17. | |2 | └─TABLE FULL SCAN|EMPLOYEES|34 |10 | |
  18. | ======================================================== |
  19. | Outputs & filters: |
  20. | ------------------------------------- |
  21. | 0 - output([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE], [T_WIN_FUN_RANK()]), filter(nil), rowset=256 |
  22. | win_expr(T_WIN_FUN_RANK()), partition_by(nil), order_by([EMPLOYEES.HIRE_DATE, DESC]), window_type(RANGE), upper(UNBOUNDED PRECEDING), lower(UNBOUNDED |
  23. | FOLLOWING) |
  24. | 1 - output([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE]), filter(nil), rowset=256 |
  25. | sort_keys([EMPLOYEES.HIRE_DATE, DESC]) |
  26. | 2 - output([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE]), filter([EMPLOYEES.DEPARTMENT_ID = 80]), rowset=256 |
  27. | access([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE]), partitions(p0) |
  28. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  29. | range_key([EMPLOYEES.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  30. +--------------------------------------------------------------------------------------------------------------------------------------------------------------+
  31. 18 rows in set (0.004 sec)

3.1、改写方式一:标量子查询逻辑实现

  1. SELECT
  2. e.FIRST_NAME,
  3. e.DEPARTMENT_ID,
  4. e.HIRE_DATE,
  5. (
  6. SELECT count(HIRE_DATE) + 1 FROM EMPLOYEES e1 WHERE
  7. DECODE(e1.DEPARTMENT_ID,NULL,'8080',e1.DEPARTMENT_ID) = DECODE(e.DEPARTMENT_ID,NULL,'8080',e.DEPARTMENT_ID) AND
  8. e1.HIRE_DATE > e.HIRE_DATE
  9. ) MAX_RN
  10. FROM EMPLOYEES e WHERE e.DEPARTMENT_ID = 80;
  11. /*
  12. 重点是在标量子查询内的 e1.HIRE_DATE > e.HIRE_DATE 逻辑,可以将 e1 表理解成一个滑动的窗口。
  13. */
  14. 34 rows in set (0.006 sec)
  15. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  16. | Query Plan |
  17. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  18. | ========================================================= |
  19. | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
  20. | --------------------------------------------------------- |
  21. | |0 |MERGE GROUP BY | |34 |29 | |
  22. | |1 |└─NESTED-LOOP OUTER JOIN | |34 |22 | |
  23. | |2 | ├─TABLE FULL SCAN |E |34 |11 | |
  24. | |3 | └─MATERIAL | |1 |11 | |
  25. | |4 | └─TABLE FULL SCAN |E1 |1 |11 | |
  26. | ========================================================= |
  27. | Outputs & filters: |
  28. | ------------------------------------- |
  29. | 0 - output([E.FIRST_NAME], [E.DEPARTMENT_ID], [E.HIRE_DATE], [T_FUN_COUNT(E1.HIRE_DATE) + 1]), filter(nil), rowset=256 |
  30. | group([E.EMPLOYEE_ID]), agg_func([T_FUN_COUNT(E1.HIRE_DATE)]) |
  31. | 1 - output([E.EMPLOYEE_ID], [E.FIRST_NAME], [E.DEPARTMENT_ID], [E.HIRE_DATE], [E1.HIRE_DATE]), filter(nil), rowset=256 |
  32. | conds([E1.HIRE_DATE > E.HIRE_DATE]), nl_params_(nil), use_batch=false |
  33. | 2 - output([E.EMPLOYEE_ID], [E.DEPARTMENT_ID], [E.FIRST_NAME], [E.HIRE_DATE]), filter([E.DEPARTMENT_ID = 80]), rowset=256 |
  34. | access([E.EMPLOYEE_ID], [E.DEPARTMENT_ID], [E.FIRST_NAME], [E.HIRE_DATE]), partitions(p0) |
  35. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  36. | range_key([E.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  37. | 3 - output([E1.HIRE_DATE]), filter(nil), rowset=256 |
  38. | 4 - output([E1.HIRE_DATE]), filter([ora_decode(cast(E1.DEPARTMENT_ID, VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', VARCHAR2(40 BYTE)), |
  39. | cast(E1.DEPARTMENT_ID, VARCHAR2(40 BYTE))) = ora_decode(cast(cast(80, NUMBER(4, 0)), VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', VARCHAR2(40 |
  40. | BYTE)), cast(cast(80, NUMBER(4, 0)), VARCHAR2(40 BYTE)))]), rowset=256 |
  41. | access([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), partitions(p0) |
  42. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  43. | range_key([E1.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  44. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  45. 26 rows in set (0.006 sec)

3.2、改写方式二:LEFT JOIN 逻辑实现

会写标量子查询的情况下,改写成LEFT JOIN 是很简单的事,逻辑一样不再赘述。

  1. SELECT
  2. e.FIRST_NAME,
  3. e.DEPARTMENT_ID,
  4. e.HIRE_DATE,
  5. count(e1.HIRE_DATE) + 1 MAX_RN
  6. FROM EMPLOYEES e LEFT JOIN EMPLOYEES e1 ON (
  7. DECODE(e1.DEPARTMENT_ID,NULL,'8080',e1.DEPARTMENT_ID) = DECODE(e.DEPARTMENT_ID,NULL,'8080',e.DEPARTMENT_ID) AND e1.HIRE_DATE > e.HIRE_DATE)
  8. WHERE e.DEPARTMENT_ID = 80
  9. GROUP BY
  10. e.FIRST_NAME,
  11. e.DEPARTMENT_ID,
  12. e.HIRE_DATE;
  13. 34 rows in set (0.012 sec)
  14. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  15. | Query Plan |
  16. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  17. | ========================================================== |
  18. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  19. | ---------------------------------------------------------- |
  20. | |0 |HASH GROUP BY | |34 |49 | |
  21. | |1 |└─NESTED-LOOP OUTER JOIN | |34 |33 | |
  22. | |2 | ├─SUBPLAN SCAN |VIEW1|34 |22 | |
  23. | |3 | └─HASH GROUP BY | |34 |22 | |
  24. | |4 | └─TABLE FULL SCAN |E |34 |10 | |
  25. | |5 | └─MATERIAL | |1 |11 | |
  26. | |6 | └─SUBPLAN SCAN |VIEW2|1 |11 | |
  27. | |7 | └─HASH GROUP BY | |1 |11 | |
  28. | |8 | └─TABLE FULL SCAN|E1 |1 |11 | |
  29. | ========================================================== |
  30. | Outputs & filters: |
  31. | ------------------------------------- |
  32. | 0 - output([VIEW1.E.FIRST_NAME], [VIEW1.E.DEPARTMENT_ID], [VIEW1.E.HIRE_DATE], [T_FUN_COUNT_SUM(VIEW1.T_FUN_COUNT(*) * CASE WHEN VIEW2.T_FUN_COUNT(E1.HIRE_DATE) |
  33. | IS NOT NULL THEN VIEW2.T_FUN_COUNT(E1.HIRE_DATE) ELSE 0 END) + 1]), filter(nil), rowset=256 |
  34. | group([VIEW1.E.HIRE_DATE], [VIEW1.E.FIRST_NAME]), agg_func([T_FUN_COUNT_SUM(VIEW1.T_FUN_COUNT(*) * CASE WHEN VIEW2.T_FUN_COUNT(E1.HIRE_DATE) IS NOT |
  35. | NULL THEN VIEW2.T_FUN_COUNT(E1.HIRE_DATE) ELSE 0 END)]) |
  36. | 1 - output([VIEW1.E.HIRE_DATE], [VIEW1.E.FIRST_NAME], [VIEW1.E.DEPARTMENT_ID], [VIEW2.T_FUN_COUNT(E1.HIRE_DATE)], [VIEW1.T_FUN_COUNT(*)]), filter(nil), rowset=256 |
  37. | conds([VIEW2.E1.HIRE_DATE > VIEW1.E.HIRE_DATE]), nl_params_(nil), use_batch=false |
  38. | 2 - output([VIEW1.E.DEPARTMENT_ID], [VIEW1.E.HIRE_DATE], [VIEW1.E.FIRST_NAME], [VIEW1.T_FUN_COUNT(*)]), filter(nil), rowset=256 |
  39. | access([VIEW1.E.DEPARTMENT_ID], [VIEW1.E.HIRE_DATE], [VIEW1.E.FIRST_NAME], [VIEW1.T_FUN_COUNT(*)]) |
  40. | 3 - output([E.DEPARTMENT_ID], [E.HIRE_DATE], [E.FIRST_NAME], [T_FUN_COUNT(*)]), filter(nil), rowset=256 |
  41. | group([E.HIRE_DATE], [E.FIRST_NAME]), agg_func([T_FUN_COUNT(*)]) |
  42. | 4 - output([E.DEPARTMENT_ID], [E.HIRE_DATE], [E.FIRST_NAME]), filter([E.DEPARTMENT_ID = 80]), rowset=256 |
  43. | access([E.DEPARTMENT_ID], [E.HIRE_DATE], [E.FIRST_NAME]), partitions(p0) |
  44. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  45. | range_key([E.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  46. | 5 - output([VIEW2.T_FUN_COUNT(E1.HIRE_DATE)], [VIEW2.E1.HIRE_DATE]), filter(nil), rowset=256 |
  47. | 6 - output([VIEW2.E1.HIRE_DATE], [VIEW2.T_FUN_COUNT(E1.HIRE_DATE)]), filter(nil), rowset=256 |
  48. | access([VIEW2.E1.HIRE_DATE], [VIEW2.T_FUN_COUNT(E1.HIRE_DATE)]) |
  49. | 7 - output([E1.HIRE_DATE], [T_FUN_COUNT(E1.HIRE_DATE)]), filter(nil), rowset=256 |
  50. | group([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), agg_func([T_FUN_COUNT(E1.HIRE_DATE)]) |
  51. | 8 - output([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), filter([ora_decode(cast(E1.DEPARTMENT_ID, VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', |
  52. | VARCHAR2(40 BYTE)), cast(E1.DEPARTMENT_ID, VARCHAR2(40 BYTE))) = ora_decode(cast(cast(80, NUMBER(4, 0)), VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), |
  53. | cast('8080', VARCHAR2(40 BYTE)), cast(cast(80, NUMBER(4, 0)), VARCHAR2(40 BYTE)))]), rowset=256 |
  54. | access([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), partitions(p0) |
  55. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  56. | range_key([E1.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  57. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  58. 40 rows in set (0.011 sec)

4、 dense_rank() over() 函数逻辑实现

dense_rank() over() 函数不会跳号,如果符合over()逻辑的数据有两条的话,那么这两条会并列第一,然后第二条数据会递增加一(即显示第二)。
这个函数在业务逻辑上也比较少用到,但是比rank()用得多,但是我们也可以尝试使用其他逻辑来实现dense_rank() over() 函数的功能。
**** 注意:以下实现的dense_rank() over() 逻辑的代码性能,会比原来dense_rank() over() 的性能更差,这里只做功能实现,不考虑性能问题 ****

  1. SELECT * FROM (
  2. SELECT
  3. FIRST_NAME,
  4. DEPARTMENT_ID,
  5. HIRE_DATE,
  6. dense_rank() OVER(PARTITION BY DEPARTMENT_ID ORDER BY HIRE_DATE DESC) MAX_RN
  7. FROM EMPLOYEES ) WHERE DEPARTMENT_ID = 80;
  8. 34 rows in set (0.001 sec)
  9. +--------------------------------------------------------------------------------------------------------------------------------------------------------------+
  10. | Query Plan |
  11. +--------------------------------------------------------------------------------------------------------------------------------------------------------------+
  12. | ======================================================== |
  13. | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
  14. | -------------------------------------------------------- |
  15. | |0 |WINDOW FUNCTION | |107 |115 | |
  16. | |1 |└─PARTITION SORT | |107 |33 | |
  17. | |2 | └─TABLE FULL SCAN|EMPLOYEES|107 |9 | |
  18. | ======================================================== |
  19. | Outputs & filters: |
  20. | ------------------------------------- |
  21. | 0 - output([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE], [T_WIN_FUN_DENSE_RANK()]), filter(nil), rowset=256 |
  22. | win_expr(T_WIN_FUN_DENSE_RANK()), partition_by([EMPLOYEES.DEPARTMENT_ID]), order_by([EMPLOYEES.HIRE_DATE, DESC]), window_type(RANGE), upper(UNBOUNDED |
  23. | PRECEDING), lower(UNBOUNDED FOLLOWING) |
  24. | 1 - output([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE]), filter(nil), rowset=256 |
  25. | sort_keys([HASH(EMPLOYEES.DEPARTMENT_ID), ASC], [EMPLOYEES.DEPARTMENT_ID, ASC], [EMPLOYEES.HIRE_DATE, DESC]) |
  26. | 2 - output([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE]), filter(nil), rowset=256 |
  27. | access([EMPLOYEES.FIRST_NAME], [EMPLOYEES.DEPARTMENT_ID], [EMPLOYEES.HIRE_DATE]), partitions(p0) |
  28. | is_index_back=false, is_global_index=false, |
  29. | range_key([EMPLOYEES.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  30. +--------------------------------------------------------------------------------------------------------------------------------------------------------------+
  31. 18 rows in set (0.004 sec)

4.1、改写方式一:标量子查询逻辑实现

  1. SELECT
  2. e.FIRST_NAME,
  3. e.DEPARTMENT_ID,
  4. e.HIRE_DATE,
  5. (SELECT COUNT(DISTINCT HIRE_DATE) +1 FROM EMPLOYEES e1 WHERE DECODE(e1.DEPARTMENT_ID,NULL,'8080',e1.DEPARTMENT_ID) = DECODE(e.DEPARTMENT_ID,NULL,'8080',e.DEPARTMENT_ID) AND e1.HIRE_DATE > e.HIRE_DATE ) MAX_RN
  6. FROM EMPLOYEES e WHERE DEPARTMENT_ID = 80 ;
  7. /*
  8. COUNT(DISTINCT HIRE_DATE) 逻辑是求 e1.HIRE_DATE > e.HIRE_DATE 符合这个逻辑的 e1表的 HIRE_DATE 数据,等价 dense_rank。
  9. */
  10. 34 rows in set (0.006 sec)
  11. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  12. | Query Plan |
  13. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  14. | ========================================================= |
  15. | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
  16. | --------------------------------------------------------- |
  17. | |0 |MERGE GROUP BY | |34 |29 | |
  18. | |1 |└─NESTED-LOOP OUTER JOIN | |34 |22 | |
  19. | |2 | ├─TABLE FULL SCAN |E |34 |11 | |
  20. | |3 | └─MATERIAL | |1 |11 | |
  21. | |4 | └─TABLE FULL SCAN |E1 |1 |11 | |
  22. | ========================================================= |
  23. | Outputs & filters: |
  24. | ------------------------------------- |
  25. | 0 - output([E.FIRST_NAME], [E.DEPARTMENT_ID], [E.HIRE_DATE], [T_FUN_COUNT(distinct E1.HIRE_DATE) + 1]), filter(nil), rowset=256 |
  26. | group([E.EMPLOYEE_ID]), agg_func([T_FUN_COUNT(distinct E1.HIRE_DATE)]) |
  27. | 1 - output([E.EMPLOYEE_ID], [E.FIRST_NAME], [E.DEPARTMENT_ID], [E.HIRE_DATE], [E1.HIRE_DATE]), filter(nil), rowset=256 |
  28. | conds([E1.HIRE_DATE > E.HIRE_DATE]), nl_params_(nil), use_batch=false |
  29. | 2 - output([E.EMPLOYEE_ID], [E.DEPARTMENT_ID], [E.FIRST_NAME], [E.HIRE_DATE]), filter([E.DEPARTMENT_ID = 80]), rowset=256 |
  30. | access([E.EMPLOYEE_ID], [E.DEPARTMENT_ID], [E.FIRST_NAME], [E.HIRE_DATE]), partitions(p0) |
  31. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  32. | range_key([E.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  33. | 3 - output([E1.HIRE_DATE]), filter(nil), rowset=256 |
  34. | 4 - output([E1.HIRE_DATE]), filter([ora_decode(cast(E1.DEPARTMENT_ID, VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', VARCHAR2(40 BYTE)), |
  35. | cast(E1.DEPARTMENT_ID, VARCHAR2(40 BYTE))) = ora_decode(cast(cast(80, NUMBER(4, 0)), VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', VARCHAR2(40 |
  36. | BYTE)), cast(cast(80, NUMBER(4, 0)), VARCHAR2(40 BYTE)))]), rowset=256 |
  37. | access([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), partitions(p0) |
  38. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  39. | range_key([E1.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  40. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  41. 26 rows in set (0.006 sec)

4.2、改写方式二:LEFT JOIN 逻辑实现

会写标量子查询的情况下,改写成LEFT JOIN 是很简单的事,逻辑一样不再赘述。

  1. SELECT
  2. e.FIRST_NAME,
  3. e.DEPARTMENT_ID,
  4. e.HIRE_DATE,
  5. count(DISTINCT e1.HIRE_DATE) + 1 MAX_RN
  6. FROM EMPLOYEES e LEFT JOIN EMPLOYEES e1 ON (
  7. DECODE(e1.DEPARTMENT_ID,NULL,'8080',e1.DEPARTMENT_ID) = DECODE(e.DEPARTMENT_ID,NULL,'8080',e.DEPARTMENT_ID) AND e1.HIRE_DATE > e.HIRE_DATE)
  8. WHERE e.DEPARTMENT_ID = 80
  9. GROUP BY
  10. e.FIRST_NAME,
  11. e.DEPARTMENT_ID,
  12. e.HIRE_DATE;
  13. 34 rows in set (0.006 sec)
  14. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  15. | Query Plan |
  16. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  17. | ===================================================== |
  18. | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
  19. | ----------------------------------------------------- |
  20. | |0 |MERGE GROUP BY | |34 |76 | |
  21. | |1 |└─PARTITION SORT | |34 |70 | |
  22. | |2 | └─HASH OUTER JOIN | |34 |62 | |
  23. | |3 | ├─TABLE FULL SCAN|E |34 |10 | |
  24. | |4 | └─TABLE FULL SCAN|E1 |107 |9 | |
  25. | ===================================================== |
  26. | Outputs & filters: |
  27. | ------------------------------------- |
  28. | 0 - output([E.FIRST_NAME], [E.DEPARTMENT_ID], [E.HIRE_DATE], [T_FUN_COUNT(distinct E1.HIRE_DATE) + 1]), filter(nil), rowset=256 |
  29. | group([E.FIRST_NAME], [E.HIRE_DATE]), agg_func([T_FUN_COUNT(distinct E1.HIRE_DATE)]) |
  30. | 1 - output([E.FIRST_NAME], [E.HIRE_DATE], [E.DEPARTMENT_ID], [E1.HIRE_DATE]), filter(nil), rowset=256 |
  31. | sort_keys([HASH(E.FIRST_NAME, E.HIRE_DATE), ASC], [E.FIRST_NAME, ASC], [E.HIRE_DATE, ASC]) |
  32. | 2 - output([E.FIRST_NAME], [E.HIRE_DATE], [E.DEPARTMENT_ID], [E1.HIRE_DATE]), filter(nil), rowset=256 |
  33. | equal_conds([ora_decode(cast(E1.DEPARTMENT_ID, VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', VARCHAR2(40 BYTE)), cast(E1.DEPARTMENT_ID, |
  34. | VARCHAR2(40 BYTE))) = ora_decode(cast(E.DEPARTMENT_ID, VARCHAR2(1048576 )), cast(NULL, VARCHAR2(0 )), cast('8080', VARCHAR2(40 BYTE)), cast(E.DEPARTMENT_ID, |
  35. | VARCHAR2(40 BYTE)))]), other_conds([E1.HIRE_DATE > E.HIRE_DATE]) |
  36. | 3 - output([E.DEPARTMENT_ID], [E.HIRE_DATE], [E.FIRST_NAME]), filter([E.DEPARTMENT_ID = 80]), rowset=256 |
  37. | access([E.DEPARTMENT_ID], [E.HIRE_DATE], [E.FIRST_NAME]), partitions(p0) |
  38. | is_index_back=false, is_global_index=false, filter_before_indexback[false], |
  39. | range_key([E.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  40. | 4 - output([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), filter(nil), rowset=256 |
  41. | access([E1.DEPARTMENT_ID], [E1.HIRE_DATE]), partitions(p0) |
  42. | is_index_back=false, is_global_index=false, |
  43. | range_key([E1.EMPLOYEE_ID]), range(MIN ; MAX)always true |
  44. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  45. 27 rows in set (0.005 sec)

5、 总结

SQL语句虽然很简单,但是要培养出SQL思维可不是一件容易的事,还是得多看,多写,多思考。
这个是要通过不同的开发需求日积月累形成的思维逻辑,而不是一蹴而就。

原文链接:https://www.cnblogs.com/yuzhijian/p/18304241

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号