Skip to main content
Question

Optimizing multiple CTE in query

  • October 23, 2018
  • 1 reply
  • 9 views

Bryan_H
Forum|alt.badge.img+2

I am working with a customer trying to optimize queries being generated from a Python application. It produces multiple level CTE such as attached below. I've tried some projection tuning and moving around WHERE and groups and joins based on the query plan but I don't seem to be making much of a dent. We've also tried materializing the WITH clauses with no significant change in run. Any other thoughts on how we can improve this query? It currently takes about 15 minutes to run - we have explain and profile if it would help:

WITH loan_per_date_with_calcs AS
(
SELECT COALESCE(charge_off_amount,0) AS charge_off_amount,
COALESCE(charge_off_sale_fees,0) AS charge_off_sale_fees,
COALESCE(charge_off_sale_proceeds,0) AS charge_off_sale_proceeds,
COALESCE(original_loan_principal,0) AS original_principal,
originator AS originator,
originator_loan_id AS originator_loan_id,
period_end_date AS period_end_date,
COALESCE(post_charge_off_recoveries,0) AS post_charge_off_recoveries,
COALESCE(post_charge_off_recovery_fee,0) AS post_charge_off_recovery_fee,
COALESCE(sub_product_type,'Unknown') AS sub_product_type,
COALESCE(unscheduled_principal,0) AS unscheduled_principal_balance,
vantage_score_band AS vantage_score_band,
vintage_band AS vintage_band,
sub_product_type = sub_product_type AS sub_product_type_all_filter,
CASE
WHEN (ROW_NUMBER() OVER (PARTITION BY originator,originator_loan_id ORDER BY period_end_date ASC) = 1) THEN original_principal
ELSE 0
END AS original_principal_row_number_asc_case,
ROW_NUMBER() OVER (PARTITION BY originator,originator_loan_id ORDER BY period_end_date ASC) AS row_number_asc,
ROW_NUMBER() OVER (PARTITION BY originator,originator_loan_id ORDER BY period_end_date ASC) = 1 AS row_number_asc_equals_one
FROM processedtu_unsec_monthly_revised
WHERE period_end_date <= '2018-05-31'
),
stratted AS
(
SELECT loan_per_date_with_calcs.period_end_date AS period_end_date,
loan_per_date_with_calcs.vantage_score_band AS vantage_score_band,
loan_per_date_with_calcs.vintage_band AS vintage_band,
SUM(loan_per_date_with_calcs.charge_off_amount) AS charge_off,
SUM(loan_per_date_with_calcs.charge_off_amount) + SUM(loan_per_date_with_calcs.charge_off_sale_fees) + SUM(loan_per_date_with_calcs.post_charge_off_recovery_fee) AS "charge_off,debt_sale_fees,_recovery_fees",
SUM(loan_per_date_with_calcs.charge_off_sale_fees) AS debt_sale_fees,
SUM(loan_per_date_with_calcs.charge_off_sale_proceeds) AS debt_sale_proceeds,
SUM(loan_per_date_with_calcs.charge_off_sale_proceeds) + SUM(loan_per_date_with_calcs.post_charge_off_recoveries) AS "debt_sale_proceeds,_gross_recoveries",
SUM(loan_per_date_with_calcs.post_charge_off_recoveries) AS gross_recoveries,
(SUM(loan_per_date_with_calcs.charge_off_amount) + SUM(loan_per_date_with_calcs.charge_off_sale_fees) + SUM(loan_per_date_with_calcs.post_charge_off_recovery_fee)) -(SUM(loan_per_date_with_calcs.charge_off_sale_proceeds) + SUM(loan_per_date_with_calcs.post_charge_off_recoveries)) AS losses,
SUM(loan_per_date_with_calcs.original_principal_row_number_asc_case) AS new_loan_original_principal,
SUM(loan_per_date_with_calcs.post_charge_off_recovery_fee) AS recovery_fees,
SUM(loan_per_date_with_calcs.charge_off_sale_fees) + SUM(loan_per_date_with_calcs.post_charge_off_recovery_fee) AS sum_debt_sale_fees_and_recovery_fees,
SUM(loan_per_date_with_calcs.unscheduled_principal_balance) AS unscheduled_principal_received
FROM loan_per_date_with_calcs
GROUP BY loan_per_date_with_calcs.period_end_date,
loan_per_date_with_calcs.vantage_score_band,
loan_per_date_with_calcs.vintage_band
),
cumulative_metrics AS
(
SELECT stratted.period_end_date AS period_end_date,
stratted.vantage_score_band AS vantage_score_band,
stratted.vintage_band AS vintage_band,
stratted.charge_off AS charge_off,
stratted."charge_off,_debt_sale_fees,_recovery_fees" AS "charge_off,_debt_sale_fees,_recovery_fees",
stratted.debt_sale_fees AS debt_sale_fees,
stratted.debt_sale_proceeds AS debt_sale_proceeds,
stratted."debt_sale_proceeds,_gross_recoveries" AS "debt_sale_proceeds,_gross_recoveries",
stratted.gross_recoveries AS gross_recoveries,
stratted.losses AS losses,
stratted.new_loan_original_principal AS new_loan_original_principal,
stratted.recovery_fees AS recovery_fees,
stratted.sum_debt_sale_fees_and_recovery_fees AS sum_debt_sale_fees_and_recovery_fees,
stratted.unscheduled_principal_received AS unscheduled_principal_received,
SUM(stratted.new_loan_original_principal) OVER (PARTITION BY stratted.vantage_score_band,stratted.vintage_band ORDER BY stratted.period_end_date ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_loan_size,
SUM(stratted.losses) OVER (PARTITION BY stratted.vantage_score_band,stratted.vintage_band ORDER BY stratted.period_end_date ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_losses,
SUM(stratted.unscheduled_principal_received) OVER (PARTITION BY stratted.vantage_score_band,stratted.vintage_band ORDER BY stratted.period_end_date ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_unscheduled_principal_received
FROM stratted
)
SELECT cumulative_metrics.period_end_date,
cumulative_metrics.vantage_score_band,
cumulative_metrics.vintage_band,
cumulative_metrics.charge_off,
cumulative_metrics."charge_off,_debt_sale_fees,_recovery_fees",
cumulative_metrics.debt_sale_fees,
cumulative_metrics.debt_sale_proceeds,
cumulative_metrics."debt_sale_proceeds,_gross_recoveries",
cumulative_metrics.gross_recoveries,
cumulative_metrics.losses,
cumulative_metrics.new_loan_original_principal,
cumulative_metrics.recovery_fees,
cumulative_metrics.sum_debt_sale_fees_and_recovery_fees,
cumulative_metrics.unscheduled_principal_received,
cumulative_metrics.cumulative_loan_size,
cumulative_metrics.cumulative_losses,
cumulative_metrics.cumulative_unscheduled_principal_received,
cumulative_metrics.cumulative_losses / NULLIF(cumulative_metrics.cumulative_loan_size,0) AS cumulative_losses__perc
,
cumulative_metrics.cumulative_unscheduled_principal_received / NULLIF(cumulative_metrics.cumulative_loan_size,0) AS cumulative_unscheduled_principal_received__perc_
FROM cumulative_metrics;

1 reply

mflower
Forum|alt.badge.img
  • Participating Frequently
  • October 25, 2018
Hello Bryan,

The CTEs are not really necessary since each CTE is referenced once only. The key element to performance looks to be how quickly we can filter the table processedtu_unsec_monthly_revised based on
WHERE period_end_date <= '2018-05-31'

There are a few calculations which I think are superfluous (they are calculated within a CTE but then not used anywhere later)
• In the loan_per_date_with_calcs CTE, I think that the following calculations can be removed:
o originator AS originator,
o originator_loan_id AS originator_loan_id,
o COALESCE(sub_product_type,'Unknown') AS sub_product_type,
o sub_product_type = sub_product_type AS sub_product_type_all_filter,
o ROW_NUMBER() OVER (PARTITION BY originator,originator_loan_id ORDER BY period_end_date ASC) AS row_number_asc,
o ROW_NUMBER() OVER (PARTITION BY originator,originator_loan_id ORDER BY period_end_date ASC) = 1 AS row_number_asc_equals_one

• In the stratted CTE
o SUM(lpdwc.charge_off_amount) + SUM(lpdwc.charge_off_sale_fees) + SUM(lpdwc.post_charge_off_recovery_fee) AS "charge_off,debt_sale_fees,_recovery_fees",

I guess that there is no option to rewrite the query in the application?
I don't expect that by removing the above calculations will make a massive difference to the performance, but it's worth checking the simplified version.

Regards, Mike


Michael Flower
EMEA Vertica Systems Engineer
(M)+44 7793 757 623
Michael.Flower@microfocus.com