PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Обединете таблица и регистър на промените в изглед в PostgreSQL

Ако приемем, че Postgres 9.1 или по-късно.
Опростих/оптимизирах основната ви заявка, за да извлечете най-новите стойности:

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;

Запитвам в каталога на PostgreSQL вместо стандартната информационна схема, защото това е по-бързо. Обърнете внимание на специалното прехвърляне към ::regclass .

Това ви дава таблица . Искате всички стойности за един unique_id подреда .
За да постигнете това, имате основно три възможности:

  1. Един подселекция (или присъединяване) на колона. Скъпо и тромаво. Но валидна опция само за няколко колони.

  2. Голям CASE изявление.

  3. Въртяща се функция . PostgreSQL предоставя crosstab() функция в допълнителния модул tablefunc за това.
    Основни инструкции:

    • PostgreSQL Crosstab Query

Основна централна таблица с crosstab()

Пренаписах напълно функцията:

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

Отделих търсенето в каталог от заявката за стойност, като crosstab() функцията с два параметъра предоставя имена на колони отделно. Липсващите стойности (без вписване в промените) се заменят с NULL автоматично. Идеално съвпадение за този случай на употреба!

Ако приемем, че attname съвпада с column_name . С изключение на unique_id , което играе специална роля.

Пълна автоматизация

Обръщайки се към вашия коментар:Има начин за автоматично предоставяне на списъка с дефиниции на колони. Не е за хора със слаби сърца обаче.

Тук използвам редица разширени функции на Postgres:crosstab() , plpgsql функция с динамичен SQL, обработка на съставен тип, разширено цитиране в долари, търсене в каталог, агрегатна функция, функция на прозореца, тип идентификатор на обект, ...

Тестова среда:

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

Автоматизирана функция за една маса

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;

Таблицата instances е твърдо кодиран, схемата е квалифицирана да бъде недвусмислена. Обърнете внимание на използването на типа таблица като тип на връщане. Има тип ред, регистриран автоматично за всяка таблица в PostgreSQL. Това е обвързано да съответства на типа на връщане на crosstab() функция.

Това свързва функцията с типа на таблицата:

  • Ще получите съобщение за грешка, ако се опитате да DROP масата
  • Вашата функция ще се провали след ALTER TABLE . Трябва да го пресъздадете (без промени). Смятам, че това е грешка в 9.1. ALTER TABLE не трябва тихо да нарушава функцията, а да извежда грешка.

Това се представя много добре.

Обадете се:

SELECT * FROM f_curr_instance(3);

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

Обърнете внимание как col1 е NULL тук.
Използвайте в заявка за показване на екземпляр с последните му стойности:

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;

Пълна автоматизация за всяка маса

(Добавено през 2016 г. Това е динамит.)
Изисква Postgres 9.1 или по-късно. (Може да се каже, че работи със стр. 8.4, но не си направих труда да направя бекпач.)

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;

Обаждане (предоставяне на типа таблица с NULL::public.instances :

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

Свързано:

  • Рефакторирайте функция PL/pgSQL, за да върнете резултатите от различни SELECT заявки
  • Как да зададете стойност на полето за съставна променлива с помощта на динамичен 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. Споделете връзка с postgres db между процеси в Python

  2. непълна информация от заявка за pg_views

  3. postgres:надстройване на потребител до суперпотребител?

  4. Бъдещето на Postgres-XL

  5. Как да изчислим сумата от множество колони в PostgreSQL