Introduction

If you are a PostgreSQL database administrator like me where you operate many databases, it is a vital task to organize your sql-scripts in a way for easy lookup and execution. I have tons of scripts. How am I supposed to have them at hand when I need them? I need to lookup these scripts from within psql to stay focused. I will share with you my solution to this problem.

I need fast answers to all kinds of questions:

  • Who is logged in? (w.sql)
  • What queries are running? - active sessions (as.sql)
  • Bloat, is vacuuming happening?
  • Who has ingested the most data? schema size, database size

We need a PostgreSQL server to play with (let’s use docker compose)

-- we 
$ cat docker-compose.yml
services:
  db:
    image: "postgres:latest"
    container_name: db
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: "root"
      POSTGRES_PASSWORD: "secret"
      POSTGRES_DB: "db"
    command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all
    volumes:
      - ./data:/var/lib/postgresql

Startup the service in detached mode

$ docker compose up -d

For convenience, set these environment variables

$ cat env.sh
export PGHOST=localhost
export PGUSER=root
export PGPASSWORD=secret
export PGPORT=5432
export PGDATABASE=db
export PGOPTIONS=--search_path=public

Now, let’s go ahead and look for scripts.

$ . env.sh
$ psql

Timing is on.
Pager is always used.
Pager usage is off.
Line style is unicode.
Border style is 2.
psql (16.4, server 16.2 (Debian 16.2-1.pgdg120+2))
Type "help" for help.
[localhost] root@db  =# -- let us list scripts where filename contains the word 'size' 
[localhost] root@db  =# \set f size
[localhost] root@db  =# :ls
/Users/BJBRA/projects/postgesql/psql/tablesize.sql
/Users/BJBRA/projects/postgesql/psql/tuplesize_by_page.sql
/Users/BJBRA/projects/postgesql/psql/dbsize.sql
/Users/BJBRA/projects/postgesql/psql/tablesize1.sql
/Users/BJBRA/projects/postgesql/psql/schemas_by_size.sql
/Users/BJBRA/projects/postgesql/psql/objectsize.sql
/Users/BJBRA/projects/postgesql/psql/tablesize_partitioned.sql

[localhost] root@db  =# -- now output the size of each schema in a database
[localhost] root@db  =# \i /Users/BJBRA/projects/postgesql/psql/schemas_by_size.sql
┌────────────────────┬─────────┐
│    table_schema    │ size_mb │
├────────────────────┼─────────┤
│ public             │  760.48 │
│ pg_catalog         │    7.53 │
│ information_schema │    0.24 │
└────────────────────┴─────────┘
(3 rows)
[localhost] root@db  =# -- output the size of each database 
[localhost] root@db  =# \i /Users/BJBRA/projects/postgesql/psql/dbsize.sql
┌───────────────┬────────────┐
│ database_name │ size_in_mb │
├───────────────┼────────────┤
│ db            │ 768 MB     │
│ template1     │ 7556 kB    │
│ postgres      │ 7492 kB    │
│ template0     │ 7337 kB    │
└───────────────┴────────────┘
(4 rows)

How did I do this?

There is a magic script behind this. First the dotfile ‘~/.psqlrc’ has the alias ’ls’. The alias ’ls’ executes the ’ls.sql’ script.

[localhost] root@db  =# \! grep -E '^\\set ls' ~/.psqlrc
\set ls              '\\i ~/projects/postgesql/psql/ls.sql'

Note

I have all my scripts in a git repo in this directory ~/projects/postgesql/psql/ . Please adjust the ’ls.sql’ to point the find command to your favorite directory.

ls.sql

--------------------------------------------------------------------------------
-- name:    ls.sql
-- purpose: filter psql scripts from within the psql client.
-- author:  Bjarte Brandt
-- date:    01.11.2022
-- usage:   \set f <scriptname>
--          :ls
-- notes:
--   set variable 'f' and thereafter run ':ls'
--   all scripts: '\set f *'
--------------------------------------------------------------------------------

-- set session
\set QUIET yes
-- will turn both header and footer off
\pset tuples_only
\unset QUIET

-- variable concatenation in psql and output to file '_ls.sql'
\set c '\\! find ~/projects/postgesql/psql -maxdepth 1 -type f -name'
\set q '"'
\out _ls.sql
\qecho :c :q*:f*.sql:q
\out
-- now execute the generated '_ls.sql'
\i _ls.sql
-- cleanup
\! /bin/rm _ls.sql

-- restore session
\set QUIET yes
-- will turn both header and footer on
\pset tuples_only
\unset QUIET

Next

I can of course ‘cat’ my scripts from within psql.

[localhost] root@db  =# \set c schemas_by_size
[localhost] root@db  =# :cat
select
  table_schema,
  round(sum(pg_total_relation_size(quote_ident(table_schema) || '.' || quote_ident(table_name)))::decimal / 1024 / 1024, 2) as size_mb
from
  information_schema.tables
group by
  table_schema
order by size_mb desc;

Enjoy!