결과 테이블 정의를 알 수없는 피벗 된 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(

참고로, 이름 위의 예제를 사용하여이 더 많은 것 같은 것입니다 tablefunccrosstab()욕구.

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 결과가 좋은 접근 방식입니까? ARRAY가 좋은 aprpoach일까요? 이런 식으로 "출력 테이블"의 정의는 이미 알려져 있고 고정되어 있습니다. 유연성은 JSON 또는 ARRAY 내에 있습니다. 나중에 정보를 처리하는 데 사용되는 많은 도구에 달려 있다고 생각합니다.
joanolo

가능하다면 위와 같은 것을 선호합니다.
Evan Carroll

답변:


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()정의에 따라 결 측값이 없으므로 2 개의 매개 변수 가 필요하지 않습니다 . 보다:

(당신은 대체하여 문제의 크로스 탭 쿼리를 고정 foo하여 bar나중에 편집에. 이것은 또한 쿼리를 수정하지만의 이름으로 작업을 계속 foo.)

알 수없는 리턴 유형, 동적 SQL

열 이름과 유형은 동적 일 수 없습니다. SQL은 호출시 결과 열의 수, 이름 및 유형을 알아야합니다. 명시 적 선언 또는 시스템 카탈로그의 정보를 사용하여 수행됩니다 ( SELECT * FROM tblPostgres는 등록 된 테이블 정의를 조회합니다).

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"이라는 쿼리를 실행하면 실제로 다른 쿼리를 나타내는 문자열 만 반환 한 다음 "다음 단계에서 실행"이라는 메시지가 나타납니다. 이것은 예를 들어 구체화 된 견해를 만드는 것이 어렵다는 것을 의미합니까?
Colin D

@ColinD : 어렵지는 않지만 보통 불가능합니다. 알려진 리턴 유형으로 생성 된 SQL에서 MV를 작성할 수 있습니다. 그러나 반환 유형을 알 수없는 MV는 가질 수 없습니다.
Erwin Brandstetter

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
이건 정말 멋지다.
Evan Carroll

1
가장 우아한 해결 방법.
Erwin Brandstetter

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 )


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.