배열 함수
배열 함수 (Array Functions)
Turso는 SQL에서 배열 컬럼과 배열을 다루는 함수들을 기본으로 제공해요. 배열을 만들고, 원소에 접근하고, 변환하는 작업을 쿼리 안에서 바로 할 수 있어요. 배열은 내부적으로는 압축된 레코드 형식 BLOB으로 저장되고, 출력될 때는 JSON 배열 모양으로 보여져요.
출처: 문서
본문
**Turso 확장 기능**: 배열 타입과 배열 함수는 SQL에서 순서 있는 컬렉션을 직접 다루는 Turso 전용 기능이에요. 표준 SQLite에는 없는 기능이에요. 배열 컬럼은 [STRICT 테이블](/sql-reference/statements/create-table#strict-tables)이 필요해요.Turso는 배열 컬럼과 배열을 만들고, 조회하고, 변환하는 함수 집합을 기본으로 지원해요. 배열은 내부적으로는 압축된 레코드 형식 BLOB으로 저장되고, 출력 시에는 JSON 배열로 표시돼요.
배열 만들기
ARRAY[] 리터럴
표현식 목록으로 배열을 만들어요.
ARRAY[expr1, expr2, ...]
SELECT ARRAY[1, 2, 3];
-- [1,2,3]
SELECT ARRAY['hello', 'world'];
-- ["hello","world"]
SELECT ARRAY[];
-- []
배열은 JSON 텍스트로 삽입할 수도 있어요:
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
INSERT INTO t VALUES (2, '["x", "y", "z"]');
SELECT tags FROM t ORDER BY id;
-- ["a","b","c"]
-- ["x","y","z"]
string_to_array
문자열을 구분자 기준으로 잘라서 배열로 만들어요.
string_to_array(text, delimiter)
string_to_array(text, delimiter, null_string)
| 매개변수 | 타입 | 설명 |
|---|---|---|
text |
TEXT | 자를 문자열 |
delimiter |
TEXT | 나눌 구분자. NULL이면 개별 문자로 나눠요 |
null_string |
TEXT | 선택. 이 문자열과 일치하는 원소는 NULL로 바꿔요 |
반환: BLOB -- 배열 값.
SELECT string_to_array('one,two,three', ',');
-- ["one","two","three"]
SELECT string_to_array('hello', NULL);
-- ["h","e","l","l","o"]
SELECT string_to_array('a,NULL,b', ',', 'NULL');
-- ["a",null,"b"]
원소 접근
첨자 연산자 []
0부터 시작하는 인덱스로 개별 원소에 접근해요. 범위를 벗어나거나 음수 인덱스면 NULL을 반환해요.
array_expr[index]
CREATE TABLE t (id INTEGER PRIMARY KEY, items TEXT[]) STRICT;
INSERT INTO t VALUES (1, '["first", "second", "third"]');
SELECT items[0], items[1], items[2] FROM t;
-- first|second|third
SELECT items[100] FROM t;
-- NULL
SELECT items[-1] FROM t;
-- NULL
첨자는 체인처럼 연결해서 다차원 배열에도 쓸 수 있어요:
CREATE TABLE m (id INTEGER PRIMARY KEY, matrix INTEGER[][]) STRICT;
INSERT INTO m VALUES (1, ARRAY[ARRAY[1,2], ARRAY[3,4]]);
SELECT matrix[0][1] FROM m;
-- 2
SELECT matrix[1][0] FROM m;
-- 3
슬라이스 연산자 [start:end]
반열린 범위 [start, end)로 부분 배열을 뽑아내요.
array_expr[start:end]
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a','b','c','d']);
SELECT tags[1:3] FROM t;
-- ["b","c"]
스칼라 함수
array_length
배열의 주어진 차원에 있는 원소 개수를 반환해요.
array_length(array)
array_length(array, dimension)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 길이를 재 배열 |
dimension |
INTEGER | 선택. 1부터 시작하는 차원 인덱스. 기본값은 1 (가장 바깥 차원) |
반환: INTEGER -- dimension의 원소 개수. 입력이 NULL이거나 dimension < 1이거나 dimension이 배열의 중첩 깊이를 넘으면 NULL이에요.
다차원 배열에서는 탐색기가 각 단계에서 0번 원소를 들여다보기 때문에, 같은 깊이의 모든 내부 배열이 같은 길이를 가진 것처럼 값이 보고돼요 (Turso가 실제로 만드는 레이아웃이 그래요).
SELECT array_length(ARRAY[10, 20, 30]);
-- 3
SELECT array_length('[]');
-- 0
SELECT array_length(NULL);
-- NULL
SELECT array_length(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 1);
-- 2 (outer dimension)
SELECT array_length(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 2);
-- 3 (inner dimension)
SELECT array_length(ARRAY[1, 2, 3], 2);
-- NULL (dimension beyond the array's depth)
SELECT array_length(ARRAY[1, 2, 3], 0);
-- NULL (dimension < 1)
array_upper
배열의 주어진 차원의 상한(포함)을 반환해요.
array_upper(array, dimension)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 길이를 재 배열 |
dimension |
INTEGER | 1부터 시작하는 차원 인덱스 |
반환: INTEGER -- dimension의 상한. NULL 조건은 array_length와 같아요.
Turso 배열은 항상 1 인덱스라서, 유효한 입력에 대해서는 array_upper(arr, dim)이 array_length(arr, dim)과 같아요. 이 함수는 PostgreSQL 호환성을 위해 제공되는 거예요.
SELECT array_upper(ARRAY[10, 20, 30], 1);
-- 3
SELECT array_upper(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 2);
-- 3
SELECT array_upper(ARRAY[1, 2, 3], 2);
-- NULL
array_append
배열 끝에 원소를 붙여요.
array_append(array, element)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 원소를 붙일 배열. NULL이면 원소 하나짜리 새 배열을 만들어요 |
element |
any | 붙일 원소 |
반환: BLOB -- 원소가 붙은 새 배열.
SELECT array_append('[1, 2]', 3);
-- [1,2,3]
SELECT array_append('[]', 'x');
-- ["x"]
SELECT array_append(NULL, 'x');
-- ["x"]
array_prepend
배열 앞에 원소를 붙여요.
array_prepend(element, array)
| 매개변수 | 타입 | 설명 |
|---|---|---|
element |
any | 앞에 붙일 원소 |
array |
BLOB/TEXT | 원소를 붙일 배열 |
반환: BLOB -- 원소가 앞에 붙은 새 배열.
SELECT array_prepend('z', '["a", "b"]');
-- ["z","a","b"]
array_cat
두 배열을 이어 붙여요.
array_cat(array1, array2)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array1 |
BLOB/TEXT | 첫 번째 배열 |
array2 |
BLOB/TEXT | 두 번째 배열 |
반환: BLOB -- 두 배열의 모든 원소를 담은 새 배열.
SELECT array_cat('[1, 2]', '[3, 4]');
-- [1,2,3,4]
SELECT array_cat('[]', '[1, 2]');
-- [1,2]
|| 연산자로도 배열을 이어 붙일 수 있어요:
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a', 'b']);
-- Append element
SELECT tags || 'c' FROM t;
-- ["a","b","c"]
-- Prepend element
SELECT 'z' || tags FROM t;
-- ["z","a","b"]
-- Concatenate arrays
SELECT tags || ARRAY['x', 'y'] FROM t;
-- ["a","b","x","y"]
array_remove
배열에서 어떤 원소가 나오는 부분을 모두 없애요.
array_remove(array, element)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 원소를 뺄 배열 |
element |
any | 뺄 원소 |
반환: BLOB -- 해당 원소가 모두 제거된 새 배열.
SELECT array_remove('[1, 2, 3, 2, 1]', 2);
-- [1,3,1]
SELECT array_remove('[1, 2, 3]', 99);
-- [1,2,3]
array_contains
배열에 특정 원소가 들어 있는지 검사해요.
array_contains(array, element)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 찾아볼 배열 |
element |
any | 찾을 원소 |
반환: INTEGER -- 찾으면 1, 없으면 0.
SELECT array_contains('[1, 2, 3]', 2);
-- 1
SELECT array_contains('[1, 2, 3]', 5);
-- 0
array_position
어떤 원소가 처음 나오는 위치를 0부터 시작하는 인덱스로 반환해요.
array_position(array, element)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 찾아볼 배열 |
element |
any | 찾을 원소 |
반환: INTEGER -- 0부터 시작하는 인덱스, 없으면 NULL.
SELECT array_position('[10, 20, 30]', 20);
-- 1
SELECT array_position('[10, 20, 30]', 10);
-- 0
SELECT array_position('[10, 20, 30]', 99);
-- NULL
array_slice
인덱스 범위로 부분 배열을 뽑아내요.
array_slice(array, start, end)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 원본 배열 |
start |
INTEGER | 시작 인덱스 (0부터 시작, 포함). NULL이면 0 |
end |
INTEGER | 끝 인덱스 (제외) |
반환: BLOB -- start부터 end - 1까지의 원소를 담은 새 배열.
SELECT array_slice('[1, 2, 3, 4]', 1, 3);
-- [2,3]
SELECT array_slice('[1, 2, 3, 4]', NULL, 2);
-- [1,2]
array_to_string
배열 원소를 구분자로 이어서 하나의 문자열로 만들어요.
array_to_string(array, delimiter)
array_to_string(array, delimiter, null_string)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array |
BLOB/TEXT | 이어 붙일 배열 |
delimiter |
TEXT | 원소 사이에 넣을 구분자 |
null_string |
TEXT | 선택. NULL 원소를 대신할 텍스트. 생략하면 NULL은 건너뛰어요 |
반환: TEXT -- 이어 붙인 문자열.
SELECT array_to_string('[1, 2, 3]', ',');
-- 1,2,3
SELECT array_to_string('["hello", "world"]', ' ');
-- hello world
SELECT array_to_string('[1, null, 3]', ',');
-- 1,3
SELECT array_to_string('[1, null, 3]', ',', 'N/A');
-- 1,N/A,3
array_contains_all
한 배열이 다른 배열의 모든 원소를 포함하는지 검사해요.
array_contains_all(haystack, needles)
| 매개변수 | 타입 | 설명 |
|---|---|---|
haystack |
BLOB/TEXT | 찾아볼 배열 |
needles |
BLOB/TEXT | 찾을 원소들의 배열 |
반환: INTEGER -- needles의 모든 원소가 haystack에 있으면 1, 아니면 0.
SELECT array_contains_all('[1, 2, 3, 4, 5]', '[2, 4]');
-- 1
SELECT array_contains_all('[1, 2, 3]', '[2, 4]');
-- 0
SELECT array_contains_all('[1, 2, 3]', '[]');
-- 1
@> 연산자가 이 함수의 별칭이에요. 배열 연산자를 참고하세요.
array_overlap
두 배열이 공통 원소를 하나라도 공유하는지 검사해요.
array_overlap(array1, array2)
| 매개변수 | 타입 | 설명 |
|---|---|---|
array1 |
BLOB/TEXT | 첫 번째 배열 |
array2 |
BLOB/TEXT | 두 번째 배열 |
반환: INTEGER -- 어느 하나의 원소라도 양쪽에 모두 나타나면 1, 아니면 0.
SELECT array_overlap('[1, 2, 3]', '[3, 4, 5]');
-- 1
SELECT array_overlap('[1, 2, 3]', '[4, 5, 6]');
-- 0
SELECT array_overlap('[]', '[1, 2]');
-- 0
array_overlaps도 별칭으로 받아들여요. && 연산자 역시 별칭이에요. 배열 연산자를 참고하세요.
집계 함수
array_agg
행 그룹의 값들을 모아서 하나의 배열로 만들어요.
array_agg(expression)
| 매개변수 | 타입 | 설명 |
|---|---|---|
expression |
any | 모을 값. NULL 값도 포함돼요 |
반환: BLOB -- 그룹의 각 행마다 원소 하나씩 담긴 배열. 빈 그룹이면 NULL을 반환해요.
CREATE TABLE t (grp TEXT, val TEXT);
INSERT INTO t VALUES ('x', 'a'), ('x', 'b'), ('y', 'c'), ('y', 'd'), ('y', 'e');
SELECT grp, array_agg(val) FROM t GROUP BY grp ORDER BY grp;
| grp | array_agg(val) |
|---|---|
| x | ["a","b"] |
| y | ["c","d","e"] |
-- Control order with a subquery
SELECT array_agg(val) FROM (SELECT val FROM t ORDER BY val);
-- ["a","b","c","d","e"]
-- Combine with array_length
SELECT array_length(array_agg(val)) FROM t;
-- 5
배열 연산자
포함: @>
왼쪽 배열이 오른쪽 배열의 모든 원소를 포함하는지 검사해요. array_contains_all(left, right)와 같아요.
array1 @> array2
SELECT ARRAY[1,2,3] @> ARRAY[1,2];
-- 1
SELECT ARRAY[1,2,3] @> ARRAY[1,4];
-- 0
겹침: &&
두 배열이 공통 원소를 하나라도 공유하는지 검사해요. array_overlap(left, right)와 같아요.
array1 && array2
SELECT ARRAY[1,2,3] && ARRAY[3,4,5];
-- 1
SELECT ARRAY[1,2] && ARRAY[3,4];
-- 0
비교: =, !=, <, >, <=, >=
배열은 원소별 비교를 지원해요. 원소는 왼쪽에서 오른쪽으로 짝지어 비교하고, 비교한 원소가 모두 같으면 더 짧은 배열이 더 작은 것으로 취급돼요.
SELECT ARRAY[1,2,3] = ARRAY[1,2,3]; -- 1
SELECT ARRAY[1,2] < ARRAY[1,3]; -- 1
SELECT ARRAY[1,2] < ARRAY[1,2,3]; -- 1 (shorter is less)
이어 붙이기: ||
피연산자 중 하나라도 배열이면, ||는 문자열 연결 대신 배열 연결이나 원소 추가/앞붙이기를 해줘요.
SELECT ARRAY[1,2] || ARRAY[3,4]; -- [1,2,3,4] (array + array)
SELECT ARRAY[1,2] || 3; -- [1,2,3] (append element)
SELECT 0 || ARRAY[1,2]; -- [0,1,2] (prepend element)
WHERE 절에서 연산자 사용하기
CREATE TABLE docs (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO docs VALUES (1, ARRAY['sql','database','tutorial']);
INSERT INTO docs VALUES (2, ARRAY['rust','systems']);
INSERT INTO docs VALUES (3, ARRAY['sql','rust']);
-- Find docs tagged with both 'sql' and 'rust'
SELECT id FROM docs WHERE tags @> ARRAY['sql','rust'];
-- 3
-- Find docs with any overlap with a set of tags
SELECT id FROM docs WHERE tags && ARRAY['database','systems'] ORDER BY id;
-- 1
-- 2
첨자 대입
UPDATE 문에서 첨자 문법을 쓰면 배열 원소를 개별적으로 갱신할 수 있어요.
UPDATE table SET column[index] = value WHERE ...;
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a','b','c']);
UPDATE t SET tags[0] = 'X' WHERE id = 1;
SELECT tags FROM t;
-- ["X","b","c"]
-- Multiple subscripts in one UPDATE
UPDATE t SET tags[0] = 'A', tags[2] = 'C' WHERE id = 1;
SELECT tags FROM t;
-- ["A","b","C"]
범위를 벗어나는 대입은 조용히 무시돼요 (배열은 그대로 유지돼요).
예제
태깅 시스템
CREATE TABLE articles (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[]
) STRICT;
INSERT INTO articles VALUES (1, 'Getting Started with SQL', ARRAY['sql','beginner','tutorial']);
INSERT INTO articles VALUES (2, 'Advanced Indexing', ARRAY['sql','performance','advanced']);
INSERT INTO articles VALUES (3, 'Rust for Systems', ARRAY['rust','systems']);
-- Find articles tagged 'sql'
SELECT title FROM articles WHERE array_contains(tags, 'sql');
-- Find articles with both 'sql' and 'advanced'
SELECT title FROM articles WHERE tags @> ARRAY['sql','advanced'];
-- Advanced Indexing
-- Count tags per article
SELECT title, array_length(tags) AS tag_count FROM articles;
-- Collect all unique tags
SELECT array_agg(DISTINCT tag) FROM (
SELECT array_to_string(tags, ',') AS tag FROM articles
);
다차원 배열
CREATE TABLE matrices (
id INTEGER PRIMARY KEY,
data INTEGER[][]
) STRICT;
INSERT INTO matrices VALUES (1, ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]]);
-- Access a row
SELECT data[0] FROM matrices WHERE id = 1;
-- [1,2,3]
-- Access a single element
SELECT data[1][2] FROM matrices WHERE id = 1;
-- 6
-- Get number of rows
SELECT array_length(data) FROM matrices WHERE id = 1;
-- 2
타입 검사
STRICT 테이블의 배열 컬럼은 삽입과 갱신 시점에 원소 타입을 검증해요:
CREATE TABLE scores (id INTEGER PRIMARY KEY, vals INTEGER[]) STRICT;
-- OK: exact integers
INSERT INTO scores VALUES (1, '[1, 2, 3]');
-- OK: floats that are exact integers are coerced
INSERT INTO scores VALUES (2, '[1.0, 2.0]');
-- OK: numeric strings are coerced
INSERT INTO scores VALUES (3, '["42", "100"]');
-- Error: non-numeric string in INTEGER array
INSERT INTO scores VALUES (4, '["hello"]');
-- Error: cannot store TEXT value in INTEGER
-- NULL elements are always allowed
INSERT INTO scores VALUES (5, '[1, null, 3]');
더 알아보기 (Learn more)
- 데이터 타입 - 배열 컬럼 타입 선언
- CREATE TABLE - STRICT 테이블 요건
- 표현식 - 배열 연산자 문법
- 집계 함수 -
array_agg