CodeGym /Courses /SQL SELF /Breaking Down Common Mistakes When Debugging and Optimizi...

Breaking Down Common Mistakes When Debugging and Optimizing PL/pgSQL

SQL SELF
Level 56 , Lesson 4
Available

Breaking Down Common Mistakes When Debugging and Optimizing PL/pgSQL

Today, to wrap up this epic journey through PL/pgSQL, we're gonna break down the most common mistakes that can trip you up while debugging and optimizing your functions and procedures. Knowing these mistakes will help you not only dodge problems in the future, but also deal with bugs way more efficiently if they do pop up.

Typical Mistakes in Debugging and Optimization

1. Incorrect Use of Variables

One of the most frequent mistakes when writing and debugging functions in PL/pgSQL is declaring or using variables the wrong way. For example, if you forget to explicitly set a variable type or get mixed up with values passed through parameters. Let's see what this looks like in practice:

CREATE OR REPLACE FUNCTION calculate_discount(order_total NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
    discount_rate NUMERIC;
BEGIN
    -- Oops! Forgot to initialize the discount_rate variable
    RETURN order_total * discount_rate;
END;
$$ LANGUAGE plpgsql;

When you call this function, you'll get an error related to using NULL in calculations, because the discount_rate variable isn't initialized by default.

How to avoid it:

  1. Always assign default values to variables when declaring them:
   DECLARE
       discount_rate NUMERIC := 0.1; -- Default value
  1. Check your variables with RAISE NOTICE to make sure they have the values you expect:
RAISE NOTICE 'Value of discount_rate: %', discount_rate;

2. No Error Logging

Another common problem is not having any logging mechanism. If something goes wrong and you’re not logging what your function is doing, it’s like looking for a black cat in a dark room—especially if you’re not even sure there’s a cat in there.

Here’s an example of a function with no logging:

CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
BEGIN
    -- Some complicated order processing logic
    UPDATE orders SET status = 'processed' WHERE id = order_id;
END;
$$ LANGUAGE plpgsql;

What if order_id is passed in wrong? What if the record in the orders table doesn’t exist?

How to avoid it: Add RAISE NOTICE or RAISE EXCEPTION to log critical steps:

CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
BEGIN
    -- Logging input data
    RAISE NOTICE 'Processing order with ID %', order_id;

    -- Complicated processing logic
    UPDATE orders SET status = 'processed' WHERE id = order_id;

    -- Logging result
    RAISE NOTICE 'Order status updated for ID %', order_id;
END;
$$ LANGUAGE plpgsql;

Now you can easily track where an error pops up, thanks to the messages you print out.

3. Ignoring Query Performance

This is one of the main villains for any database developer. For example, you write a function that looks fine, but it runs super slow. And one of the main reasons for slow queries is missing indexes or inefficient query plans.

Example of a slow query:

CREATE OR REPLACE FUNCTION get_large_orders()
RETURNS TABLE(order_id INT, total NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT id, total FROM orders WHERE total > 1000;
END;
$$ LANGUAGE plpgsql;

If the total field in the orders table isn’t indexed, the query will scan the whole table, which is super inefficient.

How to avoid it:

  1. Use EXPLAIN ANALYZE to make sure your queries are efficient:
EXPLAIN ANALYZE SELECT id, total FROM orders WHERE total > 1000;
  1. Create indexes on columns you use a lot:
CREATE INDEX idx_orders_total ON orders(total);

4. Using the Wrong Transaction Isolation Level

When running complex procedures, sometimes you get errors because you don’t really get how transaction isolation levels work. For example, if two transactions try to update the same record at the same time, you might end up with a deadlock.

Example of a potential deadlock:

BEGIN;
UPDATE orders SET status = 'processed' WHERE id = 1;

-- Waiting for another transaction's lock
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
COMMIT;

If another transaction tries to do these operations in a different order, you’ll get a mutual lock.

How to avoid it:

  1. Think through the order of your operations and stick to it.
  2. Use the SERIALIZABLE isolation level if you need to.

5. No Error Handling

Error handling isn’t just a good practice—it’s a tool for keeping your code stable. For example, in the code below, there’s no handling for possible errors:

CREATE OR REPLACE FUNCTION add_order(order_id INT)
RETURNS VOID AS $$
BEGIN
    INSERT INTO orders (id, status) VALUES (order_id, 'new');
END;
$$ LANGUAGE plpgsql;

If order_id already exists, you’ll get a duplicate key value violates unique constraint error.

How to avoid it: Use exception handling blocks:

CREATE OR REPLACE FUNCTION add_order(order_id INT)
RETURNS VOID AS $$
BEGIN
    INSERT INTO orders (id, status) VALUES (order_id, 'new');
EXCEPTION WHEN unique_violation THEN
    RAISE NOTICE 'Order with ID % already exists!', order_id;
END;
$$ LANGUAGE plpgsql;

Examples of Mistakes and How to Fix Them

Mistake 1: Queries are slow because there’s no indexing

Situation: You have a query that filters a table by a column, but there’s no index for that column.

Fix: Create an index for the relevant column.

Mistake 2: Function logic is messy and hard to debug

Situation: The function has way too much logic and isn’t split into sub-functions.

Fix: Break up a complex function into smaller sub-functions. This makes your code easier to read and debug.

Mistake 3: Incorrect use of RAISE EXCEPTION

Situation: RAISE EXCEPTION is used for every error, even minor ones.

Fix: Use RAISE NOTICE for info messages and only RAISE EXCEPTION for critical stuff.

RAISE NOTICE 'All good — current function step is done.';
RAISE EXCEPTION 'Something broke! Check your input parameters.';

Tips to Prevent Mistakes

  1. Add logging: At critical points in your function, use RAISE NOTICE to track what’s happening.
  2. Test your functions: Regularly use test data to check your functions and procedures.
  3. Keep your code readable: Break up complex functions into smaller sub-functions and procedures.
  4. Analyze performance: Use EXPLAIN ANALYZE to make sure your queries are running efficiently.
  5. Be ready for surprises: Always add exception handling blocks to deal with errors.
EXCEPTION
    WHEN OTHERS THEN
        RAISE EXCEPTION 'An unexpected error occurred: %', SQLERRM;

This will help you confidently deal with errors and keep them from popping up in the future.

2
Task
SQL SELF, level 56, lesson 4
Locked
Error Handling for Duplicate Values
Error Handling for Duplicate Values
1
Survey/quiz
Function Optimization, level 56, lesson 4
Unavailable
Function Optimization
Function Optimization
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION