MacLochlainns Weblog

Michael McLaughlin's Technical Blog

Site Admin

Archive for the ‘psql’ Category

PostgreSQL Composites

without comments

PostgreSQL like Oracle supports record data types but unlike Oracle, PostgreSQL doesn’t support collections of record data types. Here’s an example of how to define a PostgreSQL composite data type, and how to use it as a column’s data type.

CREATE TYPE address_type AS
( street_address  VARCHAR
, city            VARCHAR
, state           VARCHAR
, zip_code        VARCHAR );

Then, you define an ADDRESS table, like:

CREATE TABLE address
( address_id      SERIAL
, address_struct  ADDRESS_TYPE );

You can now insert rows like:

-- Insert the first row.
INSERT INTO address
( address_struct )
VALUES
(('52 Hubble Street','Lexington','KY','40511-1225'));
 
-- Insert the second row.
INSERT INTO address
( address_struct )
VALUES
(('54 Hubble Street','Lexington','KY','40511-1225'));

Then, you can query them like this:

SELECT * FROM address;

It returns:

 address_id |                address_struct
------------+----------------------------------------------
          1 | ("52 Hubble Street",Lexington,KY,40511-1225)
          2 | ("54 Hubble Street",Lexington,KY,40511-1225)
(2 rows)

You must use parentheses around the ADDRESS_STRUCT column to query individual items, like:

SELECT   address_id
,       (address_struct).street_address
,       (address_struct).city
,       (address_struct).state
,       (address_struct).zip_code
FROM     address;

It returns output like a table:

 address_id |  street_address  |   city    | state |  zip_code
------------+------------------+-----------+-------+------------
          1 | 52 Hubble Street | Lexington | KY    | 40511-1225
          2 | 54 Hubble Street | Lexington | KY    | 40511-1225
(2 rows)

While you can define a table that holds an array of a composite type, there’s no syntax that appears to work with an array of a composite type. I hope this helps those interested in implementing record structures in PostgreSQL.

Written by maclochlainn

March 16th, 2015 at 2:02 am

PostgreSQL Auto IDs

with 2 comments

PostgreSQL’s approach to automatic numbering is as simple as Oracle but different than MySQL, and Microsoft SQL Server. For example, you have a two-step process with Oracle, PostgreSQL, MySQL, and Microsoft SQL Server. First, you create an Oracle table with the GENERATED AS IDENTITY clause, a PostgreSQL table with the SERIAL data type, a MySQL table with the AUTO_INCREMENT clause, and a Microsoft SQL Server table with the IDENTITY(1,1) clause. Then, you need to write an INSERT statement for Oracle, MySQL, or Microsoft SQL Server like:

  1. Oracle’s INSERT statement excludes the auto-incrementing column from the list of columns or provides a NULL value in the VALUES-list. You can then assign the RETURNING INTO result from an INSERT statement to a session-level (bind) variable.
  2. MySQL’s INSERT statement excludes the auto-incrementing column from the list of columns or provides a NULL value in the VALUES-list. You can then assign the LAST_INSERT_ID() function value to a session-level variable, and populate a foreign key column.
  3. Microsoft SQL Server’s INSERT statement excludes the auto-incrementing column from the list of columns or provides a NULL value in the VALUES-list. You can then assign the SCOPE_IDENTITY() function’s value to a session-level variable, and populate a foreign key column.

PostgreSQL differs because it works differently between the SQL and PL/pgSQL contexts. Let’s look at how you link the insert of data into two tables in both contexts.

The following PostgreSQL syntax creates an ADDRESS table with an auto incrementing ADDRESS_ID column that uses a SERIAL data type, which acts like an auto numbering column:

/* Create a customer table. */
CREATE TABLE customer
( customer_id     SERIAL CONSTRAINT customer_pk PRIMARY KEY
, first_name      VARCHAR(20)
, last_name       VARCHAR(20));
 
/* Create an address table. */
CREATE TABLE address
( address_id      SERIAL CONSTRAINT address_pk PRIMARY KEY
, customer_id     INTEGER
, street_address  VARCHAR(40)
, city            VARCHAR(30)
, state           VARCHAR(8)
, zip_code        VARCHAR(10));

If you want to insert one row into the CUSTOMER table and a related row in the ADDRESS table. You have two possible approaches. One works in both the SQL and PL/pgSQL contexts. That mechanism requires you to use a scalar subquery to capture the foreign key value of the CUSTOMER_ID column in the ADDRESS table, like this:

/* Insert into customer table. */
INSERT INTO customer
( first_name, last_name ) 
VALUES
('F. Scott','Fitzgerald');
 
/* Insert into address table. */
INSERT INTO address
( customer_id
, street_address
, city
, state
, zip_code )
VALUES
((SELECT   customer_id
  FROM     customer
  WHERE    first_name = 'F. Scott'
  AND      last_name = 'Fitzgerald')
,'599 Summit Avenue'
,'St. Paul'
,'Minnesota'
,'55102');

The RETURNING INTO clause of PostgreSQL only works in a PL/pgSQL context, like this:

DO $$
DECLARE lv_customer_id INTEGER;
BEGIN
  /* Insert into customer table. */
  INSERT INTO customer
  ( first_name, last_name ) 
  VALUES
  ('Madeleine','Smith') 
  RETURNING customer_id INTO lv_customer_id;
 
  /* Insert into address table. */
  INSERT INTO address
  ( customer_id
  , street_address
  , city
  , state
  , zip_code )
  VALUES
  ( lv_customer_id
  ,'7 Blythswood Square'
  ,'Glasgow'
  ,'Scotland'
  ,'G2 4BG');
 
  /* Manage any exceptions. */
  EXCEPTION
    WHEN OTHERS THEN
      RAISE NOTICE '% %', SQLERRM, SQLSTATE;
END$$;

You query the auto generated values and data from the INSERT statement to the CUSTOMER table with a scalar subquery against the natural key (the FIRST_NAME and LAST_NAME columns) from the ADDRESS table. The following is an example of such a query:

SELECT   *
FROM     customer c INNER JOIN address a
ON       c.customer_id = a.customer_id;

It returns:

 customer_id | first_name | last_name  | address_id | customer_id |   street_address    |   city   |   state   | zip_code
-------------+------------+------------+------------+-------------+---------------------+----------+-----------+----------
           1 | F. Scott   | Fitzgerald |          1 |           1 | 599 Summit Avenue   | St. Paul | Minnesota | 55102
           2 | Madeleine  | Smith      |          2 |           2 | 7 Blythswood Square | Glasgow  | Scotland  | G2 4BG
(2 rows)

My take is that the RETURNING column_value INTO local_value clause is a better approach than using Oracle’s .NEXTVAL and .CURRVAL values. I also think the RETURNING INTO clause is a better approach than using MySQL’s LAST_INSERT_ID() or Microsoft SQL Server’s SCOPE_IDENTITY().

Initially, I felt it was odd that the PostgreSQL disallows the RETURNING INTO clause in a SQL context, because it allows the syntax in a PL/pgSQL context. After some reflection the choice makes more sense because most developers work within a procedural context when they use transactions across two or more tables. PL/pgSQL is PostgreSQL’s procedural context from managing transactions across two or more tables.

As always, I hope this helps.

Written by maclochlainn

March 15th, 2015 at 9:37 pm

PostgreSQL New Database

with one comment

How time flies, last March I explained how to install and configure PostgreSQL on Windows. It was my intent to start posting more content on PostgreSQL but I was distracted by another writing commitment on Oracle Database 12c PL/SQL Advanced Programming Techniques, which should be available in November. It tempted me away from PostgreSQL because I got to write about how to use Java inside Oracle Database 12c, which was fun. Having completed that, I’m back on task. Here’s the second entry on PostgreSQL. It shows you howto create your own database, database administrator role, user, and how to connect with psql CLI (Command Line Interface) as the new user.

  1. Create a user-defined video_db tablespace for your database. This requires that you know where the physical files where created when you installed PostgreSQL. You can discover the directory with the following query:
SELECT   setting AS "Data Location"
FROM     pg_settings
WHERE    name = 'data_directory';
            Data Location
--------------------------------------
 C:/Program Files/PostgreSQL/9.3/data
(1 row)

You create the video_db tablespace with the following syntax:

CREATE TABLESPACE video_db
  OWNER postgres
  LOCATION 'C:\Program Files\PostgreSQL\9.3\data';

You can check the presence of the video_db tablespace after creating it with the following query:

SELECT * FROM pg_tablespace;

It should print:

  spcname   | spcowner | spcacl | spcoptions
------------+----------+--------+------------
 pg_default |       10 |        |
 pg_global  |       10 |        |
 video_db   |       10 |        |
(3 rows)

It’s important to note for those new to PostgreSQL that the pg_global and pg_default tablespaces are creating when initializing the database. The pg_global holds shared tables and the pg_default holds everything else.

  1. Create a database that uses your user-defined video_db tablespace with the following two commands:
CREATE DATABASE videodb
  WITH OWNER = postgres
       ENCODING = 'UTF8'
       TABLESPACE = video_db
       LC_COLLATE = 'English_United States.1252'
       LC_CTYPE = 'English_United States.1252'
       CONNECTION LIMIT = -1;
 
COMMENT ON DATABASE videodb
  IS 'VideoDB';
  1. Create a database role, grant the super user privileges to the role, and create a user with the role. You can do that with the following three commands:
CREATE ROLE dba
  WITH SUPERUSER;
 
GRANT ALL PRIVILEGES ON DATABASE videodb TO dba;
 
CREATE USER video
  WITH ROLE dba
       PASSWORD 'video';
  1. Connect to the new videodb database with the psql CLI as the video user. You can do that with the following OS command:
psql -d videodb -U video
  1. Once connected as the new video user, you can use a system information function to determine the current database:
SELECT current_database();

It should display:

 current_database
------------------
 videodb
(1 row)

There are many privilege options, and you should choose wisely which ones you use. As always, I hope this answers questions for other users.

Written by maclochlainn

July 24th, 2014 at 3:22 pm