0

In my Postgresql db, I keep a journal of all updates against some of my tables.

The journal tables are of the form:

| Column          | Type   |
| --------------- | ------ |
| journal_id      | bigint | 
| id              | text   | 
| col_1           | text   | 
| col_i           | any    |
  • journal_id is an auto increment sequence.
  • every time the resource is updated, it creates a new record in the journal table for the same id and a new journal_id

I want to create a function show_column_differences to show the diff between the updates: which columns got updated and the before / after values.

So if the table contains:

| journal_id | id  | col_1 | col_i |
| 1          | foo | v1_1  | vi_1  |
| 2          | foo | v1_2  | vi_1  |
| 3          | foo | v1_3  | vi_3  |

It would return

> SELECT * FROM show_column_differences('my_table', 'foo');
| journal_id | previous_journal_id | col_name | value | previous_value |
| 1          | null                | col_1    | v1_1  | null           |
| 1          | null                | col_i    | vi_1  | null           |
| 2          | 1                   | col_1    | v1_2  | v1_1           |
| 3          | 2                   | col_1    | v1_3  | v1_2           |
| 3          | 2                   | col_i    | vi_3  | vi_1           |

I have created a function leveraging the LAG with OVER (PARTITION BY id ORDER BY journal_id) but I keep running into issues to get the dynamic list of columns from the record r:

CREATE OR REPLACE FUNCTION show_column_differences(
    p_table_name TEXT,
    p_id TEXT
)
RETURNS TABLE (
    journal_id TEXT,
    column_name TEXT,
    old_value TEXT,
    new_value TEXT
) AS $$
DECLARE
    r RECORD;
    col_name TEXT;
    old_col_name TEXT;
    col_value TEXT;
    old_col_value TEXT;
    column_list TEXT[];
    select_old_columns TEXT := '';
BEGIN
    -- Compute the list of columns once
    SELECT array_agg(c.column_name::TEXT)
    INTO column_list
    FROM information_schema.columns c
    WHERE c.table_name = p_table_name

    -- Build the list of old_journal columns with alias
    FOREACH col_name IN ARRAY column_list LOOP
        select_old_columns := select_old_columns || format(', old_journal.%I AS old_%I', col_name, col_name);
    END LOOP;

    -- CTE to compute the lag and join with the old version of each row
    FOR r IN EXECUTE format('
        WITH journals AS (
            SELECT *, lag(journal_id) OVER (PARTITION BY id ORDER BY journal_id ASC) AS old_journal_id
            FROM %I
            WHERE id = %L
            ORDER BY journal_id ASC
        )
        SELECT
            journals.* %s
        FROM journals
        JOIN journals AS old_journal
        ON journals.old_journal_id = old_journal.journal_id
    ', p_table_name, p_id, select_old_columns)
    LOOP
        -- Loop over each column and compare old_value and new_value using the precomputed column list
        FOREACH col_name IN ARRAY column_list LOOP
            old_col_name := 'old_' || col_name;
            -- How to access the "col_name" and "old_col_name field of the record?

            IF old_value IS DISTINCT FROM new_value THEN
                -- Set OUT parameters
                -- ...
                RETURN NEXT;
            END IF;
        END LOOP;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

  • How can I access r.old_col_name where old_col_name is a variable?
  • I am open to completely different way to achieve the diff

Thanks for the help

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.