1

When I use the following stable sql function in my queries, instead of its body directly, the execution is significantly slowed down. From the wiki I read this about scalar functions:

the body contains no aggregate or window function calls, no subqueries, no CTEs, no FROM clause or references to any table or table-like object, none of GROUP BY, HAVING, ORDER BY, DISTINCT, LIMIT, OFFSET, UNION, INTERSECT, EXCEPT

Therefore I cannot understand how I could make it inlineable.

CREATE OR REPLACE FUNCTION get_orderitem_status(oiid integer)
    RETURNS character varying
    LANGUAGE sql
    STABLE
AS
$$
SELECT COALESCE(
               (SELECT ois."statusId"
                FROM "OrderItemStatus" ois
                     JOIN "OrderItem" oi ON oi."id" = oiid
                          AND ois."orderItemId" = oi."id"
                          AND ois."createdAt" > oi."updatedAt"
                ORDER BY ois."createdAt" DESC LIMIT 1),
               (SELECT cs."statusId"
                FROM "CategoryStatus" cs
                     JOIN "OrderItem" oi ON oi."id" = oiid
                     JOIN "Item" i ON i."id" = oi."itemId"
                     JOIN "Status" sta ON sta."id" = cs."statusId"
                WHERE i."categoryId" = cs."categoryId"
                ORDER BY sta."sequence" ASC LIMIT 1)
       );
$$;

From this question, I realize that it is easier to make a table function inlineable, rather than a scalar function. But what could be a table function suitable for this task?
Here is the final query where I will use it, and where by replacing the function with its body I get the result in a third of the time:

SELECT id, get_orderitem_status(id)
FROM "OrderItem"

As the author of the cited question wrote:

I know I can just rewrite my query without the function but it is an important design goal to have these types of abstractions be available as building blocks. I am willing to take a small perf hit for that but not a giant one because the function cannot be inlined.

db<>fiddle

1

1 Answer 1

0

No, you cannot inline this function.

I respect the author's intention to abstract from the function definition, but from a performance perspective, that function is bad news. It may be good enough if you don't have lots of data. However, that function will execute two subqueries for each function call. The moderate benefits from inlining will be more than compensated by that overhead.

If you need to boost query performance, a solution might be to denormalize by storing that calculated value as an extra column that is maintained by triggers. That would be buying query performance at the expense of slowing down data modifications.

4
  • Thanks @Laurenz. This performance degradation is common, since I use many functions (sql language, and stable) in my queries. If I replace function calls with their bodies, the execution times improve significantly. If the execution of the two subqueries is so heavy, wouldn't it be slow even if I removed the function call? Commented Feb 24 at 9:25
  • That's exactly what I was talking about: if you apply the function to many rows, it will be slow, no matter if you inline or not. Commented Feb 24 at 9:34
  • I understand, but as you can see from the attached db<>fiddle, running that query without calling a function is 3x faster, and this is reflected in my database. Using many functions amplifies this problem. Commented Feb 24 at 9:46
  • Then that's the price you have to pay for maintaining this abstraction. For good performance, you'd have to give up on the abstraction and perhaps convert the subqueries into left joins. Commented Feb 24 at 10:12

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.