PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Dicas e truques do Postgres


Você trabalha com o Postgres diariamente? Escreva o código do aplicativo que fala com o Postgres? Em seguida, confira os pequenos trechos de SQL abaixo que podem ajudá-lo a trabalhar mais rápido!

Inserir várias linhas em uma instrução


A instrução INSERT pode inserir mais de uma linha em uma única instrução:
INSERT INTO planets (name, gravity)
     VALUES ('earth',    9.8),
            ('mars',     3.7),
            ('jupiter', 23.1);

Leia mais sobre o que o INSERT pode fazer aqui.

Inserir uma linha e retornar valores atribuídos automaticamente


Valores gerados automaticamente com construções DEFAULT/serial/IDENTITY podem ser retornados pela instrução INSERT usando a cláusula RETURNING. Da perspectiva do código do aplicativo, esse INSERT é executado como um SELECT que retorna um conjunto de registros.
-- table with 2 column values auto-generated on INSERT
CREATE TABLE items (
    slno       serial      PRIMARY KEY,
    name       text        NOT NULL,
    created_at timestamptz DEFAULT now()
);

INSERT INTO items (name)
     VALUES ('wooden axe'),
            ('loom'),
            ('eye of ender')
  RETURNING name, slno, created_at;

-- returns:
--      name     | slno |          created_at
-- --------------+------+-------------------------------
--  wooden axe   |    1 | 2020-08-17 05:35:45.962725+00
--  loom         |    2 | 2020-08-17 05:35:45.962725+00
--  eye of ender |    3 | 2020-08-17 05:35:45.962725+00

Chaves primárias UUID geradas automaticamente


Às vezes, UUIDs são usados ​​em vez de chaves primárias por vários motivos. Aqui está como você pode usar um UUID em vez de um serial ou IDENTITY:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE items (
    id    uuid DEFAULT uuid_generate_v4(),
    name  text NOT NULL
);

INSERT INTO items (name)
     VALUES ('wooden axe'),
            ('loom'),
            ('eye of ender')
  RETURNING id, name;
  
-- returns:
--                   id                  |     name
-- --------------------------------------+--------------
--  1cfaae8c-61ff-4e82-a656-99263b7dd0ae | wooden axe
--  be043a89-a51b-4d8b-8378-699847113d46 | loom
--  927d52eb-c175-4a97-a0b2-7b7e81d9bc8e | eye of ender

Inserir se não existir, atualizar caso contrário


No Postgres 9.5 e posterior, você pode upsert diretamente usando a construção ON CONFLICT:
CREATE TABLE parameters (
    key   TEXT PRIMARY KEY,
    value TEXT
);

-- when "key" causes a constraint violation, update the "value"
INSERT INTO parameters (key, value) 
     VALUES ('port', '5432')
ON CONFLICT (key) DO
            UPDATE SET value=EXCLUDED.value;

Copiar linhas de uma tabela para outra


A instrução INSERT tem um formato em que os valores podem ser fornecidos por uma instrução SELECT. Use isso para copiar linhas de uma tabela para outra:
-- copy between tables with similar columns 
  INSERT INTO pending_quests
SELECT * FROM quests
        WHERE progress < 100;

-- supply some values from another table, some directly
  INSERT INTO archived_quests
       SELECT now() AS archival_date, *
         FROM quests
        WHERE completed;

Se você deseja carregar tabelas em massa, confira também o comando COPY, que pode ser usado para inserir linhas de um arquivo de texto ou CSV.

Excluir e retornar informações excluídas


Você pode usar o RETURNING cláusula para retornar valores das linhas que foram excluídas usando uma instrução de exclusão em massa:
-- return the list of customers whose licenses were deleted after expiry
DELETE FROM licenses
      WHERE now() > expiry_date
  RETURNING customer_name;

Mover linhas de uma tabela para outra


Você pode mover linhas de uma tabela para outra em uma única instrução, usando CTEs com DELETE .. RETURNING :
-- move yet-to-start todo items from 2020 to 2021
WITH ah_well AS (
    DELETE FROM todos_2020
          WHERE NOT started
      RETURNING *
)
INSERT INTO todos_2021
            SELECT * FROM ah_well;

Atualizar linhas e retornar valores atualizados


A cláusula RETURNING também pode ser usada em UPDATEs. Observe que apenas os novos valores das colunas atualizadas podem ser retornados dessa maneira.
-- grant random amounts of coins to eligible players
   UPDATE players
      SET coins = coins + (100 * random())::integer
    WHERE eligible
RETURNING id, coins;

Se você precisar do valor original das colunas atualizadas:é possível através de uma auto-junção, mas não há garantia de atomicidade. Tente usar um SELECT .. FOR UPDATE em vez de.

Atualize algumas linhas aleatórias e retorne as atualizadas


Veja como você pode escolher algumas linhas aleatórias de uma tabela, atualizá-las e retornar as atualizadas, tudo de uma vez:
WITH lucky_few AS (
    SELECT id
      FROM players
  ORDER BY random()
     LIMIT 5
)
   UPDATE players
      SET bonus = bonus + 100 
    WHERE id IN (SELECT id FROM lucky_few)
RETURNING id;

Criar uma tabela como outra tabela


Use a construção CREATE TABLE .. LIKE para criar uma tabela com as mesmas colunas que outra:
CREATE TABLE to_be_audited (LIKE purchases);

Por padrão, isso não cria índices, restrições, padrões etc. semelhantes. Para isso, pergunte explicitamente ao Postgres:
CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Veja a sintaxe completa aqui.

Extrair um conjunto aleatório de linhas para outra tabela


Desde o Postgres 9.5, o recurso TABLESAMPLE está disponível para extrair uma amostra de linhas de uma tabela. Existem dois métodos de amostragem atualmente, e bernoulli é geralmente o que você quer:
-- copy 10% of today's purchases into another table
INSERT INTO to_be_audited
     SELECT *
       FROM purchases
TABLESAMPLE bernoulli(10)
      WHERE transaction_date = CURRENT_DATE;

O sistema O método tableampling é mais rápido, mas não retorna uma distribuição uniforme. Consulte os documentos para obter mais informações.

Criar uma tabela a partir de uma consulta selecionada


Você pode usar a construção CREATE TABLE .. AS para criar a tabela e preenchê-la a partir de uma consulta SELECT, tudo de uma vez:
CREATE TABLE to_be_audited AS
      SELECT *
        FROM purchases
 TABLESAMPLE bernoulli(10)
       WHERE transaction_date = CURRENT_DATE;

A tabela resultante é como uma visão materializada sem uma consulta associada a ela. Leia mais sobre CREATE TABLE .. AS aqui.

Criar tabelas não registradas


Desconectado tabelas não são suportadas por registros WAL. Isso significa que as atualizações e exclusões dessas tabelas são mais rápidas, mas não são tolerantes a falhas e não podem ser replicadas.
CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Criar tabelas temporárias


Temporário tabelas são tabelas não registradas implicitamente, com um tempo de vida mais curto. Eles se autodestroem automaticamente no final de uma sessão (padrão) ou no final da transação.

Os dados em tabelas temporárias não podem ser compartilhados entre sessões. Várias sessões podem criar tabelas temporárias com o mesmo nome.
-- temp table for duration of the session
CREATE TEMPORARY TABLE scratch_20200817_run_12 (LIKE report_v3);

-- temp table that will self-destruct after current transaction
CREATE TEMPORARY TABLE scratch_20200817_run_12
                      (LIKE report_v3)
                      ON COMMIT DROP;

-- temp table that will TRUNCATE itself after current transaction
CREATE TEMPORARY TABLE scratch_20200817_run_12
                       (LIKE report_v3)
                       ON COMMIT DELETE ROWS;

Adicionar comentários


Comentários podem ser adicionados a qualquer objeto no banco de dados. Muitas ferramentas, incluindopg_dump, entendem isso. Um comentário útil pode evitar uma tonelada de limpeza problemática!
COMMENT ON INDEX idx_report_last_updated
        IS 'needed for the nightly report app running in dc-03';

COMMENT ON TRIGGER tgr_fix_column_foo
        IS 'mitigates the effect of bug #4857';

Bloqueios de aviso


Bloqueios consultivos podem ser usados ​​para coordenar ações entre dois aplicativos conectados ao mesmo base de dados. Você pode usar esse recurso para implementar um mutex global e distribuído para uma determinada operação, por exemplo. Leia tudo sobre isso aqui nos documentos.
-- client 1: acquire a lock 
SELECT pg_advisory_lock(130);
-- ... do work ...
SELECT pg_advisory_unlock(130);

-- client 2: tries to do the same thing, but mutually exclusive
-- with client 1
SELECT pg_advisory_lock(130); -- blocks if anyone else has held lock with id 130

-- can also do it without blocking:
SELECT pg_try_advisory_lock(130);
-- returns false if lock is being held by another client
-- otherwise acquires the lock then returns true

Agregar em matrizes, matrizes JSON ou strings


Postgres fornece funções agregadas que concatenam valores em um GRUPO arrays toyield, arrays JSON ou strings:
-- get names of each guild, with an array of ids of players that
-- belong to that guild
  SELECT guilds.name AS guild_name, array_agg(players.id) AS players
    FROM guilds
    JOIN players ON players.guild_id = guilds.id
GROUP BY guilds.id;

-- same but the player list is a CSV string
  SELECT guilds.name, string_agg(players.id, ',') -- ...
  
-- same but the player list is a JSONB array
  SELECT guilds.name, jsonb_agg(players.id) -- ...
  
-- same but returns a nice JSONB object like so:
-- { guild1: [ playerid1, playerid2, .. ], .. }
SELECT jsonb_object_agg(guild_name, players) FROM (
  SELECT guilds.name AS guild_name, array_agg(players.id) AS players
    FROM guilds
    JOIN players ON players.guild_id = guilds.id
GROUP BY guilds.id
) AS q;

Agregados com pedido


Já que estamos no assunto, veja como definir a ordem dos valores que são passados ​​para a função de agregação, dentro de cada grupo :
-- each state with a list of counties sorted alphabetically
  SELECT states.name, string_agg(counties.name, ',' ORDER BY counties.name)
    FROM states JOIN counties
    JOIN states.name = counties.state_name
GROUP BY states.name;

Sim, há uma cláusula ORDER BY à direita dentro do parêntese de chamada de função. Sim, a sintaxe é estranha.

Array e desaninhar


Use o construtor ARRAY para converter um conjunto de linhas, cada uma com uma coluna, em uma matriz. O driver de banco de dados (como JDBC) deve ser capaz de mapear arrays Postgres em arrays nativos e pode ser mais fácil de trabalhar.
-- convert rows (with 1 column each) into a 1-dimensional array
SELECT ARRAY(SELECT id FROM players WHERE lifetime_spend > 10000);

A função unnest faz o inverso – converte cada item em uma matriz em arow. Eles são mais úteis na junção cruzada com uma lista de valores:
    SELECT materials.name || ' ' || weapons.name
      FROM weapons
CROSS JOIN UNNEST('{"wood","gold","stone","iron","diamond"}'::text[])
           AS materials(name);

-- returns:
--     ?column?
-- -----------------
--  wood sword
--  wood axe
--  wood pickaxe
--  wood shovel
--  gold sword
--  gold axe
-- (..snip..)

Combine Select Statements com Union


Você pode usar a construção UNION para combinar os resultados de vários SELECTs semelhantes:
SELECT name FROM weapons
UNION
SELECT name FROM tools
UNION
SELECT name FROM materials;

Use CTEs para processar ainda mais o resultado combinado:
WITH fight_equipment AS (
    SELECT name, damage FROM weapons
    UNION
    SELECT name, damage FROM tools
)
  SELECT name, damage
    FROM fight_equipment
ORDER BY damage DESC
   LIMIT 5;

Existem também as construções INTERSECT e EXCEPT, na mesma linha de UNION. Leia mais sobre essas cláusulas nos documentos.

Correções rápidas em Select:case, coalesce e nullif


O CASE, COALESCE e NULLIF para fazer pequenas “correções” rápidas para dados SELECTed.CASE é como switch em linguagens semelhantes a C:
SELECT id,
       CASE WHEN name='typ0' THEN 'typo' ELSE name END
  FROM items;
  
SELECT CASE WHEN rating='G'  THEN 'General Audiences'
            WHEN rating='PG' THEN 'Parental Guidance'
            ELSE 'Other'
       END
  FROM movies;

COALESCE pode ser usado para substituir um determinado valor em vez de NULL.
-- use an empty string if ip is not available
SELECT nodename, COALESCE(ip, '') FROM nodes;

-- try to use the first available, else use '?'
SELECT nodename, COALESCE(ipv4, ipv6, hostname, '?') FROM nodes;

NULLIF funciona de outra maneira, permitindo que você use NULL em vez de um determinado valor:
-- use NULL instead of '0.0.0.0'
SELECT nodename, NULLIF(ipv4, '0.0.0.0') FROM nodes;

Gerar dados de teste aleatórios e sequenciais


Vários métodos de geração de dados aleatórios:
-- 100 random dice rolls
SELECT 1+(5 * random())::int FROM generate_series(1, 100);

-- 100 random text strings (each 32 chars long)
SELECT md5(random()::text) FROM generate_series(1, 100);

-- 100 random text strings (each 36 chars long)
SELECT uuid_generate_v4()::text FROM generate_series(1, 100);

-- 100 random small text strings of varying lengths
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
SELECT gen_random_bytes(1+(9*random())::int)::text
  FROM generate_series(1, 100);

-- 100 random dates in 2019
SELECT DATE(
         DATE '2019-01-01' + ((random()*365)::int || ' days')::interval
       )
  FROM generate_series(1, 100);
  
-- 100 random 2-column data: 1st column integer and 2nd column string
WITH a AS (
  SELECT ARRAY(SELECT random() FROM generate_series(1,100))
),
b AS (
  SELECT ARRAY(SELECT md5(random()::text) FROM generate_series(1,100))
)
SELECT unnest(i), unnest(j)
  FROM a a(i), b b(j);

-- a daily count for 2020, generally increasing over time
SELECT i, ( (5+random()) * (row_number() over()) )::int
  FROM generate_series(DATE '2020-01-01', DATE '2020-12-31', INTERVAL '1 day')
       AS s(i);

Use bernoulli amostragem de tabela para selecionar um número aleatório de linhas de uma tabela:
-- select 15% of rows from the table, chosen randomly  
     SELECT *
       FROM purchases
TABLESAMPLE bernoulli(15)

Use generate_series para gerar valores sequenciais de inteiros, datas e outros tipos internos incrementais:
-- generate integers from 1 to 100
SELECT generate_series(1, 100);

-- call the generated values table as "s" with a column "i", to use in
-- CTEs and JOINs
SELECT i FROM generate_series(1, 100) AS s(i);

-- generate multiples of 3 in different ways
SELECT 3*i FROM generate_series(1, 100) AS s(i);
SELECT generate_series(1, 100, 3);

-- works with dates too: here are all the Mondays in 2020:
SELECT generate_series(DATE '2020-01-06', DATE '2020-12-31', INTERVAL '1 week');

Obter contagem aproximada de linhas


O desempenho horrível de COUNT(*) talvez seja o subproduto mais feio da arquitetura do Postgres. Se você precisar apenas de uma contagem aproximada de linhas para uma tabela enorme, poderá evitar uma COUNT completa consultando o coletor de estatísticas:
SELECT relname, n_live_tup FROM pg_stat_user_tables;

O resultado é preciso após um ANALYZE e será progressivamente incorreto à medida que as linhas forem modificadas. Não use isso se quiser contagens precisas.

Tipo de intervalo


O intervalo type não só pode ser usado como um tipo de dados de coluna, mas pode ser adicionado e subtraído de data e carimbo de data e hora valores:
-- get licenses that expire within the next 7 days
SELECT id
  FROM licenses
 WHERE expiry_date BETWEEN now() - INTERVAL '7 days' AND now();
 
-- extend expiry date
UPDATE licenses
   SET expiry_date = expiry_date + INTERVAL '1 year'
 WHERE id = 42;

Desativar validação de restrição para inserção em massa

-- add a constraint, set as "not valid"
ALTER TABLE players
            ADD CONSTRAINT fk__players_guilds
                           FOREIGN KEY (guild_id)
                            REFERENCES guilds(id)
            NOT VALID;

-- insert lots of rows into the table
COPY players FROM '/data/players.csv' (FORMAT CSV);

-- now validate the entire table
ALTER TABLE players
            VALIDATE CONSTRAINT fk__players_guilds;

Descarregar uma tabela ou consulta em um arquivo CSV

-- dump the contents of a table to a CSV format file on the server
COPY players TO '/tmp/players.csv' (FORMAT CSV);

-- "header" adds a heading with column names
COPY players TO '/tmp/players.csv' (FORMAT CSV, HEADER);

-- use the psql command to save to your local machine
\copy players TO '~/players.csv' (FORMAT CSV);

-- can use a query instead of a table name
\copy ( SELECT id, name, score FROM players )
      TO '~/players.csv'
      ( FORMAT CSV );

Use mais tipos de dados nativos em seu design de esquema


O Postgres vem com muitos tipos de dados integrados. Representar os dados que seu aplicativo precisa usando um desses tipos pode economizar muito código do aplicativo, tornar seu desenvolvimento mais rápido e resultar em menos erros.

Por exemplo, se você estiver representando a localização de uma pessoa usando o tipo de dadospoint e uma região de interesse como um polygon , você pode verificar se a pessoa está na região simplesmente com:
-- the @> operator checks if the region of interest (a "polygon") contains
-- the person's location (a "point")
SELECT roi @> person_location FROM live_tracking;

Aqui estão alguns tipos de dados interessantes do Postgres e links para onde você pode encontrar mais informações sobre eles:
  • Tipos de enumeração tipo C
  • Tipos geométricos – ponto, caixa, segmento de linha, linha, caminho, polígono, círculo
  • Endereços IPv4, IPv6 e MAC
  • Tipos de intervalo - intervalos de número inteiro, data e carimbo de data/hora
  • Matrizes que podem conter valores de qualquer tipo
  • UUID – se você precisar usar UUIDs, ou apenas precisar trabalhar com inteiros aleatórios de 129 bytes, considere usar o uuid tipo e o uuid-oscp extensão para armazenamento, geração e formatação de UUIDs
  • Intervalos de data e hora usando o tipo INTERVAL
  • e, claro, os populares JSON e JSONB

Extensões agrupadas


A maioria das instalações do Postgres inclui várias “extensões” padrão. As extensões são componentes instaláveis ​​(e desinstaláveis ​​de forma limpa) que fornecem funcionalidades não incluídas no núcleo. Eles podem ser instalados por banco de dados.

Alguns deles são bastante úteis, e vale a pena gastar algum tempo para conhecê-los:
  • pg_stat_statements – estatísticas sobre a execução de cada consulta SQL
  • auto_explain – registra o plano de execução de consultas (lentas)
  • postgres_fdw,dblink andfile_fdw – maneiras de acessar outras fontes de dados (como servidores Postgres remotos, servidores MySQL, arquivos no sistema de arquivos do servidor) como tabelas regulares
  • citext – um tipo de dados “texto que não diferencia maiúsculas de minúsculas”, mais eficiente do que lower()-ing em todo o lugar
  • hstore – um tipo de dados de valor-chave
  • pgcrypto –funções de hash SHA, criptografia