psql Basics
Connecting to Postgres
psql is the interactive terminal that ships with every Postgres installation. It's the fastest way to run queries, explore your schema, and manage your database, and once you learn a few tricks it's a very productive place to work.
There are two main ways to connect. The first is a connection URI, which packs everything into a single string:
psql postgresql://myuser:mypassword@localhost:5432/mydb
The second approach uses individual flags, which I find easier to read when I'm working with multiple servers:
psql -h localhost -p 5432 -U myuser -d mydb
Here's what each flag means:
| Flag | Purpose |
|---|---|
-h | Hostname or IP address |
-p | Port (default is 5432) |
-U | Username |
-d | Database name |
Once connected, you'll see a prompt like this:
mydb=>
The => means you're connected as a regular user. If you see =#, you're connected as a superuser. You can check your current connection details at any time:
mydb=> \conninfo You are connected to database "mydb" as user "myuser" via socket in "/tmp" at port "5432".
That's the one-line format from older psql versions. psql 18 prints \conninfo as a table with more detail, including SSL and protocol information.
Snowflake Postgres works with standard psql. Use your existing client with the connection string for your instance, in the form
psql "postgresql://user:pass@<your-instance-host>:5432/dbname?sslmode=require". The meta-commands and settings in this guide work the same way there.
Meta-Commands and Query Execution
Meta-commands are the backslash commands that make psql so much more than a SQL prompt. They let you explore your database without writing information_schema queries by hand.
Exploring Your Database
Here are the ones I use every day:
| Command | What it shows |
|---|---|
\l | List all databases |
\c dbname | Connect to a different database |
\dt | List tables in current schema |
\dt+ | List tables with sizes |
\d tablename | Describe a table (columns, types, constraints) |
\dn | List schemas |
\du | List roles and their attributes |
\di | List indexes |
\df | List functions |
Let's look at a few of these in action:
mydb=> \dt List of relations Schema | Name | Type | Owner --------+---------------+-------+--------- public | customers | table | myuser public | orders | table | myuser public | products | table | myuser (3 rows)
\d is probably the one I use most. It shows you a table's whole structure:
mydb=> \d customers Table "public.customers" Column | Type | Collation | Nullable | Default ------------+------------------------+-----------+----------+--------------------------------------- id | integer | | not null | nextval('customers_id_seq'::regclass) name | character varying(100) | | not null | email | character varying(255) | | | created_at | timestamp with time zone | | not null | now() Indexes: "customers_pkey" PRIMARY KEY, btree (id) "customers_email_key" UNIQUE CONSTRAINT, btree (email)
Running Queries
Type any SQL statement followed by a semicolon:
mydb=> SELECT name, email FROM customers LIMIT 3; name | email -------------+--------------------- Alice Chen | alice@example.com Bob Smith | bob@example.com Carol Jones | carol@example.com (3 rows)
For multiline queries, psql waits until it sees a semicolon. The prompt changes to show you're continuing a statement:
mydb=> SELECT name, mydb-> count(*) AS order_count mydb-> FROM customers c mydb-> JOIN orders o ON c.id = o.customer_id mydb-> GROUP BY name mydb-> ORDER BY order_count DESC;
Expanded Display
Wide tables are hard to read in the terminal. Toggle expanded mode with \x:
mydb=> \x Expanded display is on. mydb=> SELECT * FROM customers WHERE id = 1; -[ RECORD 1 ]---------------------------- id | 1 name | Alice Chen email | alice@example.com created_at | 2024-11-15 09:23:14.521+00
I recommend \x auto, which uses expanded mode only when the output is too wide for your terminal.
Timing and Performance
Turn on query timing to see how long each statement takes:
mydb=> \timing Timing is on. mydb=> SELECT count(*) FROM orders; count -------- 284710 (1 row) Time: 42.138 ms
Leave it on when you're testing index changes or comparing query plans.
Getting Help
Two help commands worth knowing: \h shows SQL syntax help, and \? lists all meta-commands:
mydb=> \h CREATE INDEX Command: CREATE INDEX Description: define a new index Syntax: CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON ...
Customizing Your psql Environment
Here are a few of my favorite settings for making psql feel like home.
Making NULLs Visible
By default, NULL values display as blank, so you can't tell them apart from an empty string. This has tripped up every Postgres user at some point. Fix it:
mydb=> \pset null '(null)' Null display is "(null)". mydb=> SELECT name, phone FROM customers WHERE id IN (1, 2); name | phone -------------+------------- Alice Chen | 555-0142 Bob Smith | (null) (2 rows)
Pretty Output with Unicode Borders
mydb=> \pset linestyle unicode Line style is unicode. mydb=> \pset border 2 Border style is 2. mydb=> SELECT name, email FROM customers LIMIT 2; ┌─────────────┬─────────────────────┐ │ name │ email │ ├─────────────┼─────────────────────┤ │ Alice Chen │ alice@example.com │ │ Bob Smith │ bob@example.com │ └─────────────┴─────────────────────┘
The .psqlrc File
Every setting you've been typing by hand can go in ~/.psqlrc so it loads automatically. Here's a starter file:
-- Quiet the confirmation messages while these settings load \set QUIET 1 -- Make NULLs visible \pset null '(null)' -- Unicode borders \pset linestyle unicode \pset border 2 -- Show timing on every query \timing -- Use expanded mode when needed \x auto -- Custom prompt showing database and user \set PROMPT1 '%n@%/%R%# ' -- Turn off the pager entirely (the default only pages output too long for the screen) \pset pager off \set QUIET 0
Save this to ~/.psqlrc and every new psql session starts with your preferred settings.
Seeing the SQL Behind Meta-Commands
One of my favorite tricks: \set ECHO_HIDDEN on shows you the actual SQL query that psql runs when you type a meta-command. It's a great way to learn the system catalogs:
mydb=> \set ECHO_HIDDEN on mydb=> \dt ********* QUERY ********** SELECT n.nspname as "Schema", c.relname as "Name", CASE c.relkind WHEN 'r' THEN 'table' ... FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','p','') ... **************************
Saved Queries
You can save frequently-used queries as psql variables and run them with a colon:
-- Add to .psqlrc \set active_queries 'SELECT pid, state, query FROM pg_stat_activity WHERE state != \'idle\';'
Then use it:
mydb=> :active_queries pid | state | query ------+--------+------------------------------------- 1234 | active | SELECT * FROM orders WHERE ... (1 row)
History Search and Tab Completion
Two features that save me time every day:
- Ctrl+R searches your command history. Start typing any part of a previous command and it finds matches.
- Tab completion fills in table names, column names, SQL keywords, and even meta-commands. Start typing and hit Tab.
Both depend on psql being built with readline (or libedit), which is true of nearly every packaged build.
Try it: type SELECT * FROM cust and press Tab, and psql completes it to customers.
Importing and Exporting Data
psql has built-in commands for moving data in and out of your database without external tools.
Running SQL Files
The \i command executes a SQL file:
mydb=> \i /path/to/schema.sql CREATE TABLE CREATE TABLE CREATE INDEX
This is how I run migration scripts and seed data files during development.
Sending Output to a File
Use \o to redirect query output:
mydb=> \o /tmp/report.txt mydb=> SELECT * FROM orders WHERE total > 1000; mydb=> \o
The first \o starts capturing, and \o with no argument stops it.
Using an External Editor
Type \e to open your current query (or an empty buffer) in your $EDITOR. Write your SQL, save, and quit, and psql runs it. This is especially nice for complex queries where you want syntax highlighting and proper indentation.
COPY for Bulk Data
\copy is psql's client-side bulk import/export command:
-- Export to CSV mydb=> \copy orders TO '/tmp/orders.csv' WITH CSV HEADER; COPY 284710 -- Import from CSV mydb=> \copy staging_orders FROM '/tmp/new_orders.csv' WITH CSV HEADER; COPY 500
\copy runs on your local machine, so it reads and writes files on your filesystem, not the server's. That's what you want with a remote or managed database, where server-side COPY ... FROM '/path' either can't see your files or isn't allowed.
Conclusion
The basics get you running queries in seconds, and the meta-commands and a good .psqlrc turn psql into a full development environment. Start with the starter file above and add to it as you find settings you like.
Related Resources
Interactive playground to practice psql commands.
Complete reference for psql commands, variables, and shortcuts.
Connecting to Snowflake Postgres and essential commands.
Settings, presets, echo, and saved queries for power users.
This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances