Вопрос

Кто-нибудь знает, как создавать перекрестные запросы в PostgreSQL?
Например, у меня есть следующая таблица:

Section    Status    Count
A          Active    1
A          Inactive  2
B          Active    4
B          Inactive  5

Я хотел бы, чтобы запрос возвращал следующую перекрестную таблицу:

Section    Active    Inactive
A          1         2
B          4         5

Это возможно?

Это было полезно?

Решение

Установите дополнительный модуль tablefunc один раз на каждую базу данных, которая предоставляет функцию crosstab().Начиная с Postgres 9.1 вы можете использовать CREATE EXTENSION для этого:

CREATE EXTENSION IF NOT EXISTS tablefunc;

Улучшенный тестовый пример

CREATE TABLE tbl (
   section   text
 , status    text
 , ct        integer  -- "count" is a reserved word in standard SQL
);

INSERT INTO tbl VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                    , ('C', 'Inactive', 7);  -- ('C', 'Active') is missing

Простая форма – не подходит для отсутствующих атрибутов

crosstab(text) с 1 входной параметр:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- needs to be "ORDER BY 1,2" here
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Возврат:

 Section | Active | Inactive
---------+--------+----------
 A       |      1 |        2
 B       |      4 |        5
 C       |      7 |           -- !!
  • Нет необходимости в кастинге и переименовании.
  • Обратите внимание неправильный результат для C:Значение 7 заполняется для первого столбца.Иногда такое поведение желательно, но не для этого варианта использования.
  • Простая форма также ограничивается точно три столбца в предоставленном входном запросе: имя_строки, категория, ценить.Нет места для дополнительные столбцы как в альтернативе с двумя параметрами ниже.

Безопасная форма

crosstab(text, text) с 2 входные параметры:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- could also just be "ORDER BY 1" here

  , $$VALUES ('Активный'::текст), ('Неактивный')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Возврат:

 Section | Active | Inactive
---------+--------+----------
 A       |      1 |        2
 B       |      4 |        5
 C       |        |        7  -- !!
  • Обратите внимание на правильный результат для C.

  • А второй параметр может быть любой запрос, который возвращает один ряд для каждого атрибута, соответствующего порядку определения столбца в конце.Часто вам потребуется запросить отдельные атрибуты из базовой таблицы следующим образом:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
    

    Это в руководстве.

    Поскольку вам в любом случае придется указывать все столбцы в списке определений столбцов (за исключением заранее определенных crosstabН() вариантов), обычно более эффективно предоставить краткий список в виде VALUES выражение, подобное продемонстрированному:

    $$VALUES ('Active'::text), ('Inactive')$$)
    

    Или (нет в инструкции):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
    
  • я использовал котировка доллара чтобы облегчить цитирование.

  • Вы даже можете выводить столбцы с помощью другой типы данных с crosstab(text, text) - до тех пор, пока текстовое представление столбца значений является допустимым вводом для целевого типа.Таким образом, у вас могут быть атрибуты разного типа и вывода. text, date, numeric и т. д.для соответствующих атрибутов.В конце есть пример кода. глава crosstab(text, text) в руководстве.

БД<>скрипка здесь

Расширенные примеры


\crosstabview в psql

Постгрес 9.6 добавил эту метакоманду в интерактивный терминал по умолчанию psql.Вы можете запустить запрос, который будете использовать в качестве первого. crosstab() параметр и передать его в \crosstabview (сразу или на следующем этапе).Нравиться:

db=> SELECT section, status, ct FROM tbl \crosstabview

Результат аналогичен приведенному выше, но это функция представления на стороне клиента исключительно.Входные строки обрабатываются немного по-другому, следовательно ORDER BY не требуется.Подробности для \crosstabview в руководстве. Внизу этой страницы приведены дополнительные примеры кода.

Соответствующий ответ на dba.SE от Даниэля Верите (автора функции psql):



А ранее принятый ответ устарело.

  • Вариант функции crosstab(text, integer) устарело.Второй integer параметр игнорируется.Я цитирую текущий руководство:

    crosstab(text sql, int N) ...

    Устаревшая версия crosstab(text).Параметр N теперь игнорируется, поскольку количество столбцов значений всегда определяется призывом к запросу

  • Ненужное кастинг и переименование.

  • Это не удастся, если строка не имеет всех атрибутов.См. безопасный вариант с двумя входными параметрами выше, чтобы правильно обработать отсутствующие атрибуты.

  • ORDER BY требуется в однопараметрической форме crosstab(). Руководство:

    На практике SQL-запрос всегда должен указывать ORDER BY 1,2 Чтобы убедиться, что входные строки правильно упорядочены

Другие советы

Вы можете использовать crosstab() Функция Дополнительный модуль Tablefunc - который вы должны установить однажды за базу данных. Поскольку PostgreSQL 9.1 вы можете использовать CREATE EXTENSION для этого:

CREATE EXTENSION tablefunc;

В вашем случае я считаю, что это будет выглядеть что-то подобное:

CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);

INSERT INTO t VALUES ('A', 'Active',   1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active',   4);
INSERT INTO t VALUES ('B', 'Inactive', 5);

SELECT row_name AS Section,
       category_1::integer AS Active,
       category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
            AS ct (row_name text, category_1 text, category_2 text);
SELECT section,
       SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
       SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status  value as a separate column explicitly

FROM t
GROUP BY section

Решение с агрегацией JSON:

CREATE TEMP TABLE t (
  section   text
, status    text
, ct        integer  -- don't use "count" as column name.
);

INSERT INTO t VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7); 


SELECT section,
       (obj ->> 'Active')::int AS active,
       (obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
      FROM t
      GROUP BY section
     )X

Извините, это не завершено, потому что я не могу проверить это здесь, но он может выключить вас в правильном направлении. Я переводю от чего-то, что я использую, это делает подобный запрос:

select mt.section, mt1.count as Active, mt2.count as Inactive
from mytable mt
left join (select section, count from mytable where status='Active')mt1
on mt.section = mt1.section
left join (select section, count from mytable where status='Inactive')mt2
on mt.section = mt2.section
group by mt.section,
         mt1.count,
         mt2.count
order by mt.section asc;

Код я работаю из:

select m.typeID, m1.highBid, m2.lowAsk, m1.highBid - m2.lowAsk as diff, 100*(m1.highBid - m2.lowAsk)/m2.lowAsk as diffPercent
from mktTrades m
   left join (select typeID,MAX(price) as highBid from mktTrades where bid=1 group by typeID)m1
   on m.typeID = m1.typeID
   left join (select typeID,MIN(price) as lowAsk  from mktTrades where bid=0 group by typeID)m2
   on m1.typeID = m2.typeID
group by m.typeID, 
         m1.highBid, 
         m2.lowAsk
order by diffPercent desc;

Что вернется к типу, самая высокая цена заявки и самая низкая цена, и разница между двумя (положительная разница будет означать, что что-то может быть куплено за меньшее, чем она может быть продана).

Crosstab Функция доступна под tablefunc расширение. Вам придется создать это расширение один раз для базы данных.

Создать расширение tablefunc;

Вы можете использовать код ниже для создания таблицы Pivot, используя вкладку CORD:

create table test_Crosstab( section text,
<br/>status text,
<br/>count numeric)

<br/>insert into test_Crosstab values ( 'A','Active',1)
                <br/>,( 'A','Inactive',2)
                <br/>,( 'B','Active',4)
                <br/>,( 'B','Inactive',5)

select * from crosstab(
<br/>'select section
    <br/>,status
    <br/>,count
    <br/>from test_crosstab'
    <br/>)as ctab ("Section" text,"Active" numeric,"Inactive" numeric)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top