一位上海的朋友说以下 SQL 执行不出结果。

    1. with tab as
    2. select bb.card_no cashier_shop_no, aa.card_open_owner merch_id,aa.card_no ,bb.txn_date,bb.last_txn_date,bb.merch_loc_name
    3. from tb_card aa
    4. join
    5. (select t.card_no,a.merch_id,a.merch_loc_name ,t.cust_id txn_date,
    6. to_char(last_day( add_months(to_date(t.cust_id,'yyyymmdd'),1)) ,'yyyymmdd') last_txn_date
    7. from tb_bill_test t join tb_merch a on t.card_no=a.cashier_shop_no
    8. where t.nbr_group='161' and a.merch_id not like '0%' )bb
    9. on aa.card_open_owner=bb.merch_id
    10. where aa.mbr_reg_date between bb.txn_date and bb.last_txn_date
    11. select bb.cashier_shop_no bb.merch_loc_nameaa.txn_date
    12. case
    13. when aa.merch_id=bb.merch_id and aa.card_no=bb.card_no
    14. then '本店会员'
    15. else '他店会员'
    16. end shop_no
    17. countdistinct aa.card_no card_num
    18. sum case when aa.p_code in ('7646','7686','7208') then -1 else 1 end count_num
    19. sumcase
    20. when aa.p_code in ('7646','7686','7208') then 0-
    21. case
    22. when aa.txn_amt>aa.earning_amt
    23. then aa.txn_amt
    24. else aa.earning_amt
    25. end
    26. else
    27. case
    28. when aa.txn_amt>aa.earning_amt
    29. then aa.txn_amt
    30. else aa.earning_amt
    31. end
    32. end txn_amt
    33. from tb_trans aa join tab bb on aa.merch_id=bb.merch_id
    34. where aa.txn_date between bb.txn_date and bb.last_txn_date and
    35. aa.p_code in '7647','7687','7207','7646','7686','7208'
    36. and aa.status in '1','R'
    37. group by bb.cashier_shop_no bb.merch_loc_nameaa.txn_dateaa.merch_id
    38. case when aa.merch_id=bb.merch_id and aa.card_no=bb.card_no then '本店会员' else '他店会员' end
    39. order by aa.merch_id aa.txn_date

    执行计划如下。

    1. Plan hash value: 4271695044
    2. -------------------------------------------------------------------------------------
    3. | Id |Operation |Name |Rows|Bytes|Cost(%CPU)|
    4. -------------------------------------------------------------------------------------
    5. | 0 |SELECT STATEMENT | | 15|1650|96737 (1)|
    6. | 1 | SORT GROUP BY | | 15|1650|96737 (1)|
    7. | 2 | VIEW |VW_DAG_0 | 15|1650|96735 (1)|
    8. | 3 | HASH GROUP BY | | 15|2430|96735 (1)|
    9. | 4 | NESTED LOOPS | | 15| 430|96734 (1)|
    10. | 5 | NESTED LOOPS | |2213|2430|96734 (1)|
    11. | 6 | NESTED LOOPS | | 1| 103| 542 (1)|
    12. | 7 | NESTED LOOPS | | 1| 65| 58 (0)|
    13. | 8 | TABLE ACCESS BY INDEX ROWID |TB_BILL_TEST | 36| 864| 20 (0)|
    14. |* 9 | INDEX RANGE SCAN |TMP_INDEX_BILL_01 | 37| | 3 (0)|
    15. |*10 | TABLE ACCESS BY INDEX ROWID |TB_MERCH | 1| 41| 3 (0)|
    16. |*11 | INDEX RANGE SCAN |I1_MERCH | 1| | 1 (0)|
    17. |*12 | TABLE ACCESS BY INDEX ROWID |TB_CARD | 2| 76| 484 (1)|
    18. |*13 | INDEX RANGE SCAN |I1_CARD_OPEN_OWNER|3855| | 24 (0)|
    19. |*14 | INDEX RANGE SCAN |I2_TRANS |2213| |95972 (1)|
    20. |*15 | TABLE ACCESS BY GLOBAL INDEX ROWID|TB_TRANS | 56|3304|96193 (1)|
    21. -------------------------------------------------------------------------------------
    22. Predicate Information (identified by operation id):
    23. ---------------------------------------------------
    24. 9 - access("T"."NBR_GROUP"='161')
    25. filter("T"."CARD_NO" IS NOT NULL)
    26. 10 - filter("A"."MERCH_ID" NOT LIKE '0%')
    27. 11 - access("T"."CARD_NO"="A"."CASHIER_SHOP_NO")
    28. 12 - filter("AA"."MBR_REG_DATE">="T"."CUST_ID" AND "AA"."MBR_REG_DATE"<=TO_CHAR(LAST_DAY(ADD_MONTHS
    29. (TO_DATE("T"."CUST_ID",'yyyymmdd'),1)),'yyyymmdd'))
    30. 13 - access("AA"."CARD_OPEN_OWNER"="A"."MERCH_ID")
    31. filter("AA"."CARD_OPEN_OWNER" IS NOT NULL)
    32. 14 - access("AA"."TXN_DATE">="T"."CUST_ID" AND "AA"."MERCH_ID"="AA"."CARD_OPEN_OWNER" AND "AA"."TXN_DATE"<=TO_CHAR(LAST_DAY(ADD_MONTHS(TO_DATE("T"."CUST_ID",'yyyymmdd'),1)),'yyyymmdd'))
    33. filter("AA"."MERCH_ID"="AA"."CARD_OPEN_OWNER")
    34. 15 - filter(("AA"."P_CODE"='7207' OR "AA"."P_CODE"='7208' OR "AA"."P_CODE"='7646' OR "AA"."P_CODE"='7647'
    35. OR "AA"."P_CODE"='7686' OR "AA"."P_CODE"='7687') AND ("AA"."STATUS"='1' OR "AA"."STATUS"='R'))

    我们拿到一条需要优化的 SQL 语句,怎么入手呢?首先要看 SQL 写法。可以利用 SQL 三段分拆方法,先观察 SQL 语句。该 SQL 语句有个 with as 子句取名为 tab,主查询中就是 tb_trans 与 tab 进行关联。with as 子句一共返回 6 000 多行数据,可以 1 秒内出结果,tb_trans 有两亿条数据。执行计划中,with as 子查询作为一个整体并且作为嵌套循环驱动表,tb_trans 作为嵌套循环被驱动表,乍一看,这也符合嵌套循环关联原则,小表驱动大表,大表走索引。但是该SQL执行不出结果,最大的可能就是 tab 与 tb_trans 关联之后返回数据量太多,因为返回结果集太多,被驱动表走索引,也就是说该 SQL 可能是被驱动表走索引返回数据量太多导致性能问题。于是检查被驱动表连接列 merch_id 基数,基数很低,tab:tb_trans 是 1 比几十万关系。

    因为被驱动表 tb_trans 与 tab 是几十万比 1 的关系,这时就不能走嵌套循环了,只能走 HASH 连接,于是使用 HINT:use_hash(aa,bb)优化 SQL,最终该 SQL 可以在 1 小时左右执行完毕。如果开启并行查询可以更快。