案例一

2015 年,网络优化班的学生问如何优化以下 SQL。

  1. explain plan for
  2. select gcodenameidcodeaddressnoroometime from
  3. LY_T_CHREC t where gcode in
  4. select gcode from LY_T_CHREC t where name='张三' and bdate ='19941109' a
  5. ;
  1. Explained
  1. select * from tabledbms_xplan.display(null,null,'ADVANCED -PROJECTION'));
  1. PLAN_TABLE_OUTPUT
  2. ---------------------------------------------------------------------------
  3. Plan hash value: 953100977
  4. ---------------------------------------------------------------------------
  5. | Id | Operation | Name | Rows |
  6. ---------------------------------------------------------------------------
  7. | 0 | SELECT STATEMENT | | 2 |
  8. |* 1 | HASH JOIN RIGHT SEMI | | 2 |
  9. |* 2 | TABLE ACCESS BY GLOBAL INDEX ROWID| LY_T_CHREC | 1 |
  10. |* 3 | INDEX RANGE SCAN | IDX_LY_T_CHREC_NAME | 15 |
  11. | 4 | PARTITION HASH ALL | | 200M|
  12. | 5 | TABLE ACCESS FULL | LY_T_CHREC | 200M|
  13. ---------------------------------------------------------------------------
  14. Query Block Name / Object Alias identified by operation id):
  15. -------------------------------------------------------------
  16. 1 - SEL$5DA710D3
  17. 2 - SEL$5DA710D3 / T@SEL$2
  18. 3 - SEL$5DA710D3 / T@SEL$2
  19. 5 - SEL$5DA710D3 / T@SEL$1
  20. Outline Data
  21. -------------
  22. /*+
  23. BEGIN_OUTLINE_DATA
  24. SWAP_JOIN_INPUTS(@「SEL$5DA710D3 T」@「SEL$2」)
  25. USE_HASH(@「SEL$5DA710D3 T」@「SEL$2」)
  26. LEADING(@「SEL$5DA710D3 T」@「SEL$1 T」@「SEL$2」)
  27. INDEX_RS_ASC(@「SEL$5DA710D3 T」@「SEL$2 (「LY_T_CHREC」.「NAME」))
  28. FULL(@「SEL$5DA710D3 T」@「SEL$1」)
  29. OUTLINE(@「SEL$2」)
  30. OUTLINE(@「SEL$1」)
  31. UNNEST(@「SEL$2」)
  32. OUTLINE_LEAF(@「SEL$5DA710D3」)
  33. ALL_ROWS
  34. DB_VERSION(『11.2.0.3』)
  35. OPTIMIZER_FEATURES_ENABLE(『11.2.0.3』)
  36. IGNORE_OPTIM_EMBEDDED_HINTS
  37. END_OUTLINE_DATA
  38. */
  39. Predicate Information identified by operation id):
  40. ---------------------------------------------------
  41. 1 - access(「GCODE」=「GCODE」)
  42. 2 - filter(「BDATE」='19941109'
  43. 3 - access(「NAME」='张三'

朋友提供的信息:子查询返回一个人开房的房号记录,共返回 63 行。该 SQL 就是查与某人相同的房间号的他人的记录。LY_T_CHREC 表有两亿条记录。整个 SQL 执行了 30 分钟还没出结果,子查询可以秒出结果,GCODE、NAME、IDCODE、ADDRESS、NOROOM、ETIME、BDATE 都有索引。

根据以上信息我们得出:该 SQL 主表 LY_T_CHREC 有两亿条数据,没有过滤条件,IN 子查询过滤之后返回 63 行数据,关联列是房间号(GCODE)。LY_T_CHREC 应该存放的是开房记录数据,GCODE 列基数应该比较高。在本书中我们反复强调:小表与大表关联,如果大表连接列基数比较高,可以走嵌套循环,让小表驱动大表,大表走连接列的索引。这里小表就是 IN 子查询,大表就是主表,我们让 IN 子查询作为 NL 驱动表。

  1. select /*+ leading(t@a) use_nl(t@a,t) */
  2. gcode, name, idcode, address, noroom, etime
  3. from zhxx_lgy.LY_T_CHREC t
  4. where gcode in (select /*+ qb_name(a) */
  5. gcode
  6. from zhxx_lgy.LY_T_CHREC t
  7. where name = '张三'
  8. and bdate = '19941109');

最终该 SQL 可以秒出。

案例二

2014 年,一位物流行业的朋友说以下 SQL 要执行 4 个多小时。

  1. SELECT "VOUCHER".FID "ID",
  2. "ENTRIES".FID "ENTRIES.ID",
  3. "ENTRIES".FEntryDC "ENTRIES.ENTRYDC",
  4. "ACCOUNT".FID "ACCOUNT.ID",
  5. "ENTRIES".FCurrencyID "CURRENCY.ID",
  6. "PERIOD".FNumber "PERIOD.NUMBER",
  7. "ENTRIES".FSeq "ENTRIES.SEQ",
  8. "ENTRIES".FLocalExchangeRate "LOCALEXCHANGERATE",
  9. "ENTRIES".FReportingExchangeRate "REPORTINGEXCHANGERATE",
  10. "ENTRIES".FMeasureUnitID "ENTRYMEASUREUNIT.ID",
  11. "ASSISTRECORDS".FID "ASSISTRECORDS.ID",
  12. "ASSISTRECORDS".FSeq "ASSISTRECORDS.SEQ",
  13. CASE
  14. WHEN (("ACCOUNT".FCAA IS NULL) AND
  15. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  16. "ENTRIES".FOriginalAmount
  17. ELSE
  18. "ASSISTRECORDS".FOriginalAmount
  19. END "ASSISTRECORDS.ORIGINALAMOUNT",
  20. CASE
  21. WHEN (("ACCOUNT".FCAA IS NULL) AND
  22. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  23. "ENTRIES".FLocalAmount
  24. ELSE
  25. "ASSISTRECORDS".FLocalAmount
  26. END "ASSISTRECORDS.LOCALAMOUNT",
  27. CASE
  28. WHEN (("ACCOUNT".FCAA IS NULL) AND
  29. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  30. "ENTRIES".FReportingAmount
  31. ELSE
  32. "ASSISTRECORDS".FReportingAmount
  33. END "ASSISTRECORDS.REPORTINGAMOUNT",
  34. CASE
  35. WHEN (("ACCOUNT".FCAA IS NULL) AND
  36. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  37. "ENTRIES".FQuantity
  38. ELSE
  39. "ASSISTRECORDS".FQuantity
  40. END "ASSISTRECORDS.QUANTITY",
  41. CASE
  42. WHEN (("ACCOUNT".FCAA IS NULL) AND
  43. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  44. "ENTRIES".FStandardQuantity
  45. ELSE
  46. "ASSISTRECORDS".FStandardQuantity
  47. END "ASSISTRECORDS.STANDARDQTY",
  48. CASE
  49. WHEN (("ACCOUNT".FCAA IS NULL) AND
  50. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  51. "ENTRIES".FPrice
  52. ELSE
  53. "ASSISTRECORDS".FPrice
  54. END "ASSISTRECORDS.PRICE",
  55. CASE
  56. WHEN ("ACCOUNT".FCAA IS NULL) THEN
  57. NULL
  58. ELSE
  59. "ASSISTRECORDS".FAssGrpID
  60. END "ASSGRP.ID"
  61. FROM T_GL_Voucher "VOUCHER"
  62. LEFT OUTER JOIN T_BD_Period "PERIOD" ON "VOUCHER".FPeriodID =
  63. "PERIOD".FID
  64. INNER JOIN T_GL_VoucherEntry "ENTRIES" ON "VOUCHER".FID =
  65. "ENTRIES".FBillID
  66. INNER JOIN T_BD_AccountView "ACCOUNT" ON "ENTRIES".FAccountID =
  67. "ACCOUNT".FID
  68. LEFT OUTER JOIN T_GL_VoucherAssistRecord "ASSISTRECORDS" ON "ENTRIES".FID =
  69. "ASSISTRECORDS".FEntryID
  70. WHERE "VOUCHER".FID IN
  71. (SELECT "VOUCHER".FID "ID"
  72. FROM T_GL_Voucher "VOUCHER"
  73. INNER JOIN T_GL_VoucherEntry "ENTRIES" ON "VOUCHER".FID =
  74. "ENTRIES".FBillID
  75. INNER JOIN T_BD_AccountView "ACCOUNT" ON "ENTRIES".FAccountID =
  76. "ACCOUNT".FID
  77. INNER JOIN t_bd_accountview PAV ON ((INSTR("ACCOUNT".flongnumber,
  78. pav.flongnumber) = 1 AND
  79. pav.faccounttableid =
  80. "ACCOUNT".faccounttableid) AND
  81. pav.fcompanyid =
  82. "ACCOUNT".fcompanyid)
  83. WHERE (("VOUCHER".FCompanyID IN ('fSSF82rRSKexM3KKN1d0tMznrtQ=')) AND
  84. (("VOUCHER".FBizStatus IN (5)) AND
  85. ((("VOUCHER".FPeriodID IN ('+wQxkBFVRiKnV7OniceMDoI4jEw=')) AND
  86. "ENTRIES".FCurrencyID =
  87. 'dfd38d11-00fd-1000-e000-1ebdc0a8100dDEB58FDC') AND
  88. (pav.FID IN ('vyPiKexLRXiyMb41VSVVzJ2pmCY='))))))
  89. ORDER BY "ID" ASC, "ENTRIES.SEQ" ASC, "ASSISTRECORDS.SEQ" ASC;

执行计划如下。

  1. -------------------------------------------------------------------------------------
  2. | Id |Operation |Name |Rows|Bytes|Cost (%CPU)|
  3. -------------------------------------------------------------------------------------
  4. | 0 |SELECT STATEMENT | | 13| 5733| 486 (1)|
  5. | 1 | SORT ORDER BY | | 13| 5733| 486 (1)|
  6. | 2 | VIEW |VM_NWVW_2 | 13| 5733| 486 (1)|
  7. | 3 | HASH UNIQUE | | 13|11115| 486 (1)|
  8. | 4 | NESTED LOOPS OUTER | | 13|11115| 485 (1)|
  9. | 5 | NESTED LOOPS | | 9| 6606| 471 (1)|
  10. | 6 | NESTED LOOPS | | 9| 6057| 467 (1)|
  11. | 7 | MERGE JOIN OUTER | | 1| 473| 459 (1)|
  12. | 8 | HASH JOIN | | 1| 427| 458 (1)|
  13. | 9 | NESTED LOOPS | | | | |
  14. | 10 | NESTED LOOPS | | 258|83850| 390 (0)|
  15. | 11 | NESTED LOOPS | | 6| 1332| 3 (0)|
  16. | 12 | TABLE ACCESS BY INDEX ROWID|T_BD_ACCOUNTVIEW | 1| 111| 2 (0)|
  17. | 13 | INDEX UNIQUE SCAN |PK_BD_ACCOUNTVIEW | 1| | 1 (0)|
  18. | 14 | INDEX RANGE SCAN |IX_BD_ACTCOMLNUM | 6| 666| 1 (0)|
  19. | 15 | INDEX RANGE SCAN |IX_GL_VCHAACCT | 489| | 1 (0)|
  20. | 16 | TABLE ACCESS BY INDEX ROWID |T_GL_VOUCHERENTRY | 42| 4326| 65 (0)|
  21. | 17 | INDEX RANGE SCAN |IX_GL_VCH_11 |7536| 750K| 68 (0)|
  22. | 18 | BUFFER SORT | | 1| 46| 391 (0)|
  23. | 19 | INDEX RANGE SCAN |IX_PERIOD_ENC | 1| 46| 1 (0)|
  24. | 20 | TABLE ACCESS BY INDEX ROWID |T_GL_VOUCHERENTRY | 17| 3400| 8 (0)|
  25. | 21 | INDEX RANGE SCAN |IX_GL_VCHENTRYFQ1 | 17| | 1 (0)|
  26. | 22 | TABLE ACCESS BY INDEX ROWID |T_BD_ACCOUNTVIEW | 1| 61| 1 (0)|
  27. | 23 | INDEX UNIQUE SCAN |PK_BD_ACCOUNTVIEW | 1| | 1 (0)|
  28. | 24 | TABLE ACCESS BY INDEX ROWID |T_GL_VOUCHERASSISTRECORD| 1| 121| 2 (0)|
  29. | 25 | INDEX RANGE SCAN |IX_GL_VCHASSREC_11 | 2| | 1 (0)|
  30. -------------------------------------------------------------------------------------
  31. Note
  32. -----
  33. - 'PLAN_TABLE' is old version

执行计划中居然是’PLAN_TABLE’ is old version,无法看到谓词信息,这需要重建 PLAN_TABLE。因为没有谓词信息,所以就不打算从执行计划入手优化 SQL 了,而是选择直接分析 SQL,从 SQL 层面优化。

SQL 语句中,select 到 from 之间没有标量子查询,没有自定义函数,from 后面有 5 个表关联,where 条件中只有一个 in(子查询),没有其他过滤条件。SQL 语句中用到的表大小如图 9-8 所示。

半连接反向驱动主表 - 图1

图 9-8

SQL 语句中有 4 个表都是大表,只有一个表 T_BD_PERIOD 是小表,在 SQL 语句中与 T_GL_VOUCHER 外关联,是外连接的从表。如果走嵌套循环,T_BD_PERIOD 只能作为被驱动表,因此排除了让小表 T_BD_PERIOD 作为嵌套循环驱动表的可能性。如果该 SQL 没有过滤条件,以上 SQL 只能走 HASH 连接。

SQL 语句中唯一的过滤条件就是 in(子查询),因此只能把优化 SQL 的希望寄托在子查询身上。in(子查询)与表 T_GL_VOUCHER 进行关联,T_GL_VOUCHER 同时也是外连接的主表,如果 in(子查询)能过滤掉 T_GL_VOUCHER 大量数据,那么可以让 T_GL_VOUCHER 作为嵌套循环驱动表,一直与后面的表 NL 下去,这样或许能优化 SQL。如果 in(子查询)不能过滤掉大量数据,那么 SQL 就无法优化,最终只能全走 HASH。询问 in(子查询)返回多少行,运行多久,得到反馈:in(子查询)返回 16 880 条数据,耗时 23 秒。于是我们将 SQL 改写为 with as 子句,而且固化(/+ materialize /)with as 子查询,让 with as 子句作为嵌套循环驱动表。

  1. with x as (
  2. SELECT /*+ materialize */ "VOUCHER".FID "ID"
  3. FROM T_GL_Voucher "VOUCHER"
  4. INNER JOIN T_GL_VoucherEntry "ENTRIES" ON "VOUCHER".FID =
  5. "ENTRIES".FBillID
  6. INNER JOIN T_BD_AccountView "ACCOUNT" ON "ENTRIES".FAccountID =
  7. "ACCOUNT".FID
  8. INNER JOIN t_bd_accountview PAV ON ((INSTR("ACCOUNT".flongnumber,
  9. pav.flongnumber) = 1 AND
  10. pav.faccounttableid =
  11. "ACCOUNT".faccounttableid) AND
  12. pav.fcompanyid =
  13. "ACCOUNT".fcompanyid)
  14. WHERE (("VOUCHER".FCompanyID IN ('fSSF82rRSKexM3KKN1d0tMznrtQ=')) AND
  15. (("VOUCHER".FBizStatus IN (5)) AND
  16. ((("VOUCHER".FPeriodID IN ('+wQxkBFVRiKnV7OniceMDoI4jEw=')) AND
  17. "ENTRIES".FCurrencyID =
  18. 'dfd38d11-00fd-1000-e000-1ebdc0a8100dDEB58FDC') AND
  19. (pav.FID IN ('vyPiKexLRXiyMb41VSVVzJ2pmCY=')))))
  20. )
  21. SELECT "VOUCHER".FID "ID",
  22. "ENTRIES".FID "ENTRIES.ID",
  23. "ENTRIES".FEntryDC "ENTRIES.ENTRYDC",
  24. "ACCOUNT".FID "ACCOUNT.ID",
  25. "ENTRIES".FCurrencyID "CURRENCY.ID",
  26. "PERIOD".FNumber "PERIOD.NUMBER",
  27. "ENTRIES".FSeq "ENTRIES.SEQ",
  28. "ENTRIES".FLocalExchangeRate "LOCALEXCHANGERATE",
  29. "ENTRIES".FReportingExchangeRate "REPORTINGEXCHANGERATE",
  30. "ENTRIES".FMeasureUnitID "ENTRYMEASUREUNIT.ID",
  31. "ASSISTRECORDS".FID "ASSISTRECORDS.ID",
  32. "ASSISTRECORDS".FSeq "ASSISTRECORDS.SEQ",
  33. CASE
  34. WHEN (("ACCOUNT".FCAA IS NULL) AND
  35. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  36. "ENTRIES".FOriginalAmount
  37. ELSE
  38. "ASSISTRECORDS".FOriginalAmount
  39. END "ASSISTRECORDS.ORIGINALAMOUNT",
  40. CASE
  41. WHEN (("ACCOUNT".FCAA IS NULL) AND
  42. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  43. "ENTRIES".FLocalAmount
  44. ELSE
  45. "ASSISTRECORDS".FLocalAmount
  46. END "ASSISTRECORDS.LOCALAMOUNT",
  47. CASE
  48. WHEN (("ACCOUNT".FCAA IS NULL) AND
  49. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  50. "ENTRIES".FReportingAmount
  51. ELSE
  52. "ASSISTRECORDS".FReportingAmount
  53. END "ASSISTRECORDS.REPORTINGAMOUNT",
  54. CASE
  55. WHEN (("ACCOUNT".FCAA IS NULL) AND
  56. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  57. "ENTRIES".FQuantity
  58. ELSE
  59. "ASSISTRECORDS".FQuantity
  60. END "ASSISTRECORDS.QUANTITY",
  61. CASE
  62. WHEN (("ACCOUNT".FCAA IS NULL) AND
  63. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  64. "ENTRIES".FStandardQuantity
  65. ELSE
  66. "ASSISTRECORDS".FStandardQuantity
  67. END "ASSISTRECORDS.STANDARDQTY",
  68. CASE
  69. WHEN (("ACCOUNT".FCAA IS NULL) AND
  70. ("ACCOUNT".FhasUserProperty <> 1)) THEN
  71. "ENTRIES".FPrice
  72. ELSE
  73. "ASSISTRECORDS".FPrice
  74. END "ASSISTRECORDS.PRICE",
  75. CASE
  76. WHEN ("ACCOUNT".FCAA IS NULL) THEN
  77. NULL
  78. ELSE
  79. "ASSISTRECORDS".FAssGrpID
  80. END "ASSGRP.ID"
  81. FROM T_GL_Voucher "VOUCHER"
  82. LEFT OUTER JOIN T_BD_Period "PERIOD" ON "VOUCHER".FPeriodID =
  83. "PERIOD".FID
  84. INNER JOIN T_GL_VoucherEntry "ENTRIES" ON "VOUCHER".FID =
  85. "ENTRIES".FBillID
  86. INNER JOIN T_BD_AccountView "ACCOUNT" ON "ENTRIES".FAccountID =
  87. "ACCOUNT".FID
  88. LEFT OUTER JOIN T_GL_VoucherAssistRecord "ASSISTRECORDS" ON "ENTRIES".FID =
  89. "ASSISTRECORDS".FEntryID
  90. WHERE "VOUCHER".FID IN
  91. (select id from x)
  92. ORDER BY "ID" ASC, "ENTRIES.SEQ" ASC, "ASSISTRECORDS.SEQ" ASC;

改写后的执行计划如下。

  1. -------------------------------------------------------------------------------------
  2. |Id |Operation |Name |Rows |Bytes|Cost(%CPU)|
  3. --------------------------------------------------------------------------------------------
  4. | 0|SELECT STATEMENT | | 24|11208| 506 (1)|
  5. | 1| TEMP TABLE TRANSFORMATION | | | | |
  6. | 2| LOAD AS SELECT |SYS_TEMP_0FD9D6853_1AD5C99D| | | |
  7. | 3| HASH JOIN | | 1| 415| 458 (1)|
  8. | 4| NESTED LOOPS | | | | |
  9. | 5| NESTED LOOPS | | 258|83850| 390 (0)|
  10. | 6| NESTED LOOPS | | 6| 1332| 3 (0)|
  11. | 7| TABLE ACCESS BY INDEX ROWID|T_BD_ACCOUNTVIEW | 1| 111| 2 (0)|
  12. | 8| INDEX UNIQUE SCAN |PK_BD_ACCOUNTVIEW | 1| | 1 (0)|
  13. | 9| INDEX RANGE SCAN |IX_BD_ACTCOMLNUM | 6| 666| 1 (0)|
  14. | 10| INDEX RANGE SCAN |IX_GL_VCHAACCT | 489| | 1 (0)|
  15. | 11| TABLE ACCESS BY INDEX ROWID |T_GL_VOUCHERENTRY | 42| 4326| 65 (0)|
  16. | 12| INDEX RANGE SCAN |IX_GL_VCH_11 | 7536| 662K| 68 (0)|
  17. | 13| SORT ORDER BY | | 24|11208| 48 (5)|
  18. | 14| NESTED LOOPS OUTER | | 24|11208| 47 (3)|
  19. | 15| NESTED LOOPS | | 17| 6086| 21 (5)|
  20. | 16| NESTED LOOPS | | 17| 5253| 13 (8)|
  21. | 17| NESTED LOOPS OUTER | | 1| 121| 5 (20)|
  22. | 18| NESTED LOOPS | | 1| 87| 4 (25)|
  23. | 19| VIEW |VW_NSO_1 | 1| 29| 2 (0)|
  24. | 20| HASH UNIQUE | | 1| 24| |
  25. | 21| VIEW | | 1| 24| 2 (0)|
  26. | 22| TABLE ACCESS FULL |SYS_TEMP_0FD9D6853_1AD5C99D| 1| 29| 2 (0)|
  27. | 23| INDEX RANGE SCAN |IX_GL_VCH_FIDCMPNUM | 1| 58| 1 (0)|
  28. | 24| INDEX RANGE SCAN |IX_PERIOD_ENC | 1| 34| 1 (0)|
  29. | 25| TABLE ACCESS BY INDEX ROWID|T_GL_VOUCHERENTRY | 17| 3196| 8 (0)|
  30. | 26| INDEX RANGE SCAN |IX_GL_VCHENTRYFQ1 | 17| | 1 (0)|
  31. | 27| TABLE ACCESS BY INDEX ROWID |T_BD_ACCOUNTVIEW | 1| 49| 1 (0)|
  32. | 28| INDEX UNIQUE SCAN | PK_BD_ACCOUNTVIEW | 1| | 1 (0)|
  33. | 29| TABLE ACCESS BY INDEX ROWID |T_GL_VOUCHERASSISTRECORD | 1| 109| 2 (0)|
  34. | 30| INDEX RANGE SCAN |IX_GL_VCHASSREC_11 | 2| | 1 (0)|
  35. -------------------------------------------------------------------------------------

将 SQL 改写之后,能在 1 分钟内执行完毕,最终 SQL 返回 42 956 条数据。

为什么要将 in 子查询改写为 with as 呢?这是因为原始 SQL 中,in 子查询比较复杂,想直接使用 HINT 让 in 子查询作为嵌套循环驱动表反向驱动主表比较困难,所以将 in 子查询改写为 with as。需要注意的是 with as 子句中必须要添加 HINT:**/*+ materialize */**,同时主表与子查询关联列必须有索引,如果不添加 HINT: /*+ materialize */如果主表与子查询关联列没有索引,优化器就不会自动将 with as 作为嵌套循环驱动表。with as 子句添加了**/*+ materialize */**会生成一个临时表,这时,就将复杂的 in 子查询简单化了,之后优化器会将 with as 子句展开(unnesting),将子查询展开一般是子查询与主表进行 HASH 连接,或者是子查询作为嵌套循环驱动表与主表进行关联,一般不会是主表作为嵌套循环驱动表,因为主表作为嵌套循环驱动表可以直接走 Filter,不用展开。优化器发现 with as 子句数据量较小,而主表较大,而且主表连接列有索引,于是自动让 with as 子句固化的结果作为了嵌套循环驱动表。

注:with as 其实就是把一大堆重复用到的sql语句放在with as里面,取一个别名,后面的查询就可以用它,这样对于大批量的sql语句起到一个优化的作用,而且清楚明了。