JSON 함수

JSON 함수 (JSON Functions)

Apache Pinot의 JSON 함수는 JSON 문서에서 값을 추출·변환·조회하는 다양한 기능을 제공해요. 변환 함수와 스칼라 함수로 나뉘어요.

출처: 문서

본문

변환 함수 (Transform Functions)

이 함수들은 Pinot SQL 쿼리에서만 사용할 수 있어요.

Function
JSONEXTRACTSCALAR(jsonField, 'jsonPath', 'resultsType', [defaultValue]) jsonField에서 'jsonPath'를 평가하고, 결과를 'resultsType' 타입으로 반환하며, null 또는 파싱 오류 시 선택적 defaultValue를 사용해요.
JSONEXTRACTSCALARFAST(jsonField, 'jsonPath', 'resultsType', [defaultValue]) 단순 선형 경로에 스트리밍 추출을 사용하며, 일반 JSONEXTRACTSCALAR 동작을 유지하고 필요 시 폴백해요.
JSONEXTRACTSCALARFIRSTMATCH(jsonField, 'jsonPath', 'resultsType', [defaultValue]) 첫 번째 주소가 지정된 값 이후에 중지해요. 잘 구성되고 중복이 없는 JSON에서만 사용하세요.
JSONEXTRACTSCALARFORY(jsonField, 'jsonPath', 'resultsType', [defaultValue]) 실험적인 Apache Fory 변형으로, 선택적 Fory 런타임 의존성이 필요하며 그 외에는 호환성 폴백을 사용해요.
JSONEXTRACTKEY(jsonField, 'jsonPath', ['paramString']) 'jsonPath'를 기반으로 일치하는 모든 JSON 필드 키를 STRING_ARRAY로 추출해요.
JSONEXTRACTINDEX(jsonField, 'jsonPath', index, 'resultsType', [defaultValue]) 'jsonPath'와 일치하는 배열에서 인덱스 값을 추출하고 요청한 스칼라 타입으로 반환해요.
​EXTRACT(dateTimeField FROM dateTimeExpression) 'YYYY-MM-DD HH:MM:SS' 형식의 DATETIME 표현식에서 필드를 추출해요. 현재 이 변환 함수는 YEAR, MONTH, DAY, HOUR, MINUTE, SECOND 필드를 지원해요.

스칼라 함수 (Scalar Functions)

이 함수들은 테이블 수집 설정에서 컬럼 변환에 사용할 수 있어요.

Function Usage
​TOJSONMAPSTR(map) ​ 맵을 JSON 문자열로 변환
​JSONFORMAT(object) 객체를 JSON 문자열로 변환
​JSONPATH(jsonField, 'jsonPath') jsonField에서 'jsonPath'를 기반으로 객체 값을 추출하며, 결과 타입은 JSON 값에 따라 추론돼요. 데이터 타입이 지정되지 않아 쿼리에서 사용할 수 없어요.
​JSONEXTRACTOBJECT(jsonField) JSON 문서를 한 번 파싱해 후속 JSONPATH* 추출에 재사용 가능한 객체 또는 배열로 만들어요. null, 스칼라, 비-JSON 입력에 대해 예외 없이 null을 반환해요.
​JSONPATHLONG(jsonField, 'jsonPath', [defaultValue]) jsonField에서 'jsonPath'를 기반으로 Long 값을 추출하고, null 또는 파싱 오류 시 선택적 defaultValue를 사용해요.
​JSONPATHLONGFAST(jsonField, 'jsonPath', [defaultValue]) 단순 선형 경로를 위한 JSONPATHLONG의 옵트인 빠른 변형. JSONPATHLONG과 같은 결과를 유지하고, 지원되지 않는 JsonPath 형태나 비-JSON 입력에 대해서는 그로 폴백해요.
​JSONPATHLONGFIRSTMATCH(jsonField, 'jsonPath', [defaultValue]) JSONPATHLONGFAST의 조기 종료 변형. 중복 키가 첫 번째 발생으로 해석되고, 주소가 지정된 필드 이후의 잘못된 내용이 거부되지 않으므로 잘 구성되고 중복이 없는 JSON에서만 사용하세요.
​JSONPATHDOUBLE(jsonField, 'jsonPath', [defaultValue]) jsonField에서 'jsonPath'를 기반으로 Double 값을 추출하고, null 또는 파싱 오류 시 선택적 defaultValue를 사용해요.
​JSONPATHDOUBLEFAST(jsonField, 'jsonPath', [defaultValue]) 단순 선형 경로를 위한 JSONPATHDOUBLE의 옵트인 빠른 변형. JSONPATHDOUBLE과 같은 결과를 유지하고, 지원되지 않는 JsonPath 형태나 비-JSON 입력에 대해서는 그로 폴백해요.
​JSONPATHDOUBLEFIRSTMATCH(jsonField, 'jsonPath', [defaultValue]) JSONPATHDOUBLEFAST의 조기 종료 변형. 중복 키가 첫 번째 발생으로 해석되고, 주소가 지정된 필드 이후의 잘못된 내용이 거부되지 않으므로 잘 구성되고 중복이 없는 JSON에서만 사용하세요.
​JSONPATHSTRING(jsonField, 'jsonPath', [defaultValue]) jsonField에서 'jsonPath'를 기반으로 String 값을 추출하고, null 또는 파싱 오류 시 선택적 defaultValue를 사용해요.
​JSONPATHSTRINGFAST(jsonField, 'jsonPath', [defaultValue]) 단순 선형 경로를 위한 JSONPATHSTRING의 옵트인 빠른 변형. JSONPATHSTRING과 같은 결과를 유지하고, 지원되지 않는 JsonPath 형태나 비-JSON 입력에 대해서는 그로 폴백해요.
​JSONPATHSTRINGFIRSTMATCH(jsonField, 'jsonPath', [defaultValue]) JSONPATHSTRINGFAST의 조기 종료 변형. 중복 키가 첫 번째 발생으로 해석되고, 주소가 지정된 필드 이후의 잘못된 내용이 거부되지 않으므로 잘 구성되고 중복이 없는 JSON에서만 사용하세요.
​JSONPATHARRAY(jsonField, 'jsonPath') jsonField에서 'jsonPath'를 기반으로 배열을 추출하고, 결과 타입은 JSON 값에 따라 추론돼요. 데이터 타입이 지정되지 않아 쿼리에서 사용할 수 없어요.
​JSONPATHARRAYDEFAULTEMPTY(jsonField, 'jsonPath') jsonField에서 'jsonPath'를 기반으로 배열을 추출하고, 결과 타입은 JSON 값에 따라 추론돼요. null 또는 파싱 오류 시 빈 배열을 반환해요. 데이터 타입이 지정되지 않아 쿼리에서 사용할 수 없어요.
​JSONPATHEXISTS(jsonField, 'jsonPath') 경로가 JSON 객체에 존재하는지 확인
JSONKEYVALUEARRAYTOMAP(keyValueArray, [keyColumnName], [valueColumnName]) 키-값 객체 배열을 맵으로 추출해요. 기본 keyColumnName은 key, 기본 valueColumnName은 value예요.
JsonStringToArray(jsonString) JSON 문자열을 Java List로 변환
JsonStringToMap(jsonString) JSON 문자열을 Java Map으로 변환
JsonStringToListOrMap(jsonString) JSON 문자열을 Java List 또는 Map으로 변환

추가 참고 페이지 (Additional Reference Pages)

Function Function
JSONPATHLONG JSONPATHDOUBLE
JSONPATHSTRING JSONKEYVALUEARRAYTOMAP
JSONEXTRACTOBJECT JSONPATHEXISTS

더 많은 예시 (More Examples)

JSON 데이터를 어떻게 쿼리하는지 보려면, 다음 테이블이 있다고 가정해요:

Table myTable:
  id        INTEGER
  jsoncolumn    JSON 

Table data:
101,{"name":{"first":"daffy"\, "last":"duck"}\,"score":101\,"data":["a"\, "b"\, "c"\, "d"]}
102,{"name":{"first":"donald"\, "last":"duck"}\,"score":102\,"data":["a"\, "b"\, "e"\, "f"]}
103,{"name":{"first":"mickey"\, "last":"mouse"}\,"score":103\,"data":["a"\, "b"\, "g"\, "h"]}
104,{"name":{"first":"minnie"\, "last":"mouse"}\,"score":104\,"data":["a"\, "b"\, "i"\, "j"]}
105,{"name":{"first":"goofy"\, "last":"dwag"}\,"score":104\,"data":["a"\, "b"\, "i"\, "j"]}
106,{"person":{"name":"daffy duck"\, "companies":[{"name":"n1"\, "title":"t1"}\, {"name":"n2"\, "title":"t2"}]}}
107,{"person":{"name":"scrooge mcduck"\, "companies":[{"name":"n1"\, "title":"t1"}\, {"name":"n2"\, "title":"t2"}]}}

또한 "jsoncolumn"에 Json Index가 있다고 가정해요. 테이블의 마지막 두 행은 나머지 행과 구조가 다르다는 점에 주의하세요. JSON 명세에 따라 JSON 컬럼은 유효한 JSON 데이터를 포함할 수 있으며 미리 정의된 스키마를 따를 필요가 없어요. 각 행의 전체 JSON 문서를 가져오려면 아래 쿼리를 실행할 수 있어요:

SELECT id, jsoncolumn 
  FROM myTable
id jsoncolumn
"101" "{"name":{"first":"daffy","last":"duck"},"score":101,"data":["a","b","c","d"]}"
102" "{"name":{"first":"donald","last":"duck"},"score":102,"data":["a","b","e","f"]}
"103" "{"name":{"first":"mickey","last":"mouse"},"score":103,"data":["a","b","g","h"]}
"104" "{"name":{"first":"minnie","last":"mouse"},"score":104,"data":["a","b","i","j"]}"
"105" "{"name":{"first":"goofy","last":"dwag"},"score":104,"data":["a","b","i","j"]}"
"106" "{"person":{"name":"daffy duck","companies":[{"name":"n1","title":"t1"},{"name":"n2","title":"t2"}]}}"
"107" "{"person":{"name":"scrooge mcduck","companies":[{"name":"n1","title":"t1"},{"name":"n2","title":"t2"}]}}"

JSON 컬럼 안의 특정 키를 파고들어 추출하려면, 해당 키의 JsonPath 표현식을 컬럼 이름 끝에 추가하면 돼요.

SELECT id,
       json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null') last_name,
       json_extract_scalar(jsoncolumn, '$.name.first', 'STRING', 'null') first_name
       json_extract_scalar(jsoncolumn, '$.data[1]', 'STRING', 'null') value
  FROM myTable
id last_name first_name value
101 duck daffy b
102 duck donald b
103 mouse mickey b
104 mouse minnie b
105 dwag goofy b
106 null null null
107 null null null

세 번째 컬럼(value)은 id 106과 107의 행에 대해 null이라는 점에 주의하세요. 이는 이 행들의 JSON 문서에 JsonPath $.data\[1]인 키가 없기 때문이에요. 이 행들을 필터링할 수 있어요.

SELECT id,
       json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null') last_name,
       json_extract_scalar(jsoncolumn, '$.name.first', 'STRING', 'null') first_name,
       json_extract_scalar(jsoncolumn, '$.data[1]', 'STRING', 'null') value
  FROM myTable
 WHERE JSON_MATCH(jsoncolumn, '"$.data[1]" IS NOT NULL')
id last_name first_name value
101 duck daffy b
102 duck donald b
103 mouse mickey b
104 mouse minnie b
105 dwag goofy b

위 데이터에는 특정 성(예: duck과 mouse)이 반복돼요. JsonPath 표현식에 GROUP BY 쿼리를 실행해 각 성의 개수를 구할 수 있어요.

  SELECT json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null') last_name,
         count(*)
    FROM myTable
   WHERE JSON_MATCH(jsoncolumn, '"$.data[1]" IS NOT NULL')
GROUP BY json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null')
ORDER BY 2 DESC
jsoncolumn.name.last count(*)
"mouse" "2"
"duck" "2"
"dwag" "1"

또한 JSON 문서 안에는 숫자 정보(jsconcolumn.$.id)가 포함돼 있어요. 아래 쿼리를 사용해 JSON 데이터에서 이러한 숫자 값을 SQL로 추출하고 합산할 수 있어요.

  SELECT json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null') last_name,
         sum(json_extract_scalar(jsoncolumn, '$.id', 'INT', 0)) total
    FROM myTable
   WHERE JSON_MATCH(jsoncolumn, '"$.name.last" IS NOT NULL')
GROUP BY json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null')
jsoncolumn.name.last sum(jsoncolumn.score)
"mouse" "207"
"dwag" "104"
"duck" "203"

JSON_MATCH와 JSON_EXTRACT_SCALAR

JSON_MATCH 함수는 JsonIndex를 활용하며, JSON 컬럼에 JsonIndex가 이미 존재하는 경우에만 사용할 수 있어요. 위 예시에서 보듯이 JSON_MATCH 연산자의 두 번째 인자는 조건문(predicate)을 받아요. 이 조건문은 JsonIndex에 대해 평가되며 =, !=, IS NULL, IS NOT NULL, IN과 >, <, >=, <=, BETWEEN 같은 관계 연산자를 지원해요.

JSON_MATCH 함수는 전체 JsonPath 표현식을 지원하지 않지만 와일드카드 * JsonPath 표현식은 사용할 수 있어요.

  SELECT json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null') last_name,
         json_extract_scalar(jsoncolumn, '$.id', 'INT', 0) total
    FROM myTable
   WHERE JSON_MATCH(jsoncolumn, '"$.data[*]" = ''f''')
GROUP BY json_extract_scalar(jsoncolumn, '$.name.last', 'STRING', 'null')
last_name total
"duck" "102"

JSON_MATCH는 IS NULL과 IS NOT NULL 연산자를 지원하지만, 이 연산자는 리프 레벨 경로 요소에만 적용되어야 해요. 즉 조건 JSON_MATCH(jsoncolumn, '"$.data[*]" IS NOT NULL')는 "$.data[*]"가 경로의 "리프" 요소를 가리키지 않으므로 유효하지 않아요. 반면 "$.data[0]" IS NOT NULL'는 "$.data[0]"가 경로의 리프 요소를 명확히 식별하므로 유효해요.

JSON_EXTRACT_SCALAR는 JsonIndex를 활용하지 않으므로, JsonIndex를 사용하는 JSON_MATCH보다 느려요. 그러나 JSON_EXTRACT_SCALAR는 더 넓은 범위의 JsonPath 표현식과 연산자를 지원해요. 빠른 인덱스 접근(JSON_MATCH)과 JsonPath 표현식(JSON_EXTRACT_SCALAR)을 모두 최대한 활용하려면 WHERE 절에서 이 두 함수를 결합해 사용할 수 있어요.

JSON_MATCH 문법 (JSON_MATCH syntax)

JSON_MATCH 함수의 두 번째 인자는 문자열 형태의 불리언 표현식이에요. 이 섹션은 JSON_MATCH의 두 번째 인자를 올바르게 작성하는 방법을 보여줘요. JSON 배열 data에서 값 k와 j를 검색한다고 가정해 봐요. 이는 다음 조건문으로 할 수 있어요:

data[0] IN ('k', 'j')

이 조건문을 JSON_MATCH에서 사용할 문자열 형태로 변환하려면, 먼저 조건의 왼쪽을 큰따옴표로 묶어 식별자로 바꿔요:

"data[0]" IN ('k', 'j')

다음으로, 조건의 리터럴도 '로 묶어야 해요. 기존의 '도 이스케이프해야 해요. 이렇게 하면:

"data[0]" IN (''k'', ''j'')

마지막으로 위 전체 표현식을 '로 묶어 문자열로 만들어야 해요:

'"data[0]" IN (''k'', ''j'')'

이제 원래 조건의 문자열 표현을 얻었고, 이를 JSON_MATCH 함수에 사용할 수 있어요:

   WHERE JSON_MATCH(jsoncolumn, '"data[0]" IN (''k'', ''j'')')

더 많은 JSON_MATCH 예시는 JSON Index를 참고하세요.

더 알아보기 (Learn more)