I recently ran a budget SuiteQL query that took a whopping 527,138ms
to complete (that’s nearly 9 minutes!).
I had to take a look and see why it was taking so long, and after a few tweaks found the issue had to do with trying to find the very budget category I needed.
So to assist in this discovery I modified my query to search for the budget category first before using it in the rest of my statement.
The way I’ve designed my SuiteQL queries lately is to use a CTE expression to capture any inputs first followed by any other operative things needed next, before then moving on to the bulk of what I want returned.
Here’s an example of what I initially had:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| -- SuiteQL query to fetch budget values for specific GL accounts
-- @param `budget_type` is a string to search for the budget values needed, eg. FINAL, DRAFT (etc)
-- @param `account_id` is the account id to fetch
-- @param `run_date` (optional) fetching values for a specific date, needed in ISO format 'YYYY-MM-DD'
WITH inputs AS (
SELECT
? AS budget_type,
? AS account_id,
TO_DATE(?, 'YYYY-MM-DD') AS run_date
)
SELECT
B.account AS credit_account,
A.id AS debit_account,
B.subsidiary AS sub_id,
BUILTIN.DF(B.category) AS budget_category,
TO_CHAR(AP.startdate, 'YYYY-MM') AS period,
BM.amount AS monthly_amount,
FROM
BudgetsMachine BM
LEFT JOIN Budgets B
ON B.id = BM.budget
LEFT JOIN AccountingPeriod AP
ON BM.period = AP.id
LEFT JOIN Account A
ON B.account = A.id
CROSS JOIN inputs
WHERE
((
inputs.run_date IS NOT NULL AND
EXTRACT(YEAR FROM AP.startdate) = EXTRACT(YEAR FROM inputs.run_date)
AND EXTRACT(MONTH FROM AP.startdate) = EXTRACT(MONTH FROM inputs.run_date)
) OR (
inputs.run_date IS NULL AND
EXTRACT(YEAR FROM AP.startdate) = EXTRACT(YEAR FROM CURRENT_DATE)
AND EXTRACT(MONTH FROM AP.startdate) = EXTRACT(MONTH FROM CURRENT_DATE)
))
AND BUILTIN.DF(B.category) LIKE '%' || inputs.budget_type || '%'
AND A.id = inputs.account_id
AND (
inputs.sub_id IS NULL OR
B.subsidiary = inputs.sub_id
)
|
What I didn’t realise at the time was that the filter line AND BUILTIN.DF(B.category) LIKE '%' || inputs.budget_type || '%'
was expensive. I needed to find a way to filter the budget I wanted.
Thankfully there was a better way, and I added another CTE to help filter the result I wanted. The problem I had was that I only wanted the latest budget and the only way to fetch the top row from a CTE expression was to use a window function.
Here how the much improved result looked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| -- SuiteQL query to fetch budget values for specific GL accounts
-- @param `budget_type` is a string to search for the budget values needed, eg. FINAL, DRAFT (etc)
-- @param `account_id` is the account id to fetch
-- @param `run_date` (optional) fetching values for a specific date, needed in ISO format 'YYYY-MM-DD'
WITH inputs AS (
SELECT
? AS budget_type,
? AS account_id,
TO_DATE(?, 'YYYY-MM-DD') AS run_date
),
-- This CTE is to get the budget needed. It will find the latest version if you are using 'FINAL' or 'DRAFT'
-- that can be found in the name of the Budget Category. It will also exclude any Budget Category's that have
-- been made inactive. Refer to Accounting Lists > Budget Category for details.
-- As the Budget Category's are named as YYYY (FINAL|DRAFT) - eg. 2025 FINAL or 2023 DRAFT it's easy to find
-- the budget I want by placing the names of these budgets in descending order.
valid_budget_category AS (
SELECT
B.category AS category_id,
BUILTIN.DF(B.category) AS category_name,
ROW_NUMBER() OVER (ORDER BY BUILTIN.DF(B.category) DESC) AS rn
FROM
Budgets B
LEFT JOIN BudgetCategory BC
ON B.category = BC.id
CROSS JOIN inputs
WHERE
BC.isinactive = 'F'
AND BUILTIN.DF(B.category) LIKE CONCAT('%', CONCAT(inputs.budget_type, '%'))
)
SELECT
B.account AS credit_account,
A.id AS debit_account,
B.subsidiary AS sub_id,
VC.category_name AS budget_category,
TO_CHAR(AP.startdate, 'YYYY-MM') AS period,
BM.amount AS monthly_amount,
FROM
BudgetsMachine BM
LEFT JOIN Budgets B
ON B.id = BM.budget
LEFT JOIN AccountingPeriod AP
ON BM.period = AP.id
LEFT JOIN Account A
ON B.account = A.id
LEFT JOIN valid_budget_categories VC
ON B.category = VC.category_id
CROSS JOIN inputs
WHERE
VC.rn = 1
AND A.id = inputs.account_id
AND ((
inputs.run_date IS NOT NULL AND
TO_CHAR(AP.startdate, 'YYYY-MM') = TO_CHAR(inputs.run_date, 'YYYY-MM')
) OR (
inputs.run_date IS NULL AND
TO_CHAR(AP.startdate, 'YYYY-MM') = TO_CHAR(CURRENT_DATE, 'YYYY-MM')
))
|
Massive Saving!#
Upon running this new SuiteQL query the runtime went down to 5029ms
!! 😳
From this I learned that I needed to watch for queries using the LIKE
with %
wildcard characters and to better filter what specific budget I wanted early.
Anyway, I hope that might help you to optimise your queries!