/pgsql_fs2w

Primary LanguagePythonBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause


Workshop

PostgreSQL logo



Preparação do Ambiente

Baixar / Instalar

VirtualBox

  • Criar Interface de Rede Host-Only:

File → Preferences → Network → Host-only Networks → + → OK

  • Importar a Imagem Appliance:

File → Import Appliance... → (localize o arquivo .ova) → Next → Import


Sobre o PostgreSQL

to do... ;)


SQL

Structured Query LanguageLinguagem Estruturada de Consulta, é a linguagem usada nos SGBDs relacionais por padrão, no entanto cada um tem suas particularidades dentro da própria linguagem, tendo implementações diferentes.
O mesmo objetivo pode ser feito de formas SQL diferentes de um SGBD pra outro.
Assim como em linguagens de programação “comuns”, existem palavras reservadas, as quais não podem ser usadas como identificadores.
Cada comando SQL é finalizado com ponto e vírgula (;).

  1. Um SELECT simples retornando uma string:
SELECT 'Um exemplo de string';
       ?column?       
----------------------
 Um exemplo de string
 
  1. Um SELECT simples com alias retornando uma string:
SELECT 'Um exemplo de string' AS apelido;
       apelido        
----------------------
 Um exemplo de string
 
  1. Um SELECT simples com alias contendo espaços retornando uma string:
SELECT 'Um exemplo de string' AS "apelido com espaço";
  apelido com espaço  
----------------------
 Um exemplo de string
 
  1. Soma de dois números:
SELECT 5 + 2;
 ?column?
----------
        7
 
  1. Soma de dois números e alias:
SELECT 5 + 2 AS resultado;
 resultado
-----------
         7
 
  1. Concatenação e conversão para data:
SELECT 'Hoje é ' || now()::date AS "Concatenação e cast para date";
 Concatenação e cast para date
-------------------------------
 Hoje é 2017-07-05
 
  1. Concatenação e conversão para hora:
SELECT 'Agora são ' || now()::time(0) AS "Concatenação e cast para time";
 Concatenação e cast para time
-------------------------------
 Agora são 14:16:21
 

Tipos de Dados

O PostgreSQL tem vários tipos de dados.
Cada atributo de uma entidade tem um tipo mais adequado.

Além dos tipos de dados variados é interessante conhecer também máscaras de dados.



  1. Tipo inteiro padrão (int4; 4 bytes):
SELECT pg_typeof(7);
 pg_typeof
-----------
 integer
  1. Tipo inteiro de 2 bytes:
SELECT pg_typeof(7::int2);
 pg_typeof
-----------
 smallint
  1. Tipo inteiro de 8 bytes:
SELECT pg_typeof(7::int8);
 pg_typeof
-----------
 bigint
  1. Tipo desconhecido de string:
SELECT pg_typeof('foo');
 pg_typeof
-----------
 unknown
  1. Tipo de string VARCHAR:
SELECT pg_typeof('foo'::varchar);
     pg_typeof     
-------------------
 character varying
  1. Tipo de string TEXT:
SELECT pg_typeof('foo'::text);
 pg_typeof
-----------
 text
  1. Tipo para endereço IP:
SELECT '192.168.0.1'::inet;
    inet     
-------------
 192.168.0.1
  1. E se for colocado um IP inválido?:
SELECT '192.168.0.256'::inet;
ERROR:  invalid input syntax for type inet: "192.168.0.256"
LINE 1: SELECT '192.168.0.256'::inet;
               ^
  1. Endereço MAC:
SELECT '00:01:07:f4:cb:55'::macaddr;
      macaddr      
-------------------
 00:01:07:f4:cb:55
  1. Endereço CIDR:
SELECT '192.168/25'::cidr;
      cidr      
----------------
 192.168.0.0/25
  1. Validação IPv6:
SELECT 'fe80::f67e:5b4f:d208:713a/64'::inet;
             inet             
------------------------------
 fe80::f67e:5b4f:d208:713a/64
  1. Tamanho de um endereço de rede como texto:
SELECT pg_column_size('fe80::f67e:5b4f:d208:713a/64'::text);
 pg_column_size
----------------
             32
  1. Tamanho de um endereço de rede como inet:
SELECT pg_column_size('fe80::f67e:5b4f:d208:713a/64'::inet);
 pg_column_size
----------------
             22
  1. Máscara para CPF:
SELECT to_char(39684721495, '000"."000"."000-00') AS cpf;
       cpf       
-----------------
  396.847.214-95

Na máscara foi utilizado zeros para CPFs que têm zeros à esquerda.

  1. Converter para algarismos romanos:
SELECT to_char(2017, 'RN');
     to_char     
-----------------
          MMXVII
  1. Número com 7 dígitos, 2 são para casas decimais:
SELECT 10500::numeric(7, 2);
 numeric  
----------
 10500.00

DDL

Data Definition Language é uma subdivisão de SQL que trata da manipulação de objetos do banco de dados.
Seus principais comandos são:

  • CREATE (criação);
  • DROP (remoção);
  • ALTER (alteração).

Alguns tipos de objetos de bancos de dados: DATABASE, SCHEMA, TABLE, VIEW, SEQUENCE, TRIGGER, etc...

  1. Criação de uma nova base de dados:
CREATE DATABASE db_workshop;
  1. Usar o meta-comando \c do cliente psql para se conectar à nova base de dados:
\c db_workshop
  1. Função que retorna a base de dados atual:
SELECT current_database();
 current_database
------------------
 db_workshop
  1. Função que retorna o usuário atual:
SELECT current_user;
 current_user
--------------
 postgres
  1. Criação de uma nova tabela:
CREATE TABLE tb_produto(
    id serial PRIMARY KEY,
    nome varchar(50) NOT NULL,
    preco numeric(7, 2) NOT NULL,
    descricao text);
  1. Verificar a estrutura da tabela:
\d tb_produto
                                  Table "public.tb_produto"
  Column   |         Type          |                        Modifiers                        
-----------+-----------------------+---------------------------------------------------------
 id        | integer               | not null default nextval('tb_produto_id_seq'::regclass)
 nome      | character varying(50) | not null
 preco     | numeric(7,2)          | not null
 descricao | text                  |
Indexes:
    "tb_produto_pkey" PRIMARY KEY, btree (id)
  1. Alterar a tabela adicionando um novo campo:
ALTER TABLE tb_produto ADD COLUMN campo_inteiro INT;
  1. Alterar o nome da tabela:
ALTER TABLE tb_produto RENAME TO tb_prod;
  1. Verificar a estrutura da tabela:
\d tb_prod
                                     Table "public.tb_prod"
    Column     |         Type          |                        Modifiers                        
---------------+-----------------------+---------------------------------------------------------
 id            | integer               | not null default nextval('tb_produto_id_seq'::regclass)
 nome          | character varying(50) | not null
 preco         | numeric(7,2)          | not null
 descricao     | text                  |
 campo_inteiro | integer               |
Indexes:
    "tb_produto_pkey" PRIMARY KEY, btree (id)
  1. Criação de uma nova tabela:
CREATE TABLE tb_foo (campo INT);
  1. Criação de um novo schema:
CREATE SCHEMA sc_teste;
  1. Listando os schemas:
SELECT nspname AS schema FROM pg_namespace;
       schema       
--------------------
 pg_toast
 pg_temp_1
 pg_toast_temp_1
 pg_catalog
 public
 information_schema
 sc_teste
  1. Criação de uma nova tabela:
CREATE TABLE tb_foo (campo INT);
  1. Criação de uma nova tabela no novo schema:
CREATE TABLE sc_teste.tb_foo(
    campo1 INT,
    campo2 INT);
  1. Pelo catálogo do sistema verificar as tabelas criadas pelo usuário:
SELECT tablename AS tabela, schemaname AS schema
    FROM pg_tables
    WHERE schemaname !~ 'information_schema|pg_.*';
 tabela  |  schema  
---------+----------
 tb_prod | public
 tb_foo  | public
 tb_foo  | sc_teste
  1. Criação de uma view baseada na consulta anterior:
CREATE VIEW vw_tabelas AS
    SELECT tablename AS tabela, schemaname AS schema
    FROM pg_tables
    WHERE schemaname !~ 'information_schema|pg_.*';
  1. Consulta na view criada:
SELECT schema || '.' || tabela AS "Tabela com namespace" FROM vw_tabelas;
 Tabela com namespace
----------------------
 public.tb_prod
 public.tb_foo
 sc_teste.tb_foo
  1. Apagando as tabelas criadas:
DROP TABLE public.tb_prod, public.tb_foo, sc_teste.tb_foo;
  1. Apagando a view criada:
DROP VIEW vw_tabelas;
  1. Apagando o schema criado:
DROP SCHEMA sc_teste;

DML

Data Manipulation Language é a parte da linguagem SQL que trata da manipulação de registros.
Fazendo uma analogia, em linguagens de programação temos o conceito de CRUD:

CRUD SQL
Create (criar) INSERT
Retrieve (buscar) SELECT
Update (modificar) UPDATE
Delete (apagar) DELETE
  1. Criação de tabela de teste:
CREATE TABLE tb_teste(
    id serial PRIMARY KEY,
    campo1 varchar(10),
    campo2 int);
  1. Verificando a estrutura da tabela:
\d tb_teste
                                Table "public.tb_teste"
 Column |         Type          |                       Modifiers                       
--------+-----------------------+-------------------------------------------------------
 id     | integer               | not null default nextval('tb_teste_id_seq'::regclass)
 campo1 | character varying(10) |
 campo2 | integer               |
Indexes:
    "tb_teste_pkey" PRIMARY KEY, btree (id)
  1. Inserir um registro:
INSERT INTO tb_teste (campo1, campo2) VALUES ('foo', 25);
  1. Veriricar o valor atual da sequence:
SELECT currval('tb_teste_id_seq');
 currval
---------
       1
  1. Inserir valores:
INSERT INTO tb_teste VALUES (2, 'bar', (random() * 100)::int);
  1. Veriricar o valor atual da sequence:
SELECT currval('tb_teste_id_seq');
 currval
---------
       1
  1. INSERT múltiplo:
INSERT INTO tb_teste (campo1, campo2) VALUES
    ('spam', 58),
    ('eggs', 87),
    ('foobar', 92),
    ('spameggs', 43),
    ('eggsspam', 99);
ERROR:  duplicate key value violates unique constraint "tb_teste_pkey"
DETAIL:  Key (id)=(2) already exists.
  1. Veriricar o valor atual da sequence:
SELECT currval('tb_teste_id_seq');
 currval
---------
       2
  1. INSERT múltiplo:
INSERT INTO tb_teste (campo1, campo2) VALUES
    ('spam', 58),
    ('eggs', 87),
    ('foobar', 92),
    ('spameggs', 43),
    ('eggsspam', 99);
  1. INSERT com SELECT:
INSERT INTO tb_teste (campo1, campo2)
    SELECT 'string', 77;
  1. Comando TABLE para selecionar todos os registros e todas as colunas:
TABLE tb_teste;
 id |  campo1  | campo2
----+----------+--------
  1 | foo      |     25
  2 | bar      |     12
  3 | spam     |     58
  4 | eggs     |     87
  5 | foobar   |     92
  6 | spameggs |     43
  7 | eggsspam |     99
  8 | string   |     77
  1. Selecionar todos os registros e todas as colunas:
SELECT * FROM tb_teste;
 id |  campo1  | campo2
----+----------+--------
  1 | foo      |     25
  2 | bar      |     12
  3 | spam     |     58
  4 | eggs     |     87
  5 | foobar   |     92
  6 | spameggs |     43
  7 | eggsspam |     99
  8 | string   |     77
  1. Explicitar campos e filtrar por valores de id maior ou igual a 5:
SELECT id, campo2, campo1 FROM tb_teste WHERE id >= 5;
 id | campo2 |  campo1  
----+--------+----------
  5 |     92 | foobar
  6 |     43 | spameggs
  7 |     99 | eggsspam
  8 |     77 | string
  1. Iniciar uma transação:
BEGIN;
  1. UPDATE sem WHERE:
UPDATE tb_teste SET campo2 = 100;
  1. Comando TABLE:
TABLE tb_teste;
 id |  campo1  | campo2
----+----------+--------
  1 | foo      |    100
  2 | bar      |    100
  3 | spam     |    100
  4 | eggs     |    100
  5 | foobar   |    100
  6 | spameggs |    100
  7 | eggsspam |    100
  8 | string   |    100
  1. ROLLBACK na transação:
ROLLBACK;
  1. Comando TABLE:
TABLE tb_teste;
 id |  campo1  | campo2
----+----------+--------
  1 | foo      |     25
  2 | bar      |     12
  3 | spam     |     58
  4 | eggs     |     87
  5 | foobar   |     92
  6 | spameggs |     43
  7 | eggsspam |     99
  8 | string   |     77
  1. UPDATE com WHERE:
UPDATE tb_teste SET campo2 = 100 WHERE id = 2;
  1. UPDATE com a cláusula BETWEEN e RETURNING:
UPDATE tb_teste SET (campo1, campo2) = (NULL, 404)
    WHERE id BETWEEN 5 AND 9
    RETURNING campo1, campo2;
 campo1 | campo2
--------+--------
        |    404
        |    404
        |    404
        |    404
  1. Apagar todos registros onde o campo1 for NULL:
DELETE FROM tb_teste WHERE campo1 IS NULL;
  1. DELETE com RETURNING:
DELETE FROM tb_teste WHERE id = 4 RETURNING campo1, campo2;
 campo1 | campo2
--------+--------
 eggs   |     87
  1. Apagar a tabela de teste:
DROP TABLE tb_teste;
  1. Criar a tabela de teste baseada em uma consulta:
CREATE TABLE tb_teste AS SELECT generate_series(1, 2000000);
  1. Habilitar o cronômetro do psql:
\timing
  1. DELETE sem WHERE:
DELETE FROM tb_teste;
Time: 8252,219 ms
  1. Apagar a tabela de teste:
DROP TABLE tb_teste;
  1. Criar a tabela de teste baseada em uma consulta:
CREATE TABLE tb_teste AS SELECT generate_series(1, 2000000);
  1. TRUNCATE na tabela:
TRUNCATE tb_teste;
Time: 37,142 ms
  1. Inserir valores através da função generate_series:
INSERT INTO tb_teste SELECT generate_series(1, 20);
  1. Veriricar a tabela:
TABLE tb_teste;
 generate_series
-----------------
               1
               2
               3
               4
               5
               6
               7
               8
               9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
  1. Limitar a 5 registros a serem exibidos:
SELECT * FROM tb_teste LIMIT 5;
 generate_series
-----------------
               1
               2
               3
               4
               5
  1. Limitar a 5 registros a serem exibidos em ordem decrescente:
SELECT generate_series FROM tb_teste ORDER BY generate_series DESC LIMIT 5;
 generate_series
-----------------
              20
              19
              18
              17
              16
  1. Não exibir 15 registros:
SELECT generate_series FROM tb_teste OFFSET 15;
 generate_series
-----------------
              16
              17
              18
              19
              20
  1. Paginação de 5 em 5:
SELECT generate_series FROM tb_teste LIMIT 5 OFFSET 0;
 generate_series
-----------------
               1
               2
               3
               4
               5
  1. Paginação de 5 em 5:
SELECT generate_series FROM tb_teste LIMIT 5 OFFSET 5;
 generate_series
-----------------
               6
               7
               8
               9
              10
  1. Paginação de 5 em 5:
SELECT generate_series FROM tb_teste LIMIT 5 OFFSET 10;
 generate_series
-----------------
              11
              12
              13
              14
              15

Chave Estrangeira

Especifica que o valor da coluna deve corresponder a um valor que esteja na coluna da tabela referenciada. Os valores dos campos referenciados devem ser únicos. Chamamos isso de integridade referencial.

  1. Criação da Tabela tb_uf:
CREATE TEMP TABLE tb_uf(
    id char(2) PRIMARY KEY,
    nome VARCHAR(30));
  1. Criação da Tabela tb_cidade:
CREATE TEMP TABLE tb_cidade(
    id serial PRIMARY KEY,
    nome VARCHAR(40),
    uf char(2) REFERENCES tb_uf (id));
  1. Tentativa de exclusão da tabela referenciada:
DROP TABLE tb_uf;
ERROR:  cannot drop table tb_uf because other objects depend on it
DETAIL:  constraint tb_cidade_uf_fkey on table tb_cidade depends on table tb_uf
HINT:  Use DROP ... CASCADE to drop the dependent objects too.
  1. Descrição da estrutura de tb_uf:
\d tb_uf
          Table "pg_temp_2.tb_uf"
 Column |         Type          | Modifiers
--------+-----------------------+-----------
 id     | character(2)          | not null
 nome   | character varying(30) |
Indexes:
    "tb_uf_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "tb_cidade" CONSTRAINT "tb_cidade_uf_fkey" FOREIGN KEY (uf) REFERENCES tb_uf(id)
  1. Descrição da estrutura de tb_cidade:
\d tb_cidade
                               Table "pg_temp_4.tb_cidade"
 Column |         Type          |                       Modifiers                        
--------+-----------------------+--------------------------------------------------------
 id     | integer               | not null default nextval('tb_cidade_id_seq'::regclass)
 nome   | character varying(40) |
 uf     | character(2)          |
Indexes:
    "tb_cidade_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "tb_cidade_uf_fkey" FOREIGN KEY (uf) REFERENCES tb_uf(id)
  1. Comando COPY para popular a tabela:
COPY tb_uf (id, nome) FROM STDIN DELIMITER ',';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>

Em seguida os registros:

MG,MINAS GERAIS
SP,SÃO PAULO
RR,RORAIMA
AC,ACRE
AL,ALAGOAS
AP,AMAPÁ
AM,AMAZONAS
BA,BAHIA
CE,CEARÁ
DF,DISTRITO FEDERAL
ES,ESPÍRITO SANTO
GO,GOIÁS
MA,MARANHÃO
MT,MATO GROSSO
MS,MATO GROSSO DO SUL
PA,PARÁ
PR,PARANÁ
PE,PERNAMBUCO
PI,PIAUÍ
RJ,RIO DE JANEIRO
RN,RIO GRANDE DO NORTE
RS,RIO GRANDE DO SUL
RO,RONDÔNIA
SC,SANTA CATARINA
SE,SERGIPE
TO,TOCANTINS
PB,PARAIBA

Dê <ENTER> e logo em seguida <Ctrl> + <D>.

  1. Inserir valores para a tabela tb_cidade:
INSERT INTO tb_cidade (nome, uf) VALUES
    ('São Paulo', 'SP'),
    ('Belo Horizonte', 'MG'),
    ('Vitória', 'ES'),
    ('Rio de Janeiro', 'RJ');
  1. Tentativa de inserir valores cuja chave estrangeira não existe na tabela referenciada:
INSERT INTO tb_cidade (nome, uf) VALUES
    ('foo', 'NN'),
    ('bar', 'NN');
ERROR:  insert or update on table "tb_cidade" violates foreign key constraint "tb_cidade_uf_fkey"
DETAIL:  Key (uf)=(NN) is not present in table "tb_uf".
  1. Inserir um registro novo em tb_uf para possibilitar o INSERT anterior:
INSERT INTO tb_uf (id, nome) VALUES ('NN', '...');
  1. Inserir valores novos em tb_cidade:
INSERT INTO tb_cidade (nome, uf) VALUES
    ('foo', 'NN'),
    ('bar', 'NN');
  1. Tentativa de apagar o registro da tabela referenciada:
DELETE FROM tb_uf WHERE id = 'NN';
ERROR:  update or delete on table "tb_uf" violates foreign key constraint "tb_cidade_uf_fkey" on table "tb_cidade"
DETAIL:  Key (id)=(NN) is still referenced from table "tb_cidade".
  1. Apagando os registros da tabela referenciadora:
DELETE FROM tb_cidade WHERE uf = 'NN';
  1. Tentativa de apagar o registro da tabela referenciada:
DELETE FROM tb_uf WHERE id = 'NN';
  1. Apagando a tabela referenciada em modo cascata:
DROP TABLE tb_uf CASCADE;
NOTICE:  drop cascades to constraint tb_cidade_uf_fkey on table tb_cidade
  1. Verificando a estrutura da tabela tb_cidade:
\d tb_cidade
                               Table "pg_temp_4.tb_cidade"
 Column |         Type          |                       Modifiers                        
--------+-----------------------+--------------------------------------------------------
 id     | integer               | not null default nextval('tb_cidade_id_seq'::regclass)
 nome   | character varying(40) |
 uf     | character(2)          |
Indexes:
    "tb_cidade_pkey" PRIMARY KEY, btree (id)

Subqueries

Também conhecidas como subqueries, são SELECTs embutidos dentro de outro SELECT que têm por finalidade flexibilizar consultas. Esse recurso está disponível no PostgreSQL desde a versão 6.3.

  1. Clonar o repositório db_empresa para os próximos exercícios:
git clone https://github.com/juliano777/db_empresa.git
  1. Entrar no diretório db_empresa:
cd db_empresa/
  1. Criar a estrutura da base de dados db_empresa:
psql -f db_empresa-schema.sql
  1. Carregar os dados da base db_empresa:
psql -f db_empresa-data.sql db_empresa
  1. Via shell, pelo psql se conectar na base:
psql db_empresa
  1. Exibir os CPFs dos colaboradores cujo salário seja maior do que média com 90% de acréscimo:
SELECT c1.cpf
    FROM tb_colaborador c1
    WHERE c1.salario > (SELECT (avg(c2.salario) * 1.9)
                     FROM tb_colaborador c2);
cpf     
-------------
11111111111
23625814788
12345678901
  1. Exibir o CPF e a diferença do salário relativa à média:
SELECT c1.cpf "CPF", c1.salario -
    (SELECT avg(c2.salario) FROM tb_colaborador c2)::numeric(7, 2)
    "Diferença da Média"
    FROM tb_colaborador c1
    LIMIT 5;
CPF          | Diferença da Média
-------------+--------------------
11111111111  |           17445.00
23625814788  |            7445.00
33344455511  |            1945.00
12345678901  |            2445.00
10236547895  |             945.00
  1. Selecionar id de tb_colaborador onde o cpf não está entre os valores retornados da subconsulta e o setor deve ser igual a 3:
SELECT c.id
    FROM tb_colaborador c
    WHERE c.cpf NOT IN
        (SELECT p.cpf
            FROM tb_pf p
            WHERE p.dt_Nascto
                BETWEEN '1982-01-01' AND '1983-12-31') AND setor = 3;
id
----
 9
11
12
13
15

CTE

A cláusula WITH fornece uma maneira de escrever comandos auxiliares para uso em uma consulta maior. Esses comandos, que são frequentemente referenciados como Common Table Expressions ou CTEs, pode ser pensado como a definição de tabelas temporárias que existem apenas para uma consulta.
Cada comando auxiliar em uma cláusula WITH pode ser um SELECT, INSERT, UPDATE, ou DELETE; e a cláusula WITH por si só é anexada a um comando principal que pode também ser um SELECT, INSERT, UPDATE, ou DELETE.

  1. Exibir os CPFs dos colaboradores cujo salário seja maior do que média com 90% de acréscimo:
WITH c2 AS (SELECT (avg(salario) * 1.9) AS media FROM tb_colaborador)
    SELECT c1.cpf
        FROM tb_colaborador c1, c2
        WHERE c1.salario > c2.media;
cpf     
-------------
11111111111
23625814788
12345678901
  1. Exibir o CPF e a diferença do salário relativa à média:
WITH c2 AS (SELECT avg(salario)::numeric(7, 2) media
            FROM tb_colaborador)
    SELECT c1.cpf "CPF", c1.salario - c2.media "Diferença da Média"
        FROM tb_colaborador c1, c2
        LIMIT 5;
CPF          | Diferença da Média
-------------+--------------------
11111111111  |           17445.00
23625814788  |            7445.00
33344455511  |            1945.00
12345678901  |            2445.00
10236547895  |             945.00

Junções

CROSS JOIN

Retorna um conjunto de informações o qual é resultante de todas as combinações possíveis entre os registros das tabelas envolvidas.

  1. Criação da tabela de carros:
CREATE TEMP TABLE tb_carro(
    id serial PRIMARY KEY,
    nome VARCHAR(20));
  1. Criação da tabela de cores:
CREATE TEMP TABLE tb_cor(
    id serial PRIMARY KEY,
    nome VARCHAR(20));
  1. Popular a tabela de carros:
INSERT INTO tb_carro (nome) VALUES
    ('Fiat 147'),
    ('VW Fusca'),
    ('Ford Corcel'),
    ('GM Opala');
  1. Popular a tabela de cores:
INSERT INTO tb_cor (nome) VALUES
    ('Verde'),
    ('Azul'),
    ('Amarelo'),
    ('Branco'),
    ('Preto'),
    ('Vermelho'),
    ('Laranja'),
    ('Cinza');
  1. Duas formas diferentes para fazer junção cruzada para obter as combinações possíveis exibindo somente os primeiros 5 registros:
SELECT c1.nome carro, c2.nome cor
    FROM tb_carro c1, tb_cor c2
    LIMIT 5;
SELECT c1.nome carro, c2.nome cor
    FROM tb_carro c1
    CROSS JOIN
    tb_cor c2
    LIMIT 5;
carro     |   cor   
----------+---------
Fiat 147  | Verde
Fiat 147  | Azul
Fiat 147  | Amarelo
Fiat 147  | Branco
Fiat 147  | Preto

NATURAL JOIN

Faz uma junção implícita tomando como base as colunas de mesmo nome nas tabelas envolvidas.
É recomendável que ao invés de usar NATURAL JOIN se use INNER JOIN, pois essa última explicita qual é o critério de vínculo entre tabelas deixando a leitura mais amigável.

  1. Duas formas diferentes para fazer junção cruzada para obter as combinações possíveis exibindo somente os primeiros 5 registros:
SELECT p.nome, p.sobrenome, c.salario
    FROM tb_colaborador c
    NATURAL JOIN tb_pf p
    WHERE c.salario >= 5000;
nome        | sobrenome | salario  
------------+-----------+----------
Chiquinho   | da Silva  | 20000.00
Aldebarina  | Ferreira  | 10000.00
Tungstênia  | Santana   |  5000.00

INNER JOIN

Retorna as informações apenas de acordo com as linhas que obedeçam as definições de relacionamento. Existe uma ligação lógica para se fazer a junção, a qual é declarada explicitamente.
É a junção padrão, que faz com que inclusive a palavra-chave INNER possa ser omitida.
Para o critério de junção pode-se usar a cláusula ON que especifica qual a condição usada ou USING que apenas diz qual campo com o mesmo nome em ambas as tabelas deve ser utilizado.

  1. INNER JOINs com ON e USING:
SELECT p.nome, p.sobrenome, c.salario
    FROM tb_colaborador c
    INNER JOIN tb_pf p
    ON c.cpf = p.cpf
    WHERE c.salario >= 5000;
SELECT p.nome, p.sobrenome, c.salario
    FROM tb_colaborador c
    INNER JOIN tb_pf p
    USING (cpf)
    WHERE c.salario >= 5000;
nome        | sobrenome | salario  
------------+-----------+----------
Chiquinho   | da Silva  | 20000.00
Aldebarina  | Ferreira  | 10000.00
Tungstênia  | Santana   |  5000.00

OUTER JOIN

Assim como na INNER JOIN, existe uma ligação lógica, mas não retorna apenas as informações que satisfaçam a regra da junção. OUTER JOINs podem ser dos tipos:

  • LEFT OUTER JOIN: retorna todos os registros da tabela à esquerda;
  • RIGHT OUTER JOIN: retorna todos os registros da tabela à direita;
  • FULL OUTER JOIN: retorna todos os registros de ambos os lados.

É de uso opcional a palavra OUTER.
Para os exercícios serão inseridos dados na tabela tb_pf, que não tenham correspondência na tabela tb_colaborador.

  1. Inserir valores para os testes:
INSERT INTO tb_pf VALUES
    ('Elzinda', '1979-05-03', NULL, '10293847567', '2233551177', 'Ambrózio',
     'f'),
    ('Carmelita', '1915-01-01', NULL, '00000000011', '0000000011', 'Antunes',
     'f'),
    ('Sizorfino', '1978-11-26', NULL, '00000000111', '0000000111', 'Chagas',
     'm');

LEFT OUTER JOIN

  1. Tabela tb_pf à esquerda:
SELECT p.nome||' '||p.sobrenome "Nome Completo", c.id
    FROM tb_pf p
    LEFT OUTER JOIN tb_colaborador c
        USING (cpf)
    OFFSET 35;
Nome Completo       | id
--------------------+----
Estriga Souto       | 36
Eltaminácio Santos  | 37
Maria dos Santos    | 38
Etelvino Castro     | 39
Carzózio da Silva   | 40
Genovézio Gomes     |   
Carmelita Antunes   |   
Elzinda Ambrózio    |   
Sizorfino Chagas    |

RIGHT OUTER JOIN

  1. Tabela tb_pf à direita:
SELECT p.nome||' '||p.sobrenome "Nome Completo", c.id
    FROM tb_colaborador c
    RIGHT OUTER JOIN tb_pf p USING (cpf)
    OFFSET 35;
Nome Completo       | id
--------------------+----
Estriga Souto       | 36
Eltaminácio Santos  | 37
Maria dos Santos    | 38
Etelvino Castro     | 39
Carzózio da Silva   | 40
Genovézio Gomes     |   
Carmelita Antunes   |   
Elzinda Ambrózio    |   
Sizorfino Chagas    |

FULL OUTER JOIN

  1. FULL OUTER JOIN:
SELECT p.nome||' '||p.sobrenome "Nome Completo", c.id
    FROM tb_pf p
    FULL OUTER JOIN tb_colaborador c USING (cpf)
    OFFSET 35;
SELECT p.nome||' '||p.sobrenome "Nome Completo", c.id
    FROM tb_colaborador c
    FULL OUTER JOIN tb_pf p USING (cpf)
    OFFSET 35;
Nome Completo       | id
--------------------+----
Estriga Souto       | 36
Eltaminácio Santos  | 37
Maria dos Santos    | 38
Etelvino Castro     | 39
Carzózio da Silva   | 40
Genovézio Gomes     |   
Carmelita Antunes   |   
Elzinda Ambrózio    |   
Sizorfino Chagas    |  

Indexação

Um índice (INDEX) é um recurso que agiliza buscas de informações em tabelas. Imagine que você está em uma biblioteca e gostaria de procurar “O Senhor dos Anéis”, de Tolkien. O que seria mais fácil?: Começar a vasculhar a biblioteca inteira até achar o livro desejado ou buscar no arquivo da biblioteca, nas fichas que estão ordenados por autor? Logicamente se for escolhido ir buscar nas fichas a busca será muito mais rápida, pois não será necessário vasculhar livro por livro na biblioteca, porque haverá uma ficha do autor e daquele livro que mostrará exatamente onde está o livro desejado. É um apontamento para a localização do livro. Um índice de banco de dados funciona da mesma forma.
Indexamos campos usados como critérios de filtragem numa consulta (cláusula WHERE, por exemplo) e aqueles cujos valores são mais restritivos comparados a outros valores da tabela.
Seu funcionamento consiste em criar ponteiros para dados gravados em campos específicos. Quando não existe índice num campo usado como critério de filtragem, é feita uma varredura em toda a tabela, de maneira que haverá execuções de entrada e saída (I/O) de disco desnecessárias, além de também desperdiçar processamento.

Dicas Gerais

  • Crie índices para campos que são utilizados em condições de consultas, pelo menos as consultas mais frequentes;
  • Crie índices para campos de chaves estrangeiras e em campos envolvidos como critérios de junção (JOIN);
  • Se houver uma consulta frequente utilize índices parciais com sua condição conforme a consulta;
  • Para consultas que buscam faixas de valores é bom ter um índice clusterizado para isso.
  1. Criação de tabela de teste:
SELECT
    generate_series(1, 20000)::int2 AS campo1, -- 20 mil registros
    round((random()*10000))::int2 AS campo2,
    round((random()*10000))::int2 AS campo3 INTO tb_index;
  1. Verificando o plano de execução:
EXPLAIN ANALYZE
SELECT campo1 FROM tb_index WHERE campo2 BETWEEN 235 AND 587;
QUERY PLAN                                                
---------------------------------------------------------------------------------------------------------
Seq Scan on tb_index  (cost=0.00..389.00 rows=688 width=2) (actual time=0.036..10.163 rows=679 loops=1)
Filter: ((campo2 >= 235) AND (campo2 <= 587))
Rows Removed by Filter: 19321
Planning time: 0.189 ms
Execution time: 10.245 ms
  1. Criação de índice:
CREATE INDEX idx_tb_index_campo2 ON tb_index (campo2);
  1. Verificando o plano de execução:
EXPLAIN ANALYZE
SELECT campo1 FROM tb_index WHERE campo2 BETWEEN 235 AND 587;
    QUERY PLAN                                                            
---------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_index  (cost=15.34..114.66 rows=688 width=2) (actual time=0.344..0.981 rows=679 loops=1)
Recheck Cond: ((campo2 >= 235) AND (campo2 <= 587))
Heap Blocks: exact=89
->  Bitmap Index Scan on idx_tb_index_campo2  (cost=0.00..15.17 rows=688 width=0) (actual time=0.305..0.305 rows=679 loops=1)
Index Cond: ((campo2 >= 235) AND (campo2 <= 587))
Planning time: 0.363 ms
Execution time: 1.073 ms
  1. Criação de índice composto:
CREATE INDEX idx_tb_index_campo2_campo3 ON tb_index (campo2, campo3);
  1. Verificando o plano de consulta:
EXPLAIN ANALYZE
SELECT campo1 FROM tb_index
    WHERE (campo2 BETWEEN 235 AND 587) AND campo3 = 1000;
QUERY PLAN                                                              
--------------------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_tb_index_campo2_campo3 on tb_index  (cost=0.29..20.90 rows=1 width=2) (actual time=0.086..0.190 rows=1 loops=1)
Index Cond: ((campo2 >= 235) AND (campo2 <= 587) AND (campo3 = 1000))
Planning time: 0.414 ms
Execution time: 0.227 ms
  1. Apagando a tabela do exercício anterior:
DROP TABLE tb_index;
  1. Criação da Tabela de Teste (Não Temporária):
CREATE TABLE tb_index(campo1 int);
  1. Inserção de 1 Milhão de Registros:
INSERT INTO tb_index SELECT generate_series(1, 1000000);
  1. Análise sem Índices de Valores Múltiplos de 19:
EXPLAIN ANALYZE
SELECT * FROM tb_index WHERE campo1 % 19 = 0;
QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
Seq Scan on tb_index  (cost=0.00..19425.00 rows=5000 width=4) (actual time=0.033..309.389 rows=52631 loops=1)
Filter: ((campo1 % 19) = 0)
Rows Removed by Filter: 947369
Planning time: 0.117 ms
Execution time: 311.717 ms
  1. Criação de Índice Total:
CREATE INDEX idx_teste_index_total ON tb_index (campo1);
  1. Verifica o plano de execução:
EXPLAIN ANALYZE
SELECT * FROM tb_index WHERE campo1 % 19 = 0;
QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
Seq Scan on tb_index  (cost=0.00..19425.00 rows=5000 width=4) (actual time=0.036..313.418 rows=52631 loops=1)
Filter: ((campo1 % 19) = 0)
Rows Removed by Filter: 947369
Planning time: 0.271 ms
Execution time: 315.805 ms
  1. Criação de índice parcial múltiplos de 19:
CREATE INDEX idx_teste_index_19 ON tb_index (campo1) WHERE campo1 % 19 = 0;
  1. Análise com valores múltiplos de 19:
EXPLAIN ANALYZE
SELECT * FROM tb_index WHERE campo1 % 19 = 0;
QUERY PLAN                                                                 
--------------------------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_teste_index_19 on tb_index  (cost=0.29..157.29 rows=5000 width=4) (actual time=0.053..37.198 rows=52631 loops=1)
Heap Fetches: 52631
Planning time: 0.321 ms
Execution time: 40.926 ms
  1. Análise com uma consulta de condição diferente de números divíveis por 19:
EXPLAIN ANALYZE
SELECT * FROM tb_index WHERE campo1 BETWEEN 241 AND 875;
QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_teste_index_total on tb_index  (cost=0.42..26.82 rows=620 width=4) (actual time=0.043..0.487 rows=635 loops=1)
Index Cond: ((campo1 >= 241) AND (campo1 <= 875))
Heap Fetches: 635
Planning time: 0.333 ms
Execution time: 0.563 ms

Range Types

Range Types são tipos de dados que representam uma faixa de valores de algum tipo de elemento (chamado de subtipo de faixa).

Simbologia de Limites de Intervalos

( ) → Parênteses: para simbolizar respectivamente limites inicial e final do tipo aberto;
[ ] → Colchetes: representam respectivamente limites inicial e final do tipo fechado.

Built-in Range Types

O PostgreSQL nativamente vem com os seguintes range types:

  • int4range: Inteiro de 4 bytes (int4, int, integer);
  • int8range: Inteiro de 8 bytes (int8, bigint);
  • numrange: Ponto flutuante (numeric);
  • tsrange: timestamp sem time zone;
  • tstzrange: timestamp com time zone;
  • daterange: Data (date)

Em adição, você pode definir seus próprios range types; veja CREATE TYPE para mais informações.

  1. Intervalo fechado de 2 a 9 (int4):
SELECT '[2, 9]'::int4range;
int4range
-----------
[2,10)
  1. Intervalo aberto em 2 e fechado em 9 (int4):
SELECT '(2, 9]'::int4range;
int4range
-----------
[3,10)
  1. Intervalos fechados usando data:
SELECT '[2017-07-15 09:00, 2017-07-15 14:00]'::tsrange;
tsrange                    
-----------------------------------------------
["2017-07-15 09:00:00","2017-07-15 14:00:00"]
  1. No intervalo de 10 a 20 contém 3 (função int4range)?
SELECT int4range(10, 20) @> 3;
 ?column?
----------
 f
  1. O valor 10 está contido entre 10 e 20 (função int4range)?
SELECT 10 <@ int4range(10, 20);
?column?
----------
t
  1. Para fixação dos conceitos aprendidos nos exercícios anteriores, agora vamos criar uma tabela de reservas:
CREATE TABLE tb_reserva(
    sala int PRIMARY KEY,
    duracao tsrange);
  1. Populando a tabela:
INSERT INTO tb_reserva VALUES
    (1, '[2014-11-01 14:30, 2014-11-01 18:30)'),
    (2, '[2014-11-02 11:00, 2014-11-02 15:00)'),
    (3, '[2014-11-03 11:00, 2014-11-03 15:00)'),
    (4, '[2014-11-04 17:00, 2014-11-04 19:00)');
  1. Verificando a tabela:
TABLE tb_reserva;
sala  |                    duracao                    
------+-----------------------------------------------
   1  | ["2014-11-01 14:30:00","2014-11-01 18:30:00")
   2  | ["2014-11-02 11:00:00","2014-11-02 15:00:00")
   3  | ["2014-11-03 11:00:00","2014-11-03 15:00:00")
   4  | ["2014-11-04 17:00:00","2014-11-04 19:00:00")
  1. Verificando se há alguma sala cuja data e hora esteja contida em alguma duração de reserva:
SELECT * FROM tb_reserva WHERE '2014-11-02 12:33'::timestamp <@ duracao;
sala  |                    duracao                    
------+-----------------------------------------------
   2  | ["2014-11-02 11:00:00","2014-11-02 15:00:00")
  1. Verificando se há alguma sala cuja duração contém a data e hora informada:
SELECT * FROM tb_reserva WHERE duracao @> '2014-11-03 14:21'::timestamp;
sala  |                    duracao                    
------+-----------------------------------------------
   3  | ["2014-11-03 11:00:00","2014-11-03 15:00:00")

DOMAIN

Domínio é um tipo de dado personalizado em que se pode definir como os dados serão inseridos de acordo com restrições definidas opcionalmente.

  1. Criação de um domínio, para validar CEPs que aceita inteiros com sete ou oito dígitos:
CREATE DOMAIN dom_cep AS integer
    CONSTRAINT chk_cep
        CHECK (length(VALUE::text) = 7
        OR length(VALUE::text) = 8);
  1. Criação de uma tabela que usará o domínio criado como tipo de dado para uma coluna:
CREATE TEMP TABLE tb_endereco_tmp(
    id serial PRIMARY KEY,
    cep dom_cep,
    logradouro text,
    numero smallint,
    cidade varchar(50),
    uf char(2));
  1. Inserções na Tabela com o domíno criado:
INSERT INTO tb_endereco_tmp (cep, logradouro, numero, cidade, uf) VALUES
    (1001000, 'Pça. da Sé', null,'São Paulo','SP'),
    (30130003, 'Av. Afonso Pena', 1212, 'Belo Horizonte', 'MG');
  1. Selecionando os dados:
SELECT
    to_char(cep, '00000-000') "CEP",
    logradouro "Logradouro",
    numero "Número",
    cidade "Cidade",
    uf "Estado"
    FROM tb_endereco_tmp;
    CEP     |   Logradouro    | Número |     Cidade     | Estado
------------+-----------------+--------+----------------+--------
  01001-000 | Pça. da Sé      |        | São Paulo      | SP
  30130-003 | Av. Afonso Pena |   1212 | Belo Horizonte | MG

Acessando o Postgres via Python (psycopg2)

import pprint  # Módulo "Pretty Print"
import psycopg2  # Driver PostgreSQL

# String de conexão
str_conexao = """
dbname='db_empresa'
user='aluno'
password='123'
host='192.168.56.2'
application_name='pypg'
options='-c log_min_duration_statement=0'
"""

# Dentro do bloco try fazer a conexão ao banco
try:
    # Conexão ao banco
    conexao = psycopg2.connect(str_conexao)

except Exception as e:
    # Exibe a string abaixo e também a mensagem de Exception
    print('Erro de conexão!\n\n{}'.format(e))

# Após a conexão devidamente estabelecida se conecte ao servidor de banco
# de dados via SSH.
# Se conecte localmente via psql e execute a seguinte query:
# SELECT * FROM pg_stat_activity WHERE client_addr IS NOT NULL;



# Criação de cursor para executar queries
cursor = conexao.cursor()


# String SQL
str_sql = 'SELECT cpf, nome, sobrenome FROM tb_pf;'

# Executa o comando SQL
cursor.execute(str_sql)

# Linhas retornadas pela consulta
rows = cursor.fetchall()

# Verifica o tipo da variável
type(rows)

# Criação da variável para impressão amigável em tela
pp = pprint.PrettyPrinter(indent=4)

# Método pprint para exibir de forma amigável o conteúdo de rows
pp.pprint(rows)

# Exibe a segunda linha
print(rows[1])

# Segunda linha e primeira coluna (cpf)
print(rows[1][0])


# String SQL
str_sql = "INSERT INTO tb_pf (cpf, nome) VALUES (95123785247, 'Foo');"


# Executa o comando SQL
cursor.execute(str_sql)


# Verifique no banco se o registro foi inserido

# Verifica se a conexão está fechada (0 = aberta, 1 = fechada)
conexao.closed

# Verifica se o cursor está fechado (booleano)
cursor.closed

# Efetiva todos os comandos dados na conexão
conexao.commit()

# Fecha a conexão
conexao.close()

# Checa se a conexão está fechada
conexao.closed

# Checa se o cursor está fechado
cursor.closed

# Fazer uma nova conexão
try:
    # Conexão ao banco
    conexao = psycopg2.connect(str_conexao)

except Exception as e:
    # Exibe a string abaixo e também a mensagem de Exception
    print('Erro de conexão!\n\n{}'.format(e))

# Determina que a conexão não será transacional
conexao.set_session(readonly=False, autocommit=True)

# Criação de um novo cursor
cursor = conexao.cursor()

# String SQL
str_sql = "INSERT INTO tb_pf (cpf, nome) VALUES (17223785247, 'Bar');"

# Executa o comando SQL
cursor.execute(str_sql)

# Determina que a conexão será somente leitura
conexao.set_session(readonly=True, autocommit=True)


# String SQL
str_sql = "INSERT INTO tb_pf (cpf, nome) VALUES (33223785247, 'Foo Bar');"


# Executa o comando SQL
cursor.execute(str_sql)

PL/Python: Programando em Python no PostgreSQL

Sintaxe:

CREATE [OR REPLACE FUNCTION] funcao([params])
RETURNS tipo AS $$
. . .
codigo
. . .
$$ LANGUAGE linguagem;

PL/pgSQL

  1. Criação da função fc_foo sem parâmetros:
CREATE OR REPLACE FUNCTION fc_foo()
RETURNS VARCHAR AS $$

BEGIN
    RETURN 'Hello, World!';
END; $$ LANGUAGE PLPGSQL;
  1. Criação da função fc_foo com parâmetros:
CREATE OR REPLACE FUNCTION fc_foo(num1 INT, num2 INT)
RETURNS INT AS $$

DECLARE res INT;

BEGIN
    RETURN (num1 + num2) * 2;
END; $$ LANGUAGE PLPGSQL;
  1. Execução da função fc_foo sem parâmetros:
SELECT fc_foo();
fc_foo     
---------------
Hello, World!
  1. Execução da função fc_foo com parâmetros:
SELECT fc_foo(2, 5);
fc_foo
--------
    14

Blocos Anônimos

Sintaxe:

DO $$

. . .
codigo
. . .
$$ LANGUAGE linguagem;
  1. Ajustando o nível de mensagens para a aplicação cliente:
SET client_min_messages = 'notice';
  1. Bloco anônimo para mostrar o número conexões ao banco no momento:
DO $$
DECLARE n_con INT;
BEGIN
    SELECT count(client_addr)
        INTO n_con
        FROM pg_stat_activity;

    RAISE NOTICE
        'Número de conexões não-locais ao banco: %',
        n_con;

END; $$ LANGUAGE PLPGSQL;
NOTICE:  Número de conexões não-locais ao banco: 10

PL/Python

  1. ($) Crie o diretório Python para módulos:
mkdir /var/lib/pgsql/python
  1. ($) Variável de ambiente Python no arquivo de perfil do usuário de sistema postgres:
echo 'export PYTHONPATH="${PYTHONPATH}:/var/lib/pgsql/python' >> \
~postgres/.bashrc
  1. ($) Variável de ambiente Python no arquivo de perfil do usuário de sistema postgres:
echo 'export PYTHONPATH="${PYTHONPATH}:/var/lib/pgsql/python' >> \
~postgres/.bashrc
  1. (#) Podemos incluir a variável PYTHONPATH no Unit File SystemD do serviço do PostgreSQL:
systemctl edit --full postgresql.service

Na sessão Unit, diretiva Environment do Unit File:

[Unit]
. . .
Environment=PYTHONPATH=/var/lib/pgsql/python
  1. (#) Reinicialize o serviço do PostgreSQL:
systemctl restart postgresq.service
  1. No psql, criar a base de dados de exemplo:
CREATE DATABASE db_plpython;
  1. Conectar à base:
\c db_plpython
  1. Habilitar PL/Python 3 na base de dados atual:
CREATE EXTENSION plpython3u;
  1. Uma simples função anônima:
DO $$                       
import sys
print(sys.version)
$$ LANGUAGE PLPYTHON3U;
3.4.5 (default, May 29 2017, 15:17:55)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
  1. Função em PL/Python 3 sem argumentos:
CREATE OR REPLACE FUNCTION fc_py()
RETURNS VARCHAR AS $$

return 'Hello, World!!!'

$$ LANGUAGE plpython3u;
  1. Sobrecarga de função:
CREATE OR REPLACE FUNCTION fc_py(num1 INT, num2 INT)
RETURNS INT AS $$

return num1 + num2
$$ LANGUAGE plpython3u;
  1. Testando a primeira função criada:
SELECT fc_py();
fc_py      
-----------------
Hello, World!!!
  1. Testando a segunda função criada:
SELECT fc_py(2, 5);
fc_py
-------
    7
  1. ($) Criação do módulo teste dentro do diretório que está em PYTHONPATH:
cat << EOF > /var/lib/pgsql/python/teste.py
def py_version():
    import sys
    return sys.version.split()[0]
EOF
  1. ($) Conexão ao banco via psql:
psql db_plpython
  1. Criação da função seguindo as boas práticas:
CREATE OR REPLACE FUNCTION fc_py_version()
RETURNS VARCHAR AS $$

from teste import py_version
return py_version()

$$ LANGUAGE plpython3u;
  1. Teste da função:
SELECT 'Minha versão de Python é: '|| fc_py_version()
    AS "Versão de Python";
Versão de Python         
---------------------------------
Minha versão de Python é: 3.4.5