Як створити поворотну CROSS JOIN, де отримане визначення таблиці невідоме?


18

З огляду на дві таблиці з невизначеним числом рядків з назвою та значенням, як я б відображав перемикання CROSS JOINфункції над їх значеннями.

CREATE TEMP TABLE foo AS
SELECT x::text AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT x::text AS name, x::int
FROM generate_series(1,5) AS t(x);

Наприклад, якби ця функція була множенням, як би я створив таблицю (множення) на зразок тієї, що нижче,

Загальна таблиця множення 1..12

Усі ці (arg1,arg2,result)рядки можна створити за допомогою

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar; 

Тож це лише питання презентації, я хотів би, щоб це також працювало зі спеціальним іменем - ім'ям, яке не є просто аргументом CASTредагування тексту, а встановленим у таблиці,

CREATE TEMP TABLE foo AS
SELECT chr(x+64) AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT chr(x+72) AS name, x::int
FROM generate_series(1,5) AS t(x);

Я думаю, що це можна було б зробити легко за допомогою CROSSTAB, здатного до динамічного повернення.

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  ', 'SELECT DISTINCT name FROM bar'
) AS **MAGIC**

Але, без того **MAGIC**, я отримую

ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM crosstab(

Для довідки, використовуючи наведені вище приклади з іменами, це щось більше, як те tablefunc, що crosstab()хочеться.

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  '
) AS t(row int, i int, j int, k int, l int, m int);

Але зараз ми повертаємось до припущень щодо змісту та розміру barтаблиці в нашому прикладі. Тож якщо

  1. Таблиці невизначеної довжини,
  2. Тоді перехресне з'єднання являє собою куб невизначеного розміру (через вище),
  3. Назви каталогів (перехресне мовлення) наведені в таблиці

Що найкраще ми можемо зробити у PostgreSQL без "списку визначення стовпців" для створення такого виду презентації?


1
Чи будуть результати JSON хорошим підходом? Невже АРІЙ буде хорошим підходом? Таким чином, визначення «вихідної таблиці» вже було б відоме (і виправлене). Ви вкладаєте гнучкість в JSON або ARRAY. Я думаю, це буде залежати від багатьох інструментів, які будуть використані згодом для обробки інформації.
joanolo

Я вважаю за краще, щоб це було так само, як вище, якщо це можливо.
Еван Керролл

Відповіді:


12

Простий випадок, статичний SQL

Недінаміческое рішення з crosstab()для простого випадку:

SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, "A" int, "B" int, "C" int, "D" int, "E" int
                 , "F" int, "G" int, "H" int, "I" int, "J" int);

Я впорядковую отримані стовпці по foo.name, ні foo.x. Обидва випадково сортуються паралельно, але це просто проста установка. Виберіть правильний порядок сортування для вашої справи. Фактичне значення другого стовпця не має значення для цього запиту (форма 1 параметра crosstab()).

Нам навіть не потрібні crosstab()два параметри, оскільки відсутні відсутні значення за визначенням. Побачити:

(Ви виправили перехресний запит в питанні, замінивши fooз barв пізнішій редакції. Це також фіксує запит, але продовжує працювати з іменами з foo.)

Невідомий тип повернення, динамічний SQL

Імена та типи стовпців не можуть бути динамічними. SQL вимагає знати кількість, імена та типи отриманих стовпців під час виклику. Або шляхом явного декларування, або з інформації в системних каталогах (ось що відбувається з SELECT * FROM tbl: Postgres шукає визначення зареєстрованої таблиці.)

Ви хочете, щоб Postgres отримував отримані стовпці з даних у користувацькій таблиці. Не відбудеться.

Так чи інакше, вам потрібно дві туди-назад на сервер. Або створіть курсор, а потім пройдіть по ньому. Або ви створюєте таблицю темп, а потім вибираєте з неї. Або ви зареєструєте тип і використовуєте його під час дзвінка.

Або ви просто генеруєте запит в один крок і виконайте його в наступному:

SELECT $$SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, $$
 || string_agg(quote_ident(name), ' int, ' ORDER BY name) || ' int)'
FROM   foo;

Це генерує запит вище, динамічно. Виконайте його на наступному кроці.

Я використовую котирування доларів ( $$), щоб просто обробляти вкладені котирування. Побачити:

quote_ident() важливо уникати протизаконних імен стовпців (і, можливо, захищатись від введення SQL).

Пов'язані:


Я помітив, що виконання запиту, який ви назвали "Невідомий тип повернення, динамічний SQL", насправді просто повертає рядок, який представляє інший запит, і тоді ви говорите "виконати його на наступному кроці". Чи означає це, що важко буде, наприклад, створити від цього матеріалізований погляд?
Колін Д

@ColinD: Не важко, але просто неможливо. Ви можете створити MV з генерованого SQL з відомим типом повернення. Але не можна мати МВ із невідомим типом повернення.
Ервін Брандстеттер

11

Що найкраще ми можемо зробити у PostgreSQL без "списку визначення стовпців" для створення такого виду презентації?

Якщо ви вирішите це як презентаційну проблему, ви можете розглянути функцію презентації після запиту.

Новіші версії psql(9.6) поставляються з \crosstabviewрезультатом перехресного представлення без підтримки SQL (оскільки SQL не може це зробити безпосередньо, як згадується у відповіді @ Ервіна: SQL вимагає знати кількість, імена та типи результуючих стовпців у час виклику )

Наприклад, ваш перший запит дає:

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar
\crosstabview

 arg1 | 1  | 2  | 3  | 4  | 5  
------+----+----+----+----+----
 1    |  1 |  2 |  3 |  4 |  5
 2    |  2 |  4 |  6 |  8 | 10
 3    |  3 |  6 |  9 | 12 | 15
 4    |  4 |  8 | 12 | 16 | 20
 5    |  5 | 10 | 15 | 20 | 25
 6    |  6 | 12 | 18 | 24 | 30
 7    |  7 | 14 | 21 | 28 | 35
 8    |  8 | 16 | 24 | 32 | 40
 9    |  9 | 18 | 27 | 36 | 45
 10   | 10 | 20 | 30 | 40 | 50
(10 rows)

Другий приклад із назвами стовпців ASCII дає:

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  \crosstabview

 arg1 | I  | J  | K  | L  | M  
------+----+----+----+----+----
 A    |  1 |  2 |  3 |  4 |  5
 B    |  2 |  4 |  6 |  8 | 10
 C    |  3 |  6 |  9 | 12 | 15
 D    |  4 |  8 | 12 | 16 | 20
 E    |  5 | 10 | 15 | 20 | 25
 F    |  6 | 12 | 18 | 24 | 30
 G    |  7 | 14 | 21 | 28 | 35
 H    |  8 | 16 | 24 | 32 | 40
 I    |  9 | 18 | 27 | 36 | 45
 J    | 10 | 20 | 30 | 40 | 50
(10 rows)

Дивіться посібник з psql та https://wiki.postgresql.org/wiki/Crosstabview для отримання додаткової інформації.


1
Це справді чортово класно.
Еван Керролл

1
Найелегантніше рішення.
Ервін Брандстеттер

1

Це не остаточне рішення

Це мій найкращий підхід до цих пір. Ще потрібно перетворити підсумковий масив у стовпці.

Спочатку я отримав декартовий продукт обох таблиць:

select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
       ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
 from bar
     cross join foo
 order by bar.name, foo.name

Але я додав номер рядка лише для того, щоб визначити кожен рядок першої таблиці.

((row_number() over ()) - 1) / (select count(*)::integer from foo)

Тоді я отримав результат у такому форматі:

[Row name] [Array of values]


select col_name, values
from
(
select '' as col_name, array_agg(name) as values from foo
UNION
select fy.name as col_name,
    (select array_agg(t.val) as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;

+---+---------------------+
|   |      ABCDEFGHIJ     |
+---+---------------------+
| I |     12345678910     |
+---+---------------------+
| J |   2468101214161820  |
+---+---------------------+
| K |  36912151821242730  |
+---+---------------------+
| L |  481216202428323640 |
+---+---------------------+
| M | 5101520253035404550 |
+---+---------------------+ 

Перетворення його в рядок, розділений комами:

select col_name, values
from
(
select '' as col_name, array_to_string(array_agg(name),',') as values from foo
UNION
select fy.name as col_name,
    (select array_to_string(array_agg(t.val),',') as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;


+---+------------------------------+
|   | A,B,C,D,E,F,G,H,I,J          |
+---+------------------------------+
| I | 1,2,3,4,5,6,7,8,9,10         |
+---+------------------------------+
| J | 2,4,6,8,10,12,14,16,18,20    |
+---+------------------------------+
| K | 3,6,9,12,15,18,21,24,27,30   |
+---+------------------------------+
| L | 4,8,12,16,20,24,28,32,36,40  |
+---+------------------------------+
| M | 5,10,15,20,25,30,35,40,45,50 |
+---+------------------------------+

(Просто спробувати пізніше: http://rextester.com/NBCYXA2183 )


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