Search code examples
sqlitetriggerssql-updatesql-insertwindow-functions

Window function lag() in trigger uses default instead of previous value


I'm trying to create an SQLite trigger to update balance for a particular account code.

accounts table :

CREATE TABLE accounts (
    year      INTEGER   NOT NULL,
    month     INTEGER   NOT NULL   CHECK(month BETWEEN 1 AND 12),
    amount    REAL      NOT NULL   CHECK(amount >= 0),
    balance   REAL,
    code      INTEGER   NOT NULL
);

When a new row is inserted I want the balance value of the new row to reflect OLD balance + NEW amount. But this trigger does not recognize the lagging balance value and I cannot figure out why:

CREATE TRIGGER trg_accounts_balance
AFTER INSERT ON accounts
BEGIN
    UPDATE accounts
    SET balance = (
        SELECT
            lag(balance, 1, 0) OVER (
                PARTITION BY code
                ORDER BY month
            ) + NEW.amount
        FROM accounts
    )
    WHERE rowid = NEW.ROWID;
END;

If I insert one row per month, I expect my data to look like:

year month amount balance code
2022 1 100.0 100.0 100
2022 2 9.99 109.99 100

But I get:

year month amount balance code
2022 1 100.0 100.0 100
2022 2 9.99 9.99 100

What am I doing wrong?


Solution

  • The query:

    SELECT
        lag(balance, 1, 0) OVER (
                    PARTITION BY code
                    ORDER BY month
                ) 
    FROM accounts
    

    returns as many rows as there are in the table and SQLite picks the first (whichever it is) to return it as the result so that it can use it to add NEW.amount.
    There is nothing that links this value to the specific row that was inserted.

    Instead, use this:

    CREATE TRIGGER trg_accounts_balance
    AFTER INSERT ON accounts
    BEGIN
        UPDATE accounts
        SET balance = COALESCE(
           (
            SELECT balance
            FROM accounts
            WHERE code = NEW.code
            ORDER BY year DESC, month DESC
            LIMIT 1, 1
           ), 0)  + NEW.amount
        WHERE rowid = NEW.ROWID;
    END;
    

    The subquery returns the previous inserted row by ordering the rows of the specific code descending and skipping the top row (which is the new row).

    See the demo.