trackrv0.3.0trackr.casoon.de

Postgres

The trackr_events table, its indexes, retention and example report queries.

Migration

migrations/001_create_trackr_events.sql creates the table and five indexes. It uses IF NOT EXISTS throughout, so running it twice is harmless.

psql "$DATABASE_URL" -f migrations/001_create_trackr_events.sql

Since 0.3.0 it also ships in the npm package as node_modules/@casoon/trackr/migrations/001_create_trackr_events.sql.

CREATE TABLE IF NOT EXISTS trackr_events (
  id          UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
  ts          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  type        TEXT        NOT NULL CHECK (type IN ('pageview', 'event')),
  name        TEXT,
  url         TEXT        NOT NULL,
  referrer_domain TEXT,
  country     TEXT,
  device      TEXT        CHECK (device IN ('desktop', 'mobile', 'tablet')),
  browser     TEXT,
  os          TEXT,
  session_id  TEXT,
  utm         JSONB       DEFAULT NULL,
  props       JSONB       DEFAULT '{}'
);

The type check matters: the handler accepts any string as type, but the table only takes pageview and event, so other values fail with 500.

Indexes

Index Column Purpose
idx_trackr_events_ts ts DESC Time ranges
idx_trackr_events_type type Pageviews vs. custom events
idx_trackr_events_url url Per-page reports
idx_trackr_events_utm utm (GIN, where not null) Campaign attribution
idx_trackr_events_props props (GIN, where not empty) Filtering on event properties

Retention

Events contain no IP address, and session IDs change daily. If you still want to limit how long events are kept, delete old rows on a schedule, for example with pg_cron:

DELETE FROM trackr_events WHERE ts < NOW() - INTERVAL '90 days';

Report queries

-- Pageviews per day, last 30 days
SELECT DATE_TRUNC('day', ts) AS day, COUNT(*) AS views
FROM trackr_events
WHERE type = 'pageview' AND ts > NOW() - INTERVAL '30 days'
GROUP BY 1 ORDER BY 1;

-- Top pages
SELECT url, COUNT(*) AS views
FROM trackr_events
WHERE type = 'pageview'
GROUP BY url ORDER BY views DESC LIMIT 20;

-- UTM sources
SELECT utm->>'source' AS source, COUNT(*) AS views
FROM trackr_events
WHERE utm IS NOT NULL
GROUP BY 1 ORDER BY 2 DESC;

-- Custom events
SELECT name, COUNT(*) AS fires
FROM trackr_events
WHERE type = 'event'
GROUP BY name ORDER BY fires DESC;

-- Daily visitors (distinct session IDs per day)
SELECT DATE_TRUNC('day', ts) AS day, COUNT(DISTINCT session_id) AS visitors
FROM trackr_events
GROUP BY 1 ORDER BY 1;

Edit this page on GitHub · Docs for v0.3.0