sql >> Database >  >> RDS >> PostgreSQL

Voeg een tabel en een wijzigingslogboek samen in een weergave in PostgreSQL

Ervan uitgaande dat Postgres 9.1 of later.
Ik heb uw basisquery vereenvoudigd/geoptimaliseerd om de nieuwste waarden op te halen:

SELECT DISTINCT ON (1,2)
       c.unique_id, a.attname AS col, c.value
FROM   pg_attribute a
LEFT   JOIN changes c ON c.column_name = a.attname
                     AND c.table_name  = 'instances'
                 --  AND c.unique_id   = 3  -- uncomment to fetch single row
WHERE  a.attrelid = 'instances'::regclass   -- schema-qualify to be clear?
AND    a.attnum > 0                         -- no system columns
AND    NOT a.attisdropped                   -- no deleted columns
ORDER  BY 1, 2, c.updated_at DESC;

Ik bevraag de PostgreSQL-catalogus in plaats van het standaardinformatieschema omdat dat sneller is. Let op de speciale cast voor ::regclass .

Dat geeft je nu een tabel . U wilt alle waarden voor één unique_id op een rij .
Om dat te bereiken heb je in principe drie opties:

  1. Eén subselect (of join) per kolom. Duur en onhandig. Maar een geldige optie voor slechts een paar kolommen.

  2. Een grote CASE verklaring.

  3. Een draaifunctie . PostgreSQL biedt de crosstab() functie in de aanvullende module tablefunc daarvoor.
    Basisinstructies:

    • PostgreSQL-kruistabelquery

Basis draaitabel met crosstab()

Ik heb de functie volledig herschreven:

SELECT *
FROM   crosstab(
    $x$
    SELECT DISTINCT ON (1, 2)
           unique_id, column_name, value
    FROM   changes
    WHERE  table_name = 'instances'
 -- AND    unique_id = 3  -- un-comment to fetch single row
    ORDER  BY 1, 2, updated_at DESC;
    $x$,

    $y$
    SELECT attname
    FROM   pg_catalog.pg_attribute
    WHERE  attrelid = 'instances'::regclass  -- possibly schema-qualify table name
    AND    attnum > 0
    AND    NOT attisdropped
    AND    attname <> 'unique_id'
    ORDER  BY attnum
    $y$
    )
AS tbl (
 unique_id integer
-- !!! You have to list all columns in order here !!! --
);

Ik heb het opzoeken van de catalogus gescheiden van de waardequery, zoals de crosstab() functie met twee parameters biedt kolomnamen afzonderlijk. Ontbrekende waarden (geen invoer in wijzigingen) worden vervangen door NULL automatisch. Een perfecte match voor deze use case!

Ervan uitgaande dat attname komt overeen met column_name . Exclusief unique_id , die een speciale rol speelt.

Volledige automatisering

Uw opmerking aanpakken:Er is een manier om de kolomdefinitielijst automatisch aan te leveren. Het is echter niet voor bangeriken.

Ik gebruik hier een aantal geavanceerde Postgres-functies:crosstab() , plpgsql-functie met dynamische SQL, verwerking van samengestelde typen, geavanceerde dollarquotering, opzoeken in catalogus, aggregatiefunctie, vensterfunctie, type objectidentificatie, ...

Testomgeving:

CREATE TABLE instances (
  unique_id int
, col1      text
, col2      text -- two columns are enough for the demo
);

INSERT INTO instances VALUES
  (1, 'foo1', 'bar1')
, (2, 'foo2', 'bar2')
, (3, 'foo3', 'bar3')
, (4, 'foo4', 'bar4');

CREATE TABLE changes (
  unique_id   int
, table_name  text
, column_name text
, value       text
, updated_at  timestamp
);

INSERT INTO changes VALUES
  (1, 'instances', 'col1', 'foo11', '2012-04-12 00:01')
, (1, 'instances', 'col1', 'foo12', '2012-04-12 00:02')
, (1, 'instances', 'col1', 'foo1x', '2012-04-12 00:03')
, (1, 'instances', 'col2', 'bar11', '2012-04-12 00:11')
, (1, 'instances', 'col2', 'bar17', '2012-04-12 00:12')
, (1, 'instances', 'col2', 'bar1x', '2012-04-12 00:13')

, (2, 'instances', 'col1', 'foo2x', '2012-04-12 00:01')
, (2, 'instances', 'col2', 'bar2x', '2012-04-12 00:13')

 -- NO change for col1 of row 3 - to test NULLs
, (3, 'instances', 'col2', 'bar3x', '2012-04-12 00:13');

 -- NO changes at all for row 4 - to test NULLs

Geautomatiseerde functie voor één tafel

CREATE OR REPLACE FUNCTION f_curr_instance(int, OUT t public.instances) AS
$func$
BEGIN
   EXECUTE $f$
   SELECT *
   FROM   crosstab($x$
      SELECT DISTINCT ON (1,2)
             unique_id, column_name, value
      FROM   changes
      WHERE  table_name = 'instances'
      AND    unique_id =  $f$ || $1 || $f$
      ORDER  BY 1, 2, updated_at DESC;
      $x$
    , $y$
      SELECT attname
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = 'public.instances'::regclass
      AND    attnum > 0
      AND    NOT attisdropped
      AND    attname <> 'unique_id'
      ORDER  BY attnum
      $y$) AS tbl ($f$
   || (SELECT string_agg(attname || ' ' || atttypid::regtype::text
                       , ', ' ORDER BY attnum) -- must be in order
       FROM   pg_catalog.pg_attribute
       WHERE  attrelid = 'public.instances'::regclass
       AND    attnum > 0
       AND    NOT attisdropped)
   || ')'
   INTO t;
END
$func$  LANGUAGE plpgsql;

De tabel instances is hard gecodeerd, schema gekwalificeerd om ondubbelzinnig te zijn. Let op het gebruik van het tabeltype als retourtype. Er is automatisch een rijtype geregistreerd voor elke tabel in PostgreSQL. Dit komt ongetwijfeld overeen met het retourtype van de crosstab() functie.

Dit bindt de functie aan het type tabel:

  • Je krijgt een foutmelding als je probeert te DROP de tafel
  • Uw functie zal mislukken na een ALTER TABLE . Je moet het opnieuw maken (zonder wijzigingen). Ik beschouw dit als een bug in 9.1. ALTER TABLE mag de functie niet stilzwijgend verbreken, maar een foutmelding geven.

Dit presteert erg goed.

Bel:

SELECT * FROM f_curr_instance(3);

unique_id | col1  | col2
----------+-------+-----
 3        |<NULL> | bar3x

Merk op hoe col1 is NULL hier.
Gebruik in een query om een ​​instantie met de nieuwste waarden weer te geven:

SELECT i.unique_id
     , COALESCE(c.col1, i.col1)
     , COALESCE(c.col2, i.col2)
FROM   instances i
LEFT   JOIN f_curr_instance(3) c USING (unique_id)
WHERE  i.unique_id = 3;

Volledige automatisering voor elke tafel

(Toegevoegd 2016. Dit is dynamiet.)
Vereist Postgres 9.1 of later. (Kan worden gemaakt om te werken met pg 8.4, maar ik nam niet de moeite om te backpatchen.)

CREATE OR REPLACE FUNCTION f_curr_instance(_id int, INOUT _t ANYELEMENT) AS
$func$
DECLARE
   _type text := pg_typeof(_t);
BEGIN
   EXECUTE
   (
   SELECT format
         ($f$
         SELECT *
         FROM   crosstab(
            $x$
            SELECT DISTINCT ON (1,2)
                   unique_id, column_name, value
            FROM   changes
            WHERE  table_name = %1$L
            AND    unique_id  = %2$s
            ORDER  BY 1, 2, updated_at DESC;
            $x$    
          , $y$
            SELECT attname
            FROM   pg_catalog.pg_attribute
            WHERE  attrelid = %1$L::regclass
            AND    attnum > 0
            AND    NOT attisdropped
            AND    attname <> 'unique_id'
            ORDER  BY attnum
            $y$) AS ct (%3$s)
         $f$
          , _type, _id
          , string_agg(attname || ' ' || atttypid::regtype::text
                     , ', ' ORDER BY attnum)  -- must be in order
         )
   FROM   pg_catalog.pg_attribute
   WHERE  attrelid = _type::regclass
   AND    attnum > 0
   AND    NOT attisdropped
   )
   INTO _t;
END
$func$  LANGUAGE plpgsql;

Aanroepen (geef het tabeltype op met NULL::public.instances :

SELECT * FROM f_curr_instance(3, NULL::public.instances);

Gerelateerd:

  • Refactor een PL/pgSQL-functie om de uitvoer van verschillende SELECT-query's te retourneren
  • Hoe de waarde van het samengestelde variabele veld in te stellen met dynamische SQL



  1. Installeer SQL Server 2019 op een Mac

  2. Genereer testgegevens met Oracle PL/SQL-ontwikkelaar

  3. cursor.execute(INSERT INTO im_entry.test (+entrym+) WAARDEN ('+p+');)

  4. Hoe COS() werkt in MariaDB