Raja's Exocortex

Postgres Upsert using Triggers

Problem

  1. Time series data taken for 5 data points: heartrate, beatsmissed, ibi, spo2, steps.
  2. HTTP API provides data point for each API call (eg GET /$tenant/$user/heartrate/<time-range>/).
  3. All the 5 data points need to be merged into a single Postgres table row. We use a Postgres ON CONFLICT DO UPDATE trigger.
  4. Bulk import data using CSV files.

Creating the PG Table

CREATE DATABASE IF NOT EXISTS omnyk;
use omnyk;

DROP TABLE IF EXISTS data_points;

CREATE TABLE data_points (
  tenantid    TEXT                         NOT NULL,
  userid      UUID                         NOT NULL,
  ringid      UUID                         NOT NULL,
  time        TIMESTAMP(3) NOT NULL,

  heartrate   SMALLINT,
  beatsmissed BOOLEAN,
  ibi         SMALLINT,
  spo2        SMALLINT,
  steps       SMALLINT,

  UNIQUE(tenantid, userid, ringid, TIME)
);

Postgres UPSERT Trigger


-- ETL for migration from KairosDB to Postgres
-- We read data point (column) at a time using the API and insert into postgres, the
-- below trigger adds an ON CONFLICT DO UPDATE to ensure each column is updated in the
-- appropriate row.

CREATE OR REPLACE FUNCTION data_points_ignore_dups() RETURNS TRIGGER AS $
    BEGIN
      IF NEW.heartrate IS NOT NULL THEN
          INSERT INTO data_points(tenantid, userid, ringid, time, heartrate)
            VALUES (NEW.tenantid, NEW.userid, NEW.ringid, NEW.time, NEW.heartrate)
            ON CONFLICT (tenantid, userid, ringid, time) DO UPDATE
            SET heartrate = (EXCLUDED.heartrate);
          RETURN NULL;
      END IF;

      IF NEW.beatsmissed IS NOT NULL THEN
          INSERT INTO data_points(tenantid, userid, ringid, time, beatsmissed)
            VALUES (NEW.tenantid, NEW.userid, NEW.ringid, NEW.time, NEW.beatsmissed)
            ON CONFLICT (tenantid, userid, ringid, time) DO UPDATE
            SET beatsmissed = (EXCLUDED.beatsmissed);
          RETURN NULL;
      END IF;

      IF NEW.ibi IS NOT NULL THEN
          INSERT INTO data_points(tenantid, userid, ringid, time, ibi)
            VALUES (NEW.tenantid, NEW.userid, NEW.ringid, NEW.time, NEW.ibi)
            ON CONFLICT (tenantid, userid, ringid, time) DO UPDATE
            SET ibi = (EXCLUDED.ibi);
          RETURN NULL;
      END IF;
      IF NEW.spo2 IS NOT NULL THEN
          INSERT INTO data_points(tenantid, userid, ringid, time, spo2)
            VALUES (NEW.tenantid, NEW.userid, NEW.ringid, NEW.time, NEW.spo2)
            ON CONFLICT (tenantid, userid, ringid, time) DO UPDATE
            SET spo2 = (EXCLUDED.spo2);
          RETURN NULL;
      END IF;

      IF NEW.steps IS NOT NULL THEN
          INSERT INTO data_points(tenantid, userid, ringid, time, steps)
            VALUES (NEW.tenantid, NEW.userid, NEW.ringid, NEW.time, NEW.steps)
            ON CONFLICT (tenantid, userid, ringid, time) DO UPDATE
            SET steps = (EXCLUDED.steps);
          RETURN NULL;
      END IF;

      RETURN NULL;
    END;
$ LANGUAGE plpgsql;

-- Attach the trigger to INSERTs. pg_trigger_depth() avoids trigger recursion.

CREATE TRIGGER data_points_insert_trigger BEFORE INSERT ON data_points
    FOR EACH ROW
        WHEN (pg_trigger_depth() < 1)
        EXECUTE PROCEDURE  data_points_ignore_dups();

#postgres #upsert #triggers