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

Динамична алтернатива на завъртане с CASE и GROUP BY

Ако не сте инсталирали допълнителния модул tablefunc , изпълнете тази команда веднъж за база данни:

CREATE EXTENSION tablefunc;

Отговор на въпрос

Много основно решение за кръстосани таблици за вашия случай:

SELECT * FROM crosstab(
  'SELECT bar, 1 AS cat, feh
   FROM   tbl_org
   ORDER  BY bar, feh')
 AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Специалната трудност ето, че няма категория (cat ) в основната таблица. За основния формуляр с 1 параметър можем просто да предоставим фиктивна колона с фиктивна стойност, служеща като категория. Стойността така или иначе се игнорира.

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

Ако имахме действителна категория колона с имена, определящи реда на стойностите в резултата, ще ни е необходим формуляр с 2 параметъра на crosstab() . Тук синтезирам колона за категория с помощта на функцията на прозореца row_number() , към базата crosstab() на:

SELECT * FROM crosstab(
   $$
   SELECT bar, val, feh
   FROM  (
      SELECT *, 'val' || row_number() OVER (PARTITION BY bar ORDER BY feh) AS val
      FROM tbl_org
      ) x
   ORDER BY 1, 2
   $$
 , $$VALUES ('val1'), ('val2'), ('val3')$$         -- more columns?
) AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Останалото е доста безпроблемно. Намерете още обяснения и връзки в тези тясно свързани отговори.

Основни положения:
Прочетете това първо, ако не сте запознати с crosstab() функция!

  • PostgreSQL Crosstab Query

Разширени:

  • Завъртане на множество колони с помощта на Tablefunc
  • Обединете таблица и регистър на промените в изглед в PostgreSQL

Правилна настройка на теста

Ето как трябва да предоставите тестов случай за начало:

CREATE TEMP TABLE tbl_org (id int, feh int, bar text);
INSERT INTO tbl_org (id, feh, bar) VALUES
   (1, 10, 'A')
 , (2, 20, 'A')
 , (3,  3, 'B')
 , (4,  4, 'B')
 , (5,  5, 'C')
 , (6,  6, 'D')
 , (7,  7, 'D')
 , (8,  8, 'D');

Динамична кръстосана таблица?

Не много динамичен , все пак, както коментира @Clodoaldo. Типовете на динамично връщане са трудни за постигане с plpgsql. Но ги има начини за заобикаляне - с някои ограничения .

За да не усложнявам допълнително останалото, демонстрирам спо-просто тестов случай:

CREATE TEMP TABLE tbl (row_name text, attrib text, val int);
INSERT INTO tbl (row_name, attrib, val) VALUES
   ('A', 'val1', 10)
 , ('A', 'val2', 20)
 , ('B', 'val1', 3)
 , ('B', 'val2', 4)
 , ('C', 'val1', 5)
 , ('D', 'val3', 8)
 , ('D', 'val1', 6)
 , ('D', 'val2', 7);

Обадете се:

SELECT * FROM crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2')
AS ct (row_name text, val1 int, val2 int, val3 int);

Връща:

 row_name | val1 | val2 | val3
----------+------+------+------
 A        | 10   | 20   |
 B        |  3   |  4   |
 C        |  5   |      |
 D        |  6   |  7   |  8

Вградена функция на tablefunc модул

Модулът tablefunc предоставя проста инфраструктура за общ crosstab() повиквания без предоставяне на списък с дефиниции на колони. Редица функции, написани на C (обикновено много бързо):

crosstabN()

crosstab1() - crosstab4() са предварително дефинирани. Една малка точка:те изискват и връщат целия text . Така че трябва да изведем нашето integer стойности. Но това опростява обаждането:

SELECT * FROM crosstab4('SELECT row_name, attrib, val::text  -- cast!
                         FROM tbl ORDER BY 1,2')

Резултат:

 row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
 A        | 10         | 20         |            |
 B        | 3          | 4          |            |
 C        | 5          |            |            |
 D        | 6          | 7          | 8          |

Персонализиран crosstab() функция

За още колони или други типове данни , създаваме свой собствен композитен тип и функция (веднъж).
Въведете:

CREATE TYPE tablefunc_crosstab_int_5 AS (
  row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);

Функция:

CREATE OR REPLACE FUNCTION crosstab_int_5(text)
  RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;

Обадете се:

SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val   -- no cast!
                              FROM tbl ORDER BY 1,2');

Резултат:

 row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
 A        |   10 |   20 |      |      |
 B        |    3 |    4 |      |      |
 C        |    5 |      |      |      |
 D        |    6 |    7 |    8 |      |

Едно полиморфна, динамична функция за всички

Това надхвърля това, което се покрива от tablefunc модул.
За да направя връщания тип динамичен, използвам полиморфен тип с техника, описана подробно в този свързан отговор:

  • Рефакторирайте функция PL/pgSQL, за да върнете резултатите от различни SELECT заявки

Формуляр с 1 параметър:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L) t(%s)'
                , _qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

Претоварване с този вариант за формуляра с 2 параметъра:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _cat_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L, %L) t(%s)'
                , _qry, _cat_qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

pg_typeof(_rowtype)::text::regclass :Има дефиниран тип ред за всеки дефиниран от потребителя съставен тип, така че атрибутите (колони) са изброени в системния каталог pg_attribute . Бързата лента за получаване:прехвърляне на регистрирания тип (regtype ) към text и пуснете този text към regclass .

Създаване на съставни типове веднъж:

Трябва да дефинирате веднъж всеки тип връщане, който ще използвате:

CREATE TYPE tablefunc_crosstab_int_3 AS (
    row_name text, val1 int, val2 int, val3 int);

CREATE TYPE tablefunc_crosstab_int_4 AS (
    row_name text, val1 int, val2 int, val3 int, val4 int);

...

За ad hoc разговори можете също да създадете временна таблица със същия (временен) ефект:

CREATE TEMP TABLE temp_xtype7 AS (
    row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);

Или използвайте типа на съществуваща таблица, изглед или материализиран изглед, ако е наличен.

Обаждане

Използване на горните типове редове:

Формуляр с 1 параметър (без липсващи стойности):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
 , NULL::tablefunc_crosstab_int_3);

Формуляр с 2 параметъра (някои стойности може да липсват):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
 , $$VALUES ('val1'), ('val2'), ('val3')$$
 , NULL::tablefunc_crosstab_int_3);

Тази една функция работи за всички типове връщане, докато crosstabN() рамка, предоставена от tablefunc модулът се нуждае от отделна функция за всеки.
Ако сте именували типовете си в последователност, както е показано по-горе, трябва само да замените удебеления номер. За да намерите максималния брой категории в основната таблица:

SELECT max(count(*)) OVER () FROM tbl  -- returns 3
GROUP  BY row_name
LIMIT  1;

Това е приблизително толкова динамично, колкото става, ако искате отделни колони . Масиви като демонстрирани от @Clocoaldo или просто текстово представяне или резултатът, увит в тип документ като json или hstore може да работи за произволен брой категории динамично.

Отказ от отговорност:
Винаги е потенциално опасно, когато въведеното от потребителя се преобразува в код. Уверете се, че това не може да се използва за SQL инжектиране. Не приемайте информация от ненадеждни потребители (директно).

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

SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
                       , NULL::tablefunc_crosstab_int_3);


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. липсва запис на клауза FROM за таблица Grupo cakephp

  2. ГРЕШКА в PostgreSQL:функция to_tsvector(променлив знак, неизвестен) не съществува

  3. Как да създам потребител за db в postgresql?

  4. SELECT DISTINCT е по-бавен от очакваното в моята таблица в PostgreSQL

  5. Рефакториране на външния ключ към полета