Перехресний запит PostgreSQL


196

Хто-небудь знає, як створити крос-запити в 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

Чи можливо це?


1
У мене була дещо інша структура, і цей приклад мені було трохи важко зрозуміти, тому я задокументував свій спосіб мислення цього stackoverflow.com/q/49051959/808723 . Можливо, це корисно для будь-кого.
GameScripting

Відповіді:


317

Встановіть додатковий модуль 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);

Повернення:

Розділ | Активний | Неактивний
--------- + -------- + ----------
 А | 1 | 2
 Б | 4 | 5
 C |      7 | - !!
  • Не потрібно кастингу та перейменування.
  • Зверніть увагу на неправильний результат для C: значення 7заповнюється для першого стовпця. Іноді така поведінка є бажаною, але не для цього випадку використання.
  • Проста форма також обмежена рівно трьома стовпцями у наданому вхідному запиті: ім'я рядка , категорія , значення . Немає місця для додаткових стовпців, як, наприклад, в 2-х параметрах нижче.

Безпечна форма

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 ('Active'::text), ('Inactive')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Повернення:

Розділ | Активний | Неактивний
--------- + -------- + ----------
 А | 1 | 2
 Б | 4 | 5
 C | |        7   - !!
  • Зверніть увагу на правильний результат для C.

  • Другий параметр може бути будь-яким запитом , який повертає один ряд на відповідність атрибута порядку визначення стовпця в кінці. Часто ви хочете запитувати різні атрибути з нижньої таблиці, як це:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    Це в посібнику.

    Оскільки у будь-якому випадку вам потрібно прописати всі стовпці зі списку визначення стовпців (за винятком попередньо визначених варіантів), як правило, більш ефективно надати короткий список у виразі, як показано:crosstabN()VALUES

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

    Або (не в посібнику):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • Я використовував котирування в доларах, щоб полегшити котирування.

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

db <> скрипка тут

Розширені приклади


\crosstabview в psql

Postgres 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щоб забезпечити правильність упорядкування вхідних рядків


3
+1, хороша розписка, дякую, що помітилиIn practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
ChristopheD

У мене є проблеми з використанням $$ VALUES .. $$. Я замість цього використовував 'VALUES (' '<attr>' ':: <type>), ..'
Marco Fantasia

Чи можемо ми вказати прив'язку параметрів у крос-запиті? Я отримую цю помилку => не вдалося визначити тип даних параметра $ 2
Ashish

1
Чи можна встановити значення за замовчуванням для стовпця в запиті перехресних таблиць?
Ашиш

2
@Ashish: Почніть нове запитання. Коментарі - це не місце. Ви завжди можете зв’язатись із цим для контексту.
Ервін Брандстеттер

30

Ви можете використовувати 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 ... FROM crosstab ('виберіть розділ :: текст, стан, кількість: текст з t, де status =' 'active' '', 2) AS. .. (зауважте подвійні цитати). У разі, якщо параметр передається користувачем під час виконання (наприклад, як функціональний параметр), ви можете сказати: SELECT ... FROM crosstab ('виберіть розділ :: текст, стан, кількість: текст від t, де status =' ' '|| par_active ||' '' ', 2) AS ... (потрійні лапки тут!). У BIRT це також працює з? заповнювач.
Вім Верхаверт

26
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

1
Чи може хтось пояснити, що до цієї відповіді додає функція перехресних таблиць у модулі tablefunc, що і виконує завдання під рукою, і на мій погляд, це легше зрозуміти?
Джон Пауелл

4
@ JohnBarça: Простий такий випадок легко вирішити за допомогою операторів CASE. Однак це стає непростим дуже швидко, якщо більше атрибутів та / або інших типів даних, ніж просто цілі числа. Як осторонь: ця форма використовує сукупну функцію sum(), було б краще використовувати min()або max()ні, ELSEяка працюєtext . Але це має дещо інші ефекти, ніж те corosstab(), що використовується лише значення "перше" на атрибут. Не має значення, доки може бути лише один. Нарешті, продуктивність теж актуальна. crosstab()написано на С та оптимізовано для виконання завдання.
Ервін Брандштеттер

Це не працює для мене, для postgresql. Я отримую помилкуERROR: 42803: aggregate function calls may not be nested
Одрі

1
@Audrey у вас тоді не працює той самий SQL?

2
Подумайте про додавання пояснення проти просто блоку коду
Даніель Л. ВанДенБош

10

Рішення з агрегуванням 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

Дякую, це допомогло мені з пов’язаною проблемою.
JeffCharter

1

Вибачте, це не є повним, тому що я не можу перевірити його тут, але це може привести вас у правильному напрямку. Я перекладаю з чогось, що я використовую, що робить подібний запит:

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;

що поверне типID, найвищу ціну та найнижчу запропоновану ціну та різницю між двома (позитивна різниця означатиме, що щось можна купити за менший, ніж можна продати).


1
У вас відсутнє словосполучення, інакше це правильно. Плани пояснення в моїй системі дуже відрізняються - функція перехресних таблиць коштує 22,5, тоді як підхід LEFT JOIN приблизно в 4 рази дорожчий, а вартість становить 91,38. Він також виробляє приблизно вдвічі більше фізичних читання та виконує хеш-з'єднання - що може бути досить дорогим порівняно з іншими типами приєднання.
Єремія Пешка

Дякую, Єремія, це добре знати. Я підтримав іншу відповідь, але ваш коментар варто зберегти, тому я не видаляю цього.
LanceH

-1

Crosstabфункція доступна в tablefuncрозширенні. Вам потрібно буде створити це розширення один раз для бази даних.

СТВОРИТИ РОЗШИРЕННЯ tablefunc;

Ви можете використовувати наведений нижче код для створення зведеної таблиці за допомогою поперечної вкладки:

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)

1
Ця відповідь нічого не додає до попередніх відповідей.
Ервін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.