DEV Community

Sergey Royz
Sergey Royz

Posted on

Calculating a difference between consecutive rows using triggers

Annotation

In this article, we delve into a scenario where the seamless storage of sensor data in a database is coupled with the concurrent computation of the variance between successive measurements.

Problem statement

Sensor data serves as a temporal representation of metric states. An added layer of valuable information can be derived by quantifying the rate of change, or the "velocity," between consecutive readings. However, the computation of these differences, commonly referred to as deltas, can be resource-intensive when performed on demand for each sensor.

The proposal is to use the trigger mechanism during data insertion, making the computation of these deltas automatic and cost-effective. This also shifts the computation burden from the application layer to the database.

What is a trigger?

From Wiki A database trigger is procedural code that is automatically executed in response to certain events on a particular table or view in a database. The trigger is mostly used for maintaining the integrity of the information on the database. For example, when a new record (representing a new worker) is added to the employees table, new records should also be created in the tables of the taxes, vacations and salaries. Triggers can also be used to log historical data, for example to keep track of employees' previous salaries.

Example

Proceeding with our case, let's consider the following table:

CREATE TABLE sensor_data
(
    "timestamp"   INTEGER NOT NULL,
    "sensor_id"   INTEGER NOT NULL,
    "value"       FLOAT   NOT NULL
);

ALTER TABLE sensor_data ADD UNIQUE ("timestamp", "sensor_id");
Enter fullscreen mode Exit fullscreen mode

With columns to store value and time deltas the creation script will look as follows:

CREATE TABLE sensor_data
(
    "timestamp" BIGINT  NOT NULL,
    "sensor_id" INTEGER NOT NULL,
    "value"     FLOAT   NOT NULL,
    "dv"        FLOAT,
    "dt"        BIGINT
);

ALTER TABLE sensor_data ADD UNIQUE ("timestamp", "sensor_id");
Enter fullscreen mode Exit fullscreen mode

Creating a trigger

CREATE OR REPLACE FUNCTION calculate_deltas()
    RETURNS trigger AS
$$
DECLARE
    last_value FLOAT;
    last_ts    BIGINT;
    now        BIGINT;
BEGIN
    RAISE NOTICE 'NEW: %', NEW;

    SELECT "timestamp", value
    INTO last_ts, last_value
    FROM sensor_data t
    WHERE t.sensor_id = NEW.sensor_id
    ORDER BY "timestamp" DESC
    LIMIT 1;

    RAISE NOTICE 'last_ts = %; last_value = %', last_ts, last_value;

    IF FOUND THEN
        SELECT (EXTRACT(EPOCH FROM now()) * 1000)::BIGINT INTO now;
        NEW."timestamp" := now;
        NEW.dv := NEW.value - last_value;
        NEW.dt := now - last_ts;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE TRIGGER tr_sensor_data_deltas
    BEFORE INSERT
    ON sensor_data
    FOR EACH ROW
EXECUTE PROCEDURE calculate_deltas();
Enter fullscreen mode Exit fullscreen mode

Showcase

Now let's insert a couple of values into sensor_data table.

INSERT INTO my_sensor_data ("timestamp", sensor_id, "value")
VALUES ((EXTRACT(EPOCH FROM now()) * 1000)::BIGINT, 1, 3.14);
-- wait a bit
INSERT INTO sensor_data("timestamp", sensor_id, "value") 
VALUES((EXTRACT(EPOCH FROM now()) * 1000)::int, 1, 2.74);
Enter fullscreen mode Exit fullscreen mode

Now let's review the data in the table:

SELECT * FROM sensor_data;
Enter fullscreen mode Exit fullscreen mode

Output should be like this

timestamp sensor_id value dv dt
1701109996686 1 2.74 -0.39 4814
1701109991872 1 3.14 NULL NULL

The dv and dt columns were populated by the trigger automatically.

Top comments (0)