8.15. 배열

8.15.1. 배열 타입 정의
8.15.2. 배열값 저장
8.15.3. 배열 접근
8.15.4. 배열 수정
8.15.5. 배열 검색
8.15.6. Array Input and Output Syntax

PostgreSQL은 테이블의 칼럼을 다차원의 가변적인 배열로 정의 할 수 있습니다. 배열은 build-in, 사용자 정의, enum, composite 타입, 범위 자료형 또는 도메인으로 만들수 있다.

8.15.1. 배열 타입 정의

배열 타입을 설명하기 위해 테이블을 만들었다.

CREATE TABLE sal_emp (
    name            text,
    pay_by_quarter  integer[],
    schedule        text[][]
);

위 코드처럼, 배열 타입은 데이타 타입 옆에 대괄호([])를 붙여서 만들 수 있다. 위 명령어는 text타입의 (name) 칼럼, 직원이 분기에 받는 급여를 의미하는 1차원 integer 배열의 (payrd_by_quartere) 칼럼, 직원의 일주일 일정을 의미하는 2차원 text 배열의 (schedule) 칼럼을 가진 sal_emp테이블을 만든다.

CREATE TABLE 할때 배열의 크기를 지정 할수도 있다. 예를 들면,

CREATE TABLE tictactoe (
    squares   integer[3][3]
);

그러나, 현재는 배열의 크기 제한에 대해서는 무시하도록 구현되어있다. 즉, 크기 제한을 두지 않은것과 동일하게 동작한다.

현재 구현에서는 배열 차원 갯수에 제한을 두지 않는다. 배열에 특정 타입을 지정하면 배열 차원의 갯수나 크기에 상관없이 해당 배열은 동일한 타입을 가진다. 그러므로 CREATE TABLE에서 배열의 크기나 차원을 지정하는건 단지 형식적인 것이다; 실행시간에 아무런 영향을 주지 않는다.

또 다른 방법은, 1차원 배열 선언에 대해서, SQL 표준인 ARRAY 키워드를 사용하는것이다. pay_by_quarter 는 다음과 같이 선언할수 있다:

    pay_by_quarter  integer ARRAY[4],

또는 크기 지정없이도 가능하다.

    pay_by_quarter  integer ARRAY,

앞에서도 말했다 시피, PostgreSQL은 어떠한 경우에도 배열 크기에 제한하지 않는다.

8.15.2. 배열값 저장

배열에 상수를 저장하기 위해서는, 값들의 양끝을 중괄호로 감싸고, 쉼표를 구분자로 사용하여 구분하면 된다. (당신이 C를 안다면, C에서 구조체 초기화 하는 문법과 크게 다르지 않다는것을 알수있다.) 값 양옆에 쌍 따옴표를 넣을수 도 있으며, 쉼표와 중괄호는 받드시 있어야한다. (좀 더 자세한 설명은 아래에서 하겠다.) 배열상수의 일반적인 형식은 다음과 같다:

'{ val1 delim val2 delim ... }'

delim pg_type에도 나왔듯이 구분 문자이다. PostgreSQL 배포판의 표준 자료형은 세미콜론(;)을 사용하는 box형을 제외하고는 모두 쉼표(,)를 사용한다. 각각의 val은 배열 상수의 원소이기거나, 부분 배열이다. 배열 상수 예제는 다음과 같다.

'{{1,2,3},{4,5,6},{7,8,9}}'

정수형 타입의 3개의 부분 배열, 3-by-3배열의 이차원 배열이다.

배열 상수의 값을 NULL로 저장하고 싶은 경우엔, 값에 NULL을 적어주면 된다. (대문자와 소문자 구분은 하지 않는다.) NULL이라는 문자열을 저장하고 싶다면, 양옆에 쌍따옴표로 감싸면 된다.

(배열 상수는 4.1.2.7절에서 설명하는 제네릭타입의 상수와 같은 특수한 경우이다. 상수는 초기화할 때 문자열로 취급하고, 배열 입력에 대한 변환을 하지 않는다. 그러므로 명시적으로 타입 정의가 필요하다.)

이젠 INSERT문에 대해서 알아보자:

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"training", "presentation"}}');

INSERT INTO sal_emp
    VALUES ('Carol',
    '{20000, 25000, 25000, 25000}',
    '{{"breakfast", "consulting"}, {"meeting", "lunch"}}');

위의 두 insert문의 결과는 다음과 같다:

SELECT * FROM sal_emp;
 name  |      pay_by_quarter       |                 schedule
-------+---------------------------+-------------------------------------------
 Bill  | {10000,10000,10000,10000} | {{meeting,lunch},{training,presentation}}
 Carol | {20000,25000,25000,25000} | {{breakfast,consulting},{meeting,lunch}}
(2 rows)

다차원 배열은 꼭 각 차원끼리 쌍을 이뤄야한다. 쌍을 이루지 않으면 에러를 발생한다. 예를 들면:

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"meeting"}}');
ERROR:  multidimensional arrays must have array expressions with matching dimensions

ARRAY 생성을 아래와 같이 정의할수 도 있다.:

INSERT INTO sal_emp
    VALUES ('Bill',
    ARRAY[10000, 10000, 10000, 10000],
    ARRAY[['meeting', 'lunch'], ['training', 'presentation']]);

INSERT INTO sal_emp
    VALUES ('Carol',
    ARRAY[20000, 25000, 25000, 25000],
    ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']]);

배열의 값들이 기존의 SQL 상수와 표현식을 사용하고 있는것에 주목하자; 예를들면 문자열의 경우 배열을 나타내는 쌍따옴표 대신 따옴표를 사용한다. ARRAY 생성에 대해 좀 더 자세히 알고싶다면 4.2.12절를 참고하면 된다.

8.15.3. 배열 접근

테이블에 쿼리를 날려볼것이다. 우선, 배열의 여러 값 중 하나에 접근하는 방법에 대해 소개하려고 한다. 아래는 1분기와 2분기의 급여가 차이나는 사람의 이름을 가져오는 쿼리이다:

SELECT name FROM sal_emp WHERE pay_by_quarter[1] <> pay_by_quarter[2];

 name
-------
 Carol
(1 row)

배열의 인덱스 번호는 대괄호 사이에 적으면 된다. PostgreSQL 에서는 기본적으로 1부터 시작한다. 즉, n개의 원소가 있는 배열은 array[1] 로 시작하고 array[n]로 끝난다.

아래는 모든 직원의 3분기 급여를 가져오는 쿼리이다.

SELECT pay_by_quarter[3] FROM sal_emp;

 pay_by_quarter
----------------
          10000
          25000
(2 rows)

임의의 크기로 배열이나 부분배열을 잘라내서 접근 할수있다. 1차원 이상의 배열을 잘라내는 방법은 lower-bound: upper-bound을 적으면 된다. 예를들어 아래는 Bill의 첫째주, 두번째주의 첫날 스케줄을 가져오는 쿼리이다:.

SELECT schedule[1:2][1:1] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{meeting},{training}}
(1 row)

어느 차원에서든지, slice를 사용한다면, 즉 배열 인덱스에 콜론이 있다면 모든 차원이 slice를 사용하는것으로 간주한다. 인덱스 번호만 있는(콜론 없는) 차원은 1부터 배열의 갯수까지 slice를 사용하는 것으로 판단한다. 예를들어 [2][1:2]로 판단한다, 아래 예제를 보자:

SELECT schedule[1:2][2] FROM sal_emp WHERE name = 'Bill';

                 schedule
-------------------------------------------
 {{meeting,lunch},{training,presentation}}
(1 row)

slice가 아닌경우와의 혼동을 피하기위한 가장 좋은 방법은 모든 차원에 대해 slice 문법을 사용하는 것이다. e.g., [2][1:1]가 아닌 [1:2][1:1]로 한다.

It is possible to omit the lower-bound and/or upper-bound of a slice specifier; the missing bound is replaced by the lower or upper limit of the array's subscripts. For example:

SELECT schedule[:2][2:] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{lunch},{presentation}}
(1 row)

SELECT schedule[:][1:1] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{meeting},{training}}
(1 row)

배열의 subscript 표현식은 배열 자체가 null이거나 일부 subscript 표현식이 null인경우 null을 리턴한다. 또한 배열 범위를 넘어선 subscript의 경우도 null을 리턴한다(에러로 판단하지 않는다). 예를들어 schedule[1:3][1:2]일때 schedule[3][3]에 접근하면 null을 리턴한다. 다시 말해 잘못된 배열의 접근을 하게되면 에러가 아닌 null을 리턴한다.

배열의 slice 표현식도 배열 자체가 null이거나 subscript 표현식이 null 이라면 null을 리턴한다. 하지만 배열의 범위를 벗어나는 slice를 접근 했을경우 null 대신 빈(0-차원)배열을 리턴한다.(slice가 아닌 배열의 동작과는 다르고 and is done for historical reasons.) If the requested slice partially overlaps the array bounds, then it is silently reduced to just the overlapping region instead of returning null.

배열의 차원을 알고 싶을때는 array_dims 함수를 이용해서 알수 있다:

SELECT array_dims(schedule) FROM sal_emp WHERE name = 'Carol';

 array_dims
------------
 [1:2][1:2]
(1 row)

array_dimstext로 값을 리턴한다 사람이 보기에는 편하지만 프로그램 하기에는 불편하다. array_upperarray_lower, 함수는 배열 차원의 상한, 하한을 리턴한다.

SELECT array_upper(schedule, 1) FROM sal_emp WHERE name = 'Carol';

 array_upper
-------------
           2
(1 row)

array_length 함수는 배열 차원의 길이를 리턴한다.

SELECT array_length(schedule, 1) FROM sal_emp WHERE name = 'Carol';

 array_length
--------------
            2
(1 row)

cardinality returns the total number of elements in an array across all dimensions. It is effectively the number of rows a call to unnest would yield:

SELECT cardinality(schedule) FROM sal_emp WHERE name = 'Carol';

 cardinality
-------------
           4
(1 row)

8.15.4. 배열 수정

배열의 값은 다음과 같이 수정할수 있다.

UPDATE sal_emp SET pay_by_quarter = '{25000,25000,27000,27000}'
    WHERE name = 'Carol';

또는 ARRAY 표현식을 사용할 수도 있다.

UPDATE sal_emp SET pay_by_quarter = ARRAY[25000,25000,27000,27000]
    WHERE name = 'Carol';

또한 배열의 한개의 값만 수정할 수도 있고,

UPDATE sal_emp SET pay_by_quarter[4] = 15000
    WHERE name = 'Bill';

slice를 수정할 수도 있다.

UPDATE sal_emp SET pay_by_quarter[1:2] = '{27000,27000}'
    WHERE name = 'Carol';

The slice syntaxes with omitted lower-bound and/or upper-bound can be used too, but only when updating an array value that is not NULL or zero-dimensional (otherwise, there is no existing subscript limit to substitute).

저장된 배열의 값을 당장은 아니지만 더 큰 위치에 할당 할수 있다. 현재 배열의 크기보다 더 큰 위치에 할당을 했다면 현재의 위치와 할당 하려는 위치 사이의 값은 null로 채워진다. 예를들어 배열 myarray가 4개의 배열 값을 가지고 있다고 하고 6번째 위치myarray[6]에 값을 저장하면 myarray[5]는 null이 저장될 것이다. 현재 배열의 크기가 증가하는것은 1차원 배열에서만 구현이 되어있고 다차원에서는 구현 되어 있지 않는다.

Subscripted는 기존 1부터 시작하는 subscripts가 아닌 배열을 할당할수 있다. 예를들어 myarray[-2:7]은 subscript 값 -2부터 7까지의 배열을 생성하는것을 의미한다.

연결연산자 ||를 이용해서 새로운 배열을 생성할수 있다.

SELECT ARRAY[1,2] || ARRAY[3,4];
 ?column?
-----------
 {1,2,3,4}
(1 row)

SELECT ARRAY[5,6] || ARRAY[[1,2],[3,4]];
      ?column?
---------------------
 {{5,6},{1,2},{3,4}}
(1 row)

연결연산자는 1차원 배열의 처음이나 끝에 하나의 원소를 저장할수 있다. 또한 N차원 두개의 배열, 혹은 N차원 배열과 N+1차원 배열도 연결 할수 있다.

하나의 원소를 1차원 배열 처음 또는 끝에 저장하게 되면, subscript의 하한은 동일한 배열을 결과로 리턴한다. 예를들면

SELECT array_dims(1 || '[0:1]={2,3}'::int[]);
 array_dims
------------
 [0:2]
(1 row)

SELECT array_dims(ARRAY[1,2] || 3);
 array_dims
------------
 [1:3]
(1 row)

When an N-dimensional array is pushed onto the beginning or end of an N+1-dimensional array, the result is analogous to the element-array case above. Each N-dimensional sub-array is essentially an element of the N+1-dimensional array's outer dimension. For example:

SELECT array_dims(ARRAY[1,2] || ARRAY[3,4,5]);
 array_dims
------------
 [1:5]
(1 row)

SELECT array_dims(ARRAY[[1,2],[3,4]] || ARRAY[[5,6],[7,8],[9,0]]);
 array_dims
------------
 [1:5][1:2]
(1 row)

When an N-dimensional array is pushed onto the beginning or end of an N+1-dimensional array, the result is analogous to the element-array case above. Each N-dimensional sub-array is essentially an element of the N+1-dimensional array's outer dimension. For example:

SELECT array_dims(ARRAY[1,2] || ARRAY[[3,4],[5,6]]);
 array_dims
------------
 [1:3][1:2]
(1 row)

또한 배열은 array_prepend, array_append, 또는 array_cat함수를 이용해서 생성할수 있다. 처음 두개의 함수는 1차원 배열에 대해서만 동작을 하지만 array_cat함수는 다차원 배열에서도 동작한다. 아래는 이 함수들을 이용한 예제들이다:

SELECT array_prepend(1, ARRAY[2,3]);
 array_prepend
---------------
 {1,2,3}
(1 row)

SELECT array_append(ARRAY[1,2], 3);
 array_append
--------------
 {1,2,3}
(1 row)

SELECT array_cat(ARRAY[1,2], ARRAY[3,4]);
 array_cat
-----------
 {1,2,3,4}
(1 row)

SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);
      array_cat
---------------------
 {{1,2},{3,4},{5,6}}
(1 row)

SELECT array_cat(ARRAY[5,6], ARRAY[[1,2],[3,4]]);
      array_cat
---------------------
 {{5,6},{1,2},{3,4}}

In simple cases, the concatenation operator discussed above is preferred over direct use of these functions. However, because the concatenation operator is overloaded to serve all three cases, there are situations where use of one of the functions is helpful to avoid ambiguity. For example consider:

SELECT ARRAY[1, 2] || '{3, 4}';  -- the untyped literal is taken as an array
 ?column?
-----------
 {1,2,3,4}

SELECT ARRAY[1, 2] || '7';                 -- so is this one
ERROR:  malformed array literal: "7"

SELECT ARRAY[1, 2] || NULL;                -- so is an undecorated NULL
 ?column?
----------
 {1,2}
(1 row)

SELECT array_append(ARRAY[1, 2], NULL);    -- this might have been meant
 array_append
--------------
 {1,2,NULL}

In the examples above, the parser sees an integer array on one side of the concatenation operator, and a constant of undetermined type on the other. The heuristic it uses to resolve the constant's type is to assume it's of the same type as the operator's other input — in this case, integer array. So the concatenation operator is presumed to represent array_cat, not array_append. When that's the wrong choice, it could be fixed by casting the constant to the array's element type; but explicit use of array_append might be a preferable solution.

8.15.5. 배열 검색

배열의 값을 검색하기 위해서는 모든 값을 검사하는 방법밖에 없다. 여러분이 배열의 크기를 알고 있다면 직접 배열 인덱스 번호를 넣어서 하면 된다. 예를들어 :

SELECT * FROM sal_emp WHERE pay_by_quarter[1] = 10000 OR
                            pay_by_quarter[2] = 10000 OR
                            pay_by_quarter[3] = 10000 OR
                            pay_by_quarter[4] = 10000;

하지만 배열의 크기가 대단히 크다면 위 작업은 금방 짜증이 나고, 만약 배열의 크기를 모른다면 위 작업으로는 검색을 할수 없다. 다른 방법으로는 9.24절함수가 있다. 위 쿼리는 다음과 같이 변경 할수 있다.

SELECT * FROM sal_emp WHERE 10000 = ANY (pay_by_quarter);

추가적으로 모든 배열이 10000 값인 row를 찾을수도 있다.

SELECT * FROM sal_emp WHERE 10000 = ALL (pay_by_quarter);

다른 방법으로, generate_subscripts 함수를 이용할수도 있다. 예를 들어:

SELECT * FROM
   (SELECT pay_by_quarter,
           generate_subscripts(pay_by_quarter, 1) AS s
      FROM sal_emp) AS foo
 WHERE pay_by_quarter[s] = 10000;

이 함수는 표 9.62에 설명 되어 있다.

You can also search an array using the && operator, which checks whether the left operand overlaps with the right operand. For instance:

SELECT * FROM sal_emp WHERE pay_by_quarter && ARRAY[10000];

This and other array operators are further described in 9.19절. It can be accelerated by an appropriate index, as described in 11.2절.

You can also search for specific values in an array using the array_position and array_positions functions. The former returns the subscript of the first occurrence of a value in an array; the latter returns an array with the subscripts of all occurrences of the value in the array. For example:

SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon');
 array_position
----------------
              2
(1 row)

SELECT array_positions(ARRAY[1, 4, 3, 1, 3, 4, 2, 1], 1);
 array_positions
-----------------
 {1,4,8}
(1 row)

작은 정보

배열은 set자료구조가 아니다. 특정 값을 검색하기 위해 배열을 사용하는건 잘못 설계한것이다. Consider using a separate table with a row for each item that would be an array element. This will be easier to search, and is likely to scale better for a large number of elements.

8.15.6. Array Input and Output Syntax

The external text representation of an array value consists of items that are interpreted according to the I/O conversion rules for the array's element type, plus decoration that indicates the array structure. The decoration consists of curly braces ({ and }) around the array value plus delimiter characters between adjacent items. The delimiter character is usually a comma (,) but can be something else: it is determined by the typdelim setting for the array's element type. Among the standard data types provided in the PostgreSQL distribution, all use a comma, except for type box, which uses a semicolon (;). In a multidimensional array, each dimension (row, plane, cube, etc.) gets its own level of curly braces, and delimiters must be written between adjacent curly-braced entities of the same level.

The array output routine will put double quotes around element values if they are empty strings, contain curly braces, delimiter characters, double quotes, backslashes, or white space, or match the word NULL. Double quotes and backslashes embedded in element values will be backslash-escaped. For numeric data types it is safe to assume that double quotes will never appear, but for textual data types one should be prepared to cope with either the presence or absence of quotes.

By default, the lower bound index value of an array's dimensions is set to one. To represent arrays with other lower bounds, the array subscript ranges can be specified explicitly before writing the array contents. This decoration consists of square brackets ([]) around each array dimension's lower and upper bounds, with a colon (:) delimiter character in between. The array dimension decoration is followed by an equal sign (=). For example:

SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2
 FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;

 e1 | e2
----+----
  1 |  6
(1 row)

The array output routine will include explicit dimensions in its result only when there are one or more lower bounds different from one.

If the value written for an element is NULL (in any case variant), the element is taken to be NULL. The presence of any quotes or backslashes disables this and allows the literal string value NULL to be entered. Also, for backward compatibility with pre-8.2 versions of PostgreSQL, the array_nulls configuration parameter can be turned off to suppress recognition of NULL as a NULL.

As shown previously, when writing an array value you can use double quotes around any individual array element. You must do so if the element value would otherwise confuse the array-value parser. For example, elements containing curly braces, commas (or the data type's delimiter character), double quotes, backslashes, or leading or trailing whitespace must be double-quoted. Empty strings and strings matching the word NULL must be quoted, too. To put a double quote or backslash in a quoted array element value, precede it with a backslash. Alternatively, you can avoid quotes and use backslash-escaping to protect all data characters that would otherwise be taken as array syntax.

You can add whitespace before a left brace or after a right brace. You can also add whitespace before or after any individual item string. In all of these cases the whitespace will be ignored. However, whitespace within double-quoted elements, or surrounded on both sides by non-whitespace characters of an element, is not ignored.

작은 정보

The ARRAY constructor syntax (see 4.2.12절) is often easier to work with than the array-literal syntax when writing array values in SQL commands. In ARRAY, individual element values are written the same way they would be written when not members of an array.