Commanding PostgreSQL 9.6: New psql Meta-Commands

Published

Among the new features of PostgreSQL 9.6 are new meta-commands that make creating database-based scripts, working with views, and reading errors so much simpler and quicker. So join us as we explore the new powers of psql.

Psql is a powerful command line tool that enables not just SQL command editing and execution but file import and export, database discovery and more through the meta-commands. If you want to get the best out of PostgreSQL, psql is an essential power-tool, and the new meta-commands add more blades to its cutting edges.

Some of the new meta-commands that come with PostgreSQL 9.6 are \crosstabview,\gexec, \sv, \ev, and \errverbose. We covered \crosstabview in a previous article and now we'll introduce you to the others.

To demonstrate how these commands work, let's first generate some data. To do that, we'll create a table of 25 customers with a customer id, name, and city column and then insert some customers into that table.

CREATE TABLE customers (  
    customer_id SERIAL NOT NULL PRIMARY KEY,
    name TEXT,
    city TEXT                        
);

INSERT INTO customers (name, city) VALUES  
('Roger Einstein', 'Cambridge'),
('Nick Newton', 'Boston'),
('Michele Curie', 'Oxford'),
('Michael Hawking', 'Portland'),
('Ned Copernicus', 'Portland'),
('Neil Galilei', 'Boston'),
('David Tesla', 'Princeton'),
('John Edison', 'Cambridge'),
('Phil Darwin', 'Boston'),
('Fred Nobel', 'Portland'),
('Anthony Fermi', 'Boston'),
('Howard Bell', 'Princeton'),
('Josh Kepler', 'Princeton'),
('Jessica Lovelace', 'Cambridge'),
('Peter Hubble', 'Oxford'),
('Wilson Sagan', 'Portland'),
('Paul Boyle', 'Cambridge'),
('Jonas Hahn', 'Oxford'),
('Tom Hooke', 'Boston'),
('Omar Sina', 'Princeton'),
('Adam Al-Khawarizmi', 'Cambridge'),
('Sara Faraday', 'Princeton'),
('Ken Mendel', 'Cambridge'),
('Dana Franklin', 'Portland'),
('Maria Rutherford', 'Oxford');

\gexec

With \gexec we can generate multiple queries and have psql execute them for us. To see how this works, let's imagine that we need to divide our "customers" table into separate tables by city in order to shard them later on. On a small data set it might be relatively easy to select records and create new tables according to a distinct data field. However, for a large data set this could be a very daunting task, especially if we needed to run multiple queries and execute them individually.

To create new tables for our customers, we'll have to find all the cities that they share. Using DISTINCT, we could find the distinct cities, but we'd still have to use CREATE TABLE for each one. With format() function, we can generate a list of CREATE TABLE commands for each city like:

SELECT  
    format('CREATE TABLE city_%s (customer_id INT PRIMARY KEY, name TEXT, city TEXT);', c.city)
FROM (SELECT DISTINCT city FROM customers) AS c;  

This will generate a table of CREATE TABLE commands from the number of DISTINCT cities. Each of these commands will be executed in order, by row once we add \gexec.

                                      format                                      
----------------------------------------------------------------------------------
 CREATE TABLE city_Princeton (customer_id INT PRIMARY KEY, name TEXT, city TEXT);
 CREATE TABLE city_Oxford (customer_id INT PRIMARY KEY, name TEXT, city TEXT);
 CREATE TABLE city_Portland (customer_id INT PRIMARY KEY, name TEXT, city TEXT);
 CREATE TABLE city_Cambridge (customer_id INT PRIMARY KEY, name TEXT, city TEXT);
 CREATE TABLE city_Boston (customer_id INT PRIMARY KEY, name TEXT, city TEXT);

Substituting \gexec for the semicolon at the end of the SQL statement tells psql to execute each of these SQL commands where we'll get:

SELECT format('CREATE TABLE city_%s (customer_id INT PRIMARY KEY, name TEXT, city TEXT);', c.city) FROM (SELECT DISTINCT city FROM customers) AS c \gexec  
CREATE TABLE  
CREATE TABLE  
CREATE TABLE  
CREATE TABLE  
CREATE TABLE  

Next, we'll have to insert each customer into a table based on their city. To do that, we'll create a similar SQL query as above also using the format() function. This time, we'll use the INSERT INTO command to select each customer by their city name, which we'll use as the table name for the insert. Also, we'll make sure to select their name using the WHERE clause so that we don't accidentally place customers into the wrong table.

SELECT  
    format('INSERT INTO city_%s SELECT * FROM customers WHERE name = $$%s$$;', c.city, c.name) 
FROM customers AS c;  

Again, executing this query we can view each INSERT INTO command before they're executed using \gexec.

                                         format                                          
-----------------------------------------------------------------------------------------
 INSERT INTO city_Cambridge SELECT * FROM customers WHERE name = $$Roger Einstein$$;
 INSERT INTO city_Boston SELECT * FROM customers WHERE name = $$Nick Newton$$;
 INSERT INTO city_Oxford SELECT * FROM customers WHERE name = $$Michele Curie$$;
 INSERT INTO city_Portland SELECT * FROM customers WHERE name = $$Michael Hawking$$;
...

Now, subtituting the semicolon for \gexec, each query will be run in sequence:

SELECT format('INSERT INTO city_%s SELECT * FROM customers WHERE name = $$%s$$;', c.city, c.name) FROM customers AS c \gexec  
INSERT 0 1  
INSERT 0 1  
INSERT 0 1  
INSERT 0 1  
...

After that's finished, we can inspect our tables to make sure that all the customers have been added correctly.

\sv and \ev

The view meta-commands \sv (show view) and \ev (edit view) have also been added to psql. Historically, since version 9.1, we've had the \sf and \ef commands that allow us to view and edit PostgreSQL functions. Now in 9.6, the command has been extended to views.

The view meta-commands are particularly useful when figuring out how your views are constructed and editing them right from the terminal, instead of having to drop and recreate them. Using the view commands are pretty straightforward, so let's take a look at how \sf and \ef work by using the five city tables we created above.

First, we'll create a view that combines the customers located in the cities of Boston, Princeton, Cambridge, and Portland and call it "us_cities".

CREATE OR REPLACE VIEW us_cities AS (  
    SELECT 
        * 
    FROM 
        city_boston
    UNION
    SELECT 
        * 
    FROM 
        city_princeton 
    UNION 
    SELECT 
        * 
    FROM 
        city_cambridge
    UNION
    SELECT 
        * 
    FROM 
        city_portland
);

This will produce a table containing ten customers with their customer ids and city locations in no particular order:

 customer_id |        name        |   city    
-------------+--------------------+-----------
          16 | Wilson Sagan       | Portland
           4 | Michael Hawking    | Portland
          21 | Adam Al-Khawarizmi | Cambridge
          13 | Josh Kepler        | Princeton
          22 | Sara Faraday       | Princeton
          12 | Howard Bell        | Princeton
           2 | Nick Newton        | Boston
           9 | Phil Darwin        | Boston
           6 | Neil Galilei       | Boston
          10 | Fred Nobel         | Portland
           7 | David Tesla        | Princeton
          14 | Jessica Lovelace   | Cambridge
           8 | John Edison        | Cambridge
          17 | Paul Boyle         | Cambridge
          11 | Anthony Fermi      | Boston
           1 | Roger Einstein     | Cambridge
          23 | Ken Mendel         | Cambridge
           5 | Ned Copernicus     | Portland
          19 | Tom Hooke          | Boston
          24 | Dana Franklin      | Portland
          20 | Omar Sina          | Princeton

With \sv [view_name] we'll get the definition of the view,

link

and adding a + to it (\sv+ [table_name]) will give us a more verbose version of this by appending line numbers next to each line of the view definition.

link

If any updates need to be made to the view, we can do that using \ev [view_name], which will open an edit buffer in your default text editor. Suppose we want to take out Cambridge because the city refers to Cambridge in the UK not Cambridge, Massachusetts. We'd write \ev us_cities, and remove the customers from Cambridge:

link

After editing, save it. It will close and take you back into psql. From there, type either a semicolon or \g to execute the changes to the view. You'll get the CREATE VIEW acknowledgment that the view has been successfully replaced.

Editing a view doesn't just mean adding or subtracting tables or columns, we can also create new views by simply changing the name. For example, if we wanted to edit the name of the "us_cities" view to "us_customers" to reflect what our view really comprises, we'll open the editor again using \ev us_cities, then change the name, save it, then again when back in psql type a semicolon or \g to create the new view:

link

Once you've received the CREATE VIEW acknowledgment, you'll notice that you still have the old "us_cities" view and the new "us_customers" view. That's because psql uses the CREATE OR REPLACE VIEW command which looks for a view called "us_customers". Since it the view doesn't exist a new view is created. From there, you can drop any views that you don't need.

\errverbose

Now we'll turn to the last meta-command we'll cover, \errverbose. If you want to see a verbose version of the most recent server error message, we now can use \errverbose. Perviously, if you wanted to view an expanded version of an error message, you'd have to set psql's [VERBOSITY] variable to verbose. The drawback of that is it'd give you a verbose error report every time an error occured, which might not be what you're looking for.

Now, you don't have to fiddle with psql settings with the introduction of \errverbose. Running this command, you'll get a verbose version of the last error message produced in psql. The output that you'll get varies depending on the error. However, what you'll always get are the PostgreSQL error code, as well as the function, file name, and line number in the source code that triggered the error.

To show you how this works, we can try it out by editing the "us_customer" view that we created above. We'll try to rename the column name to customer_name. We'll type \ev us_customers to edit the view like:

link

After saving the view in the editor, trying to execute the new view in psql will give us an error:

ERROR:  cannot change name of view column "name" to "customer_name"  

This error is due to the way views are set up in the source code. This error code doesn't give us much detail except that we can't change name to customer_name. To get a better understanding of why we can't change the name, we'll run \errverbose which gives us:

ERROR:  42P16: cannot change name of view column "name" to "customer_name"  
LOCATION:  checkViewTupleDesc, view.c:299  

The error code that's returned (42P16) corresponds to an invalid table definition; or an invalid view. Below that, we are given the function checkViewTupleDesc, file name view.c, and line number in the PostgreSQL source code #299 which triggered the error. This function validates views to make sure that the old view matches with the new view, with the exception of changed column attribute names.

Just to see another error, let's attempt to add another record to the "city_portland" table. This time, we'll try to add a record with an id that already exists. We have five customers in this table:

 customer_id |      name       |   city   
-------------+-----------------+----------
           4 | Michael Hawking | Portland
           5 | Ned Copernicus  | Portland
          10 | Fred Nobel      | Portland
          16 | Wilson Sagan    | Portland
          24 | Dana Franklin   | Portland

If we try to add another customer with a customer_id of 4, it will produce an unique key error since we set that column up as a primary key column.

INSERT INTO city_portland (customer_id, name, city) VALUES (4, 'Brian Bosworth', 'Portland');  
ERROR:  duplicate key value violates unique constraint "city_portland_pkey"  
DETAIL:  Key (customer_id)=(4) already exists.  

Runnng \errverbose after this error will give us:

ERROR:  23505: duplicate key value violates unique constraint "city_portland_pkey"  
DETAIL:  Key (customer_id)=(4) already exists.  
SCHEMA NAME:  public  
TABLE NAME:  city_portland  
CONSTRAINT NAME:  city_portland_pkey  
LOCATION:  _bt_check_unique, nbtinsert.c:433  

This message provides us with the error code 23505 that corresponds to a unique violation error. We're given the same information in "DETAIL" as the previous error, but it also includes the schema, table, and constraint names, which might be useful if we have several schemas involved. Finally, the location of the error is provided again, producing the function, file name, and the line number in the source code that triggered the error.

Summary

The meta-commands that come with psql are very useful for developers who want to spend more time analyzing their data than writing SQL commands or changing their PostgreSQL settings. While it might not offer the bling that GUIs have, the tradeoff is that you have access to these meta-commands. With PostgreSQL 10 right around the corner, psql will get even more powerful with the addition of even more meta-commands such as nestable conditional block commands \if, \elif, \else, and \endif. So, to start taking advantage of PostgreSQL 9.6, look nowhere else except your terminal, and start using psql.


If you have any feedback about this or any other Compose article, drop the Compose Articles team a line at articles@compose.com. We're happy to hear from you.

attribution S W

Abdullah Alger
Abdullah Alger is a former University lecturer who likes to dig into code, show people how to use and abuse technology, talk about GIS, and fish when the conditions are right. Coffee is in his DNA. Love this article? Head over to Abdullah Alger’s author page to keep reading.

Conquer the Data Layer

Spend your time developing apps, not managing databases.