sql >> Database teknologi >  >> RDS >> PostgreSQL

Flet en tabel og en ændringslog til en visning i PostgreSQL

Forudsat Postgres 9.1 eller senere.
Jeg forenklede/optimerede din grundlæggende forespørgsel for at hente de seneste værdier:

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;

Jeg forespørger i PostgreSQL-kataloget i stedet for standardinformationsskemaet, fordi det er hurtigere. Bemærk den særlige cast til ::regclass .

Det giver dig nu en tabel . Du vil have alle værdier for én unique_id i en række .
For at opnå dette har du grundlæggende tre muligheder:

  1. Ét undervalg (eller join) pr. kolonne. Dyrt og uhåndterligt. Men en gyldig mulighed for kun nogle få kolonner.

  2. En stor CASE erklæring.

  3. En pivotfunktion . PostgreSQL leverer crosstab() funktion i det ekstra modul tablefunc for det.
    Grundlæggende instruktioner:

    • PostgreSQL krydstabulatorforespørgsel

Grundlæggende pivottabel med crosstab()

Jeg omskrev fuldstændig funktionen:

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

Jeg adskilte katalogopslaget fra værdiforespørgslen som crosstab() funktion med to parametre giver kolonnenavne separat. Manglende værdier (ingen indtastning i ændringer) erstattes med NULL automatisk. Et perfekt match til denne brugssag!

Forudsat at attname matcher column_name . Ekskluderer unique_id , som spiller en særlig rolle.

Fuld automatisering

Adressering af din kommentar:Der er en måde at levere kolonnedefinitionslisten automatisk. Det er dog ikke for sarte sjæle.

Jeg bruger en række avancerede Postgres-funktioner her:crosstab() , plpgsql-funktion med dynamisk SQL, sammensat typehåndtering, avanceret dollarnotering, katalogopslag, aggregeret funktion, vinduesfunktion, objektidentifikatortype, ...

Testmiljø:

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

Automatisk funktion til én tabel

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;

Tabellen instances er hårdkodet, skema kvalificeret til at være utvetydigt. Bemærk brugen af ​​tabeltypen som returtype. Der er automatisk registreret en rækketype for hver tabel i PostgreSQL. Dette er bundet til at matche returtypen for crosstab() funktion.

Dette binder funktionen til tabellens type:

  • Du får en fejlmeddelelse, hvis du prøver at DROP bordet
  • Din funktion vil mislykkes efter en ALTER TABLE . Du skal genskabe den (uden ændringer). Jeg betragter dette som en fejl i 9.1. ALTER TABLE bør ikke lydløst bryde funktionen, men give en fejl.

Dette fungerer meget godt.

Ring til:

SELECT * FROM f_curr_instance(3);

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

Bemærk hvordan col1 er NULL her.
Brug i en forespørgsel til at vise en forekomst med dens seneste værdier:

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;

Fuld automatisering for ethvert bord

(Tilføjet 2016. Dette er dynamit.)
Kræver Postgres 9.1 eller senere. (Kunne fås til at fungere med s. 8.4, men jeg gad ikke at backpatch.)

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;

Kald (forsyner tabeltypen med NULL::public.instances :

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

Relateret:

  • Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler
  • Sådan indstilles værdien af ​​det sammensatte variabelfelt ved hjælp af dynamisk SQL



  1. Sikre dine Mongo Clusters med SSL

  2. Sådan installeres SQL Server på en Mac

  3. SQLskills Wait Types Library viser nu SentryOne-data

  4. verify_queryable_inventory returneret ORA-20008:Timeout