PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Scal tabelę i dziennik zmian w widoku w PostgreSQL

Zakładając Postgres 9.1 lub później.
Uprościłem / zoptymalizowałem Twoje podstawowe zapytanie, aby pobrać najnowsze wartości:

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;

Odpytuję katalog PostgreSQL zamiast standardowego schematu informacji, ponieważ jest to szybsze. Zwróć uwagę na specjalny rzut na ::regclass .

To daje ci stół . Chcesz mieć wszystkie wartości dla jednego unique_id w rzędziu .
Aby to osiągnąć masz zasadniczo trzy możliwości:

  1. Jeden podselekcja (lub sprzężenie) na kolumnę. Drogie i nieporęczne. Ale poprawna opcja tylko dla kilku kolumn.

  2. Duży CASE oświadczenie.

  3. Funkcja obrotowa . PostgreSQL zapewnia crosstab() funkcja w dodatkowym module tablefunc w tym celu.
    Podstawowe instrukcje:

    • Kwerendy PostgreSQL Crosstab

Podstawowa tabela przestawna z crosstab()

Całkowicie przepisałem funkcję:

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

Oddzieliłem wyszukiwanie katalogu od zapytania wartości, ponieważ crosstab() funkcja z dwoma parametrami udostępnia nazwy kolumn oddzielnie. Brakujące wartości (brak wpisu w zmianach) są zastępowane przez NULL automatycznie. Doskonale pasuje do tego przypadku użycia!

Zakładając, że attname pasuje do column_name . Z wyłączeniem unique_id , który odgrywa szczególną rolę.

Pełna automatyzacja

Adresowanie komentarza:Jest sposób aby automatycznie dostarczyć listę definicji kolumn. Jednak to nie jest dla osób o słabym sercu.

Używam tutaj wielu zaawansowanych funkcji Postgresa:crosstab() , funkcja plpgsql z dynamicznym SQL, obsługa typów złożonych, zaawansowane kwotowanie w dolarach, wyszukiwanie katalogu, funkcja agregująca, funkcja okna, typ identyfikatora obiektu, ...

Środowisko testowe:

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

Automatyczna funkcja dla jednej tabeli

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;

Tabela instances jest zakodowany na stałe, schemat kwalifikowany jako jednoznaczny. Zwróć uwagę na użycie typu tabeli jako typu zwracanego. Dla każdej tabeli w PostgreSQL jest rejestrowany automatycznie typ wiersza. Jest to powiązane z typem zwracanym przez crosstab() funkcja.

To wiąże funkcję z typem tabeli:

  • Dostaniesz komunikat o błędzie, jeśli spróbujesz DROP stół
  • Twoja funkcja nie powiedzie się po ALTER TABLE . Musisz go odtworzyć (bez zmian). Uważam to za błąd w wersji 9.1. ALTER TABLE nie powinien potajemnie przerywać funkcji, ale zgłaszać błąd.

Działa to bardzo dobrze.

Zadzwoń:

SELECT * FROM f_curr_instance(3);

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

Zwróć uwagę, jak col1 jest NULL tutaj.
Użyj w zapytaniu, aby wyświetlić instancję z jej najnowszymi wartościami:

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;

Pełna automatyzacja dla dowolnej tabeli

(Dodano 2016. To jest dynamit.)
Wymaga Postgresa 9.1 lub później. (Może działać z pg 8.4, ale nie zawracałem sobie głowy łataniem wstecznym.)

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;

Call (podając typ tabeli z NULL::public.instances :

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

Powiązane:

  • Refaktoryzuj funkcję PL/pgSQL, aby zwrócić dane wyjściowe różnych zapytań SELECT
  • Jak ustawić wartość pola zmiennej złożonej za pomocą dynamicznego SQL



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak utworzyć losowy ciąg, który jest odpowiedni dla identyfikatora sesji w PostgreSQL?

  2. Slick 2.0 Ogólne operacje CRUD

  3. Uaktualnić kolumnę PostgreSQL JSON do JSONB?

  4. Audyt PostgreSQL za pomocą pgAudit

  5. Dlaczego pg_restore zwraca pomyślnie, ale w rzeczywistości nie przywraca mojej bazy danych?