sql >> Base de Datos >  >> RDS >> PostgreSQL

Consejos y trucos de Postgres

¿Trabaja con Postgres a diario? ¿Escribir código de aplicación que hable con Postgres? ¡Luego, consulte los fragmentos de código SQL del tamaño de un bocado a continuación que pueden ayudarlo a trabajar más rápido!

Insertar varias filas en una declaración

La declaración INSERT puede insertar más de una fila en una sola declaración:

INSERT INTO planets (name, gravity)
     VALUES ('earth',    9.8),
            ('mars',     3.7),
            ('jupiter', 23.1);

Lea más sobre lo que INSERT puede hacer aquí.

Insertar una fila y devolver valores asignados automáticamente

Los valores generados automáticamente con construcciones DEFAULT/serial/IDENTITY pueden ser devueltos por la declaración INSERT utilizando la cláusula RETURNING. Desde la perspectiva del código de la aplicación, tal INSERCIÓN se ejecuta como una SELECCIÓN que devuelve un 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

Claves primarias UUID generadas automáticamente

Los UUID a veces se usan en lugar de claves principales por varias razones. Así es como puede usar un UUID en lugar de una serie o IDENTIDAD:

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

Insertar si no existe, actualizar de lo contrario

En Postgres 9.5 y versiones posteriores, puede upsert directamente usando el constructo 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 filas de una tabla a otra

La sentencia INSERT tiene un formato donde los valores pueden ser proporcionados por una sentencia SELECT. Use esto para copiar filas de una tabla a otra:

-- 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;

Si está buscando tablas de carga masiva, consulte también el comando COPY, que se puede usar para insertar filas desde un archivo de texto o CSV.

Eliminar y devolver información eliminada

Puedes usar el RETURNING cláusula para devolver valores de las filas que se eliminaron utilizando una instrucción de eliminación masiva:

-- return the list of customers whose licenses were deleted after expiry
DELETE FROM licenses
      WHERE now() > expiry_date
  RETURNING customer_name;

Mover filas de una tabla a otra

Puede mover filas de una tabla a otra en una sola declaración, usando CTE con 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;

Actualizar filas y devolver valores actualizados

La cláusula RETURNING también se puede utilizar en las ACTUALIZACIONES. Tenga en cuenta que solo los nuevos valores de las columnas actualizadas se pueden devolver de esta manera.

-- grant random amounts of coins to eligible players
   UPDATE players
      SET coins = coins + (100 * random())::integer
    WHERE eligible
RETURNING id, coins;

Si necesita el valor original de las columnas actualizadas:es posible a través de una autocombinación, pero no hay garantía de atomicidad. Intente usar SELECT .. FOR UPDATE en su lugar.

Actualizar algunas filas aleatorias y devolver las actualizadas

Así es como puede elegir algunas filas aleatorias de una tabla, actualizarlas y devolver las actualizadas, todo de una sola 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;

Crear una tabla como otra tabla

Use la construcción CREATE TABLE .. LIKE para crear una tabla con las mismas columnas que otra:

CREATE TABLE to_be_audited (LIKE purchases);

De forma predeterminada, esto no crea índices, restricciones, valores predeterminados, etc. similares. Todo eso, pregunte a Postgres explícitamente:

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Vea la sintaxis completa aquí.

Extraer un conjunto aleatorio de filas en otra tabla

Desde Postgres 9.5, la función TABLESAMPLE está disponible para extraer una muestra de filas de una tabla. Actualmente existen dos métodos de muestreo y bernoulli suele ser el que quieres:

-- 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;

El sistema El método de muestreo de tablas es más rápido, pero no devuelve una distribución uniforme. Consulte los documentos para obtener más información.

Crear una tabla a partir de una consulta seleccionada

Puede usar la construcción CREATE TABLE .. AS para crear la tabla y completarla a partir de una consulta SELECT, todo de una sola vez:

CREATE TABLE to_be_audited AS
      SELECT *
        FROM purchases
 TABLESAMPLE bernoulli(10)
       WHERE transaction_date = CURRENT_DATE;

La tabla resultante es como una vista materializada sin una consulta asociada. Lea más sobre CREATE TABLE .. AS aquí.

Crear tablas no registradas

No registrado las tablas no están respaldadas por registros WAL. Esto significa que las actualizaciones y eliminaciones de dichas tablas son más rápidas, pero no toleran bloqueos y no se pueden replicar.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Crear tablas temporales

Temporal las tablas son tablas no registradas implícitamente, con una vida útil más corta. Se autodestruyen automáticamente al final de una sesión (predeterminado) o al final de la transacción.

Los datos de las tablas temporales no se pueden compartir entre sesiones. Múltiples sesiones pueden crear tablas temporales con el mismo nombre.

-- 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;

Agregar comentarios

Los comentarios se pueden agregar a cualquier objeto en la base de datos. Muchas herramientas, incluyendo pg_dump, entienden esto. ¡Un comentario útil podría evitar un montón de problemas durante la limpieza!

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';

Bloqueos de aviso

Los bloqueos de advertencia se pueden usar para coordinar acciones entre dos aplicaciones conectadas a la misma base de datos. Puede usar esta función para implementar un mutex distribuido global para una determinada operación, por ejemplo. Lea todo sobre esto aquí en thedocs.

-- 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 en matrices, matrices JSON o cadenas

Postgres proporciona funciones agregadas que concatenan valores en un GRUPO matrices de campo de juguete, matrices JSON o cadenas:

-- 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 Con Orden

Mientras estamos en el tema, aquí se explica cómo establecer el orden de los valores que se pasan a la función agregada, 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;

Sí, hay una cláusula ORDER BY final dentro del paréntesis de llamada de función. Sí, la sintaxis es rara.

Array y Unnest

Utilice el constructor ARRAY para convertir un conjunto de filas, cada una con una columna, en una matriz. El controlador de la base de datos (como JDBC) debería poder mapear arreglos de Postgres en arreglos nativos y podría ser más fácil trabajar con ellos.

-- convert rows (with 1 column each) into a 1-dimensional array
SELECT ARRAY(SELECT id FROM players WHERE lifetime_spend > 10000);

La función unnest hace lo contrario:convierte cada elemento de una matriz en una fila. Son más útiles en la unión cruzada con una 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..)

Combinar declaraciones selectas con unión

Puede usar la construcción UNION para combinar los resultados de múltiples SELECCIONES similares:

SELECT name FROM weapons
UNION
SELECT name FROM tools
UNION
SELECT name FROM materials;

Use CTE para procesar aún más el 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;

También hay construcciones INTERSECT y EXCEPT, en la misma línea que UNION. Lea más sobre estas cláusulas en thedocs.

Soluciones rápidas en Select:case, coalesce and nullif

CASE, COALESCE y NULLIF para hacer pequeños "arreglos" rápidos para datos SELECCIONADOS. CASE es como cambiar en lenguajes similares 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 se puede utilizar para sustituir un determinado valor en lugar 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 al revés, permitiéndole usar NULL en lugar de un cierto valor:

-- use NULL instead of '0.0.0.0'
SELECT nodename, NULLIF(ipv4, '0.0.0.0') FROM nodes;

Generar datos de prueba aleatorios y secuenciales

Varios métodos para generar datos aleatorios:

-- 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);

Usa bernoulli Muestreo de tabla para seleccionar un número aleatorio de filas de una tabla:

-- select 15% of rows from the table, chosen randomly  
     SELECT *
       FROM purchases
TABLESAMPLE bernoulli(15)

Usa generate_series para generar valores secuenciales de números enteros, fechas y otros tipos incorporados incrementables:

-- 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');

Obtener recuento aproximado de filas

El horrible desempeño de COUNT(*) es quizás el subproducto más feo de la arquitectura de Postgres. Si solo necesita un recuento de filas aproximado para una tabla enorme, puede evitar un COUNT completo consultando el recopilador de estadísticas:

SELECT relname, n_live_tup FROM pg_stat_user_tables;

El resultado es exacto tras un ANÁLISIS, y será erróneo progresivamente a medida que se modifiquen las filas. No utilice esto si desea contar con precisión.

Tipo de intervalo

El intervalo El tipo no solo se puede usar como un tipo de datos de columna, sino que también se puede agregar y restar de fecha y marca de tiempo 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;

Desactivar validación de restricciones para inserción masiva

-- 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;

Volcar una tabla o consulta a un archivo 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 );

Utilice más tipos de datos nativos en su diseño de esquema

Postgres viene con muchos tipos de datos integrados. Representar los datos que su aplicación necesita usando uno de estos tipos puede ahorrar mucho código de aplicación, hacer que su desarrollo sea más rápido y generar menos errores.

Por ejemplo, si está representando la ubicación de una persona utilizando el tipo de datos point y una región de interés como un polygon , puede verificar si la persona está en la región simplemente con:

-- the @> operator checks if the region of interest (a "polygon") contains
-- the person's location (a "point")
SELECT roi @> person_location FROM live_tracking;

Aquí hay algunos tipos de datos de Postgres interesantes y enlaces a donde puede encontrar más información sobre ellos:

  • Tipos de enumeración tipo C
  • Tipos geométricos:punto, cuadro, segmento de línea, línea, ruta, polígono, círculo
  • Direcciones IPv4, IPv6 y MAC
  • Tipos de rango:rangos de números enteros, fechas y marcas de tiempo
  • Arreglos que pueden contener valores de cualquier tipo
  • UUID:si necesita usar UUID, o simplemente necesita trabajar con números enteros aleatorios de 129 bytes, considere usar uuid tipo y el uuid-oscp extensión para almacenar, generar y formatear UUID
  • Intervalos de fecha y hora usando el tipo INTERVALO
  • y, por supuesto, los siempre populares JSON y JSONB

Extensiones incluidas

La mayoría de las instalaciones de Postgres incluyen un montón de "extensiones" estándar. Las extensiones son componentes instalables (y desinstalables limpiamente) que brindan funcionalidades no incluidas en el núcleo. Se pueden instalar por base de datos.

Algunos de estos son bastante útiles y vale la pena dedicar un tiempo a conocerlos:

  • pg_stat_statements:estadísticas sobre la ejecución de cada consulta SQL
  • auto_explain:registra el plan de ejecución de consultas (lentas)
  • postgres_fdw,dblink yfile_fdw:formas de acceder a otras fuentes de datos (como servidores Postgres remotos, servidores MySQL, archivos en el sistema de archivos del servidor) como tablas normales
  • citext:un tipo de datos de "texto que no distingue entre mayúsculas y minúsculas", más eficiente que bajar () en todo el lugar
  • hstore:un tipo de datos clave-valor
  • pgcrypto:funciones hash SHA, cifrado