[JSON Parse] JSON에 들어가있는 데이터 사용하기

2026. 1. 26. 13:44·DB/SQL
728x90

[SQL] Base64로 인코딩된 JSON, DB에서 바로 열고 배열까지 풀기 (MySQL, Postgre, BigQuery)

개발을 하다 보면 데이터베이스 컬럼에 알아볼 수 없는 문자열이 저장된 경우를 종종 마주합니다.

eyJ1aV9tb2RlIjogImRhcmsiLCAidGFncyI6IFsiU1FMIiwgIkRiIiwgIlRpcCJdfQ==

바로 Base64로 인코딩된 JSON 데이터입니다.

보통은 애플리케이션 코드(Java, Python, Node.js 등)에서 데이터를 가져온 뒤 언어 레벨에서 디코딩해서 사용합니다. 하지만 긴급하게 데이터를 확인하거나, 데이터 웨어하우스에서 통계를 내야 할 때는 DB 접속 툴(DBeaver, Datagrip 등)에서 SQL 쿼리만으로 해결해야 하는 순간이 옵니다.

오늘은 MySQL(MariaDB), PostgreSQL 그리고 클라우드 환경인 Google BigQuery에서 이 암호문 같은 데이터를 SQL만으로 자유자재로 다루는 법을 정리해 봅니다.

특히 JSON 배열(['A', 'B'])을 행(Row)으로 펼치는 고급 기법(UNNEST/CROSS JOIN)까지 다룹니다.


1. 이 짓을 도대체 왜, 언제 하나요? (Use Case)

정상적인 DB 설계라면 JSON 데이터를 JSON 타입 컬럼에 넣거나, 정규화해서 테이블로 쪼개 저장하는 것이 맞습니다. 하지만 현실(Legacy)은 그렇지 않을 때가 많습니다.

  • 긴급 디버깅 (Hotfix): 운영 중인 서비스의 설정값이 꼬였는데, 로그 찍을 시간도 없이 DB 원천 데이터를 바로 확인해야 할 때.
  • 통계 추출 (Analytics): "설정값에 'Dark Mode'를 켜둔 사용자가 몇 명이지?" 처럼, 숨겨진 데이터로 집계 쿼리를 짜야 할 때.
  • ELT 데이터 파이프라인: 원천 로그를 일단 Base64 통째로 데이터 레이크(BigQuery 등)에 적재해두고, 나중에 쿼리로 파싱해서 쓰는 경우.

2. MySQL / MariaDB (8.0+)

가정: TB_USER_LOG 테이블의 LOG_BLOB 컬럼에 Base64 문자열이 들어있습니다.

✅ 조회 및 값 추출 (SELECT)

가장 먼저 해야 할 일은 '문자셋 변환'입니다. Base64를 풀면 바이너리(이진 데이터)가 되는데, 이를 반드시 UTF-8 문자로 변환해줘야 JSON 함수가 먹힙니다.

SELECT 
    ID,
    -- 1단계: 통째로 디코딩해서 눈으로 확인하기
    CONVERT(FROM_BASE64(LOG_BLOB) USING utf8mb4) AS FULL_JSON,

    -- 2단계: JSON 내부의 특정 값만 콕 집어서 추출하기 (화살표 연산자)
    -- JSON_UNQUOTE를 안 쓰면 "dark" 처럼 따옴표가 붙어서 나옵니다.
    JSON_UNQUOTE(
        JSON_EXTRACT(
            CONVERT(FROM_BASE64(LOG_BLOB) USING utf8mb4), 
            '$.ui_mode'
        )
    ) AS UI_MODE
FROM TB_USER_LOG
WHERE ID = 101;

✅ 값 수정 (UPDATE)

데이터를 꺼내서, 수정하고, 다시 묶어서 저장하는 과정을 쿼리 한 방으로 처리합니다.

UPDATE TB_USER_LOG
SET LOG_BLOB = TO_BASE64(                 -- 3. [마무리] 다시 Base64로 포장
    JSON_SET(                             -- 2. [수정] JSON 값을 변경
        CONVERT(FROM_BASE64(LOG_BLOB) USING utf8mb4), -- 1. [준비] 내용물 꺼내기
        '$.ui_mode',                      -- 바꿀 타겟 경로
        'light'                           -- 새로운 값
    )
)
WHERE ID = 101;

🔥 고급: JSON 배열 평탄화 (JSON_TABLE)

만약 데이터가 {"tags": ["SQL", "Db", "Tip"]} 처럼 배열을 포함하고 있고, 이 태그들을 각각 하나의 행(Row)으로 쪼개고 싶다면 어떻게 해야 할까요?

이때 사용하는 것이 JSON_TABLE입니다. 이 함수는 내부적으로 원본 테이블과 추출된 JSON 데이터 간의 CROSS JOIN(상호 조인)과 유사한 역할을 수행하여 데이터를 뻥튀기(Unnest)합니다.

SELECT 
    T.ID,
    JT.TAG_NAME
FROM 
    TB_USER_LOG T,
    JSON_TABLE(
        CONVERT(FROM_BASE64(T.LOG_BLOB) USING utf8mb4), -- 1. 디코딩 대상
        '$.tags[*]'                                     -- 2. 배열 경로 ([*]는 전체)
        COLUMNS (
            TAG_NAME VARCHAR(50) PATH '$'               -- 3. 값을 넣을 컬럼 정의
        )
    ) AS JT;

결과:

ID TAG_NAME
101 SQL
101 Db
101 Tip

한 줄이었던 데이터가 태그 개수만큼 3줄로 늘어났습니다.


3. PostgreSQL

PostgreSQL은 JSON 처리에 있어 가장 강력하고 엄격한 기능을 제공합니다.

가정: tb_app_config 테이블의 config_data 컬럼(text 타입)에 데이터가 있습니다.

✅ 조회 및 값 추출 (SELECT)

PostgreSQL은 decode 함수를 사용하며, ::json 또는 ::jsonb로 형변환(Casting)이 필수입니다.

SELECT 
    id,
    -- 1단계: 디코딩 (bytea -> text 변환)
    convert_from(decode(config_data, 'base64'), 'UTF8') AS json_raw,

    -- 2단계: 값 추출 (화살표 연산자 ->>)
    (convert_from(decode(config_data, 'base64'), 'UTF8')::json ->> 'ui_mode') AS ui_mode
FROM tb_app_config
WHERE id = 55;

✅ 값 수정 (UPDATE)

jsonb_set 함수를 활용합니다. 수정 시 jsonb 타입으로 변환했다가, 저장할 때는 다시 bytea -> base64로 돌아가야 하는 '형변환 지옥'에 주의하세요.

UPDATE tb_app_config
SET config_data = encode(        -- 4. [마무리] 다시 Base64로 인코딩
    jsonb_set(                   -- 3. [수정] JSON 값 변경
        convert_from(decode(config_data, 'base64'), 'UTF8')::jsonb, -- 1. [준비]
        '{ui_mode}',             -- 2. [타겟] 경로 (배열 표기법 사용)
        '"light"'                -- 새로운 값 (JSON 규격 준수)
    )::text::bytea,              -- 인코딩을 위해 text -> bytea로 변환
    'base64'
)
WHERE id = 55;

🔥 고급: 배열 풀기 (CROSS JOIN LATERAL & UNNEST)

PostgreSQL에서 JSON 배열을 행으로 펼칠 때는 CROSS JOIN LATERAL과 jsonb_array_elements 함수를 조합합니다.

이 구문은 "각 행(Row)마다(LATERAL) JSON 배열을 풀어서(unnest) 원본 테이블과 곱하기(CROSS JOIN) 하겠다"는 의미입니다.

SELECT 
    t.id,
    -- 배열 요소에서 텍스트 추출
    elem.value #>> '{}' as tag_name 
FROM 
    tb_app_config t
    -- CROSS JOIN LATERAL을 사용하여 각 행의 JSON 배열을 순회
    CROSS JOIN LATERAL jsonb_array_elements(
        convert_from(decode(t.config_data, 'base64'), 'UTF8')::jsonb -> 'tags'
    ) AS elem
WHERE t.id = 55;

핵심 포인트:

  • CROSS JOIN: 원래 테이블의 1개 행이 배열 요소 N개와 결합되어 N개의 행이 됩니다.
  • LATERAL: 조인하는 오른쪽 서브쿼리(jsonb_array_elements)가 왼쪽 테이블(t)의 컬럼을 참조할 수 있게 해줍니다. 이게 없으면 함수 안에서 t.config_data를 못 읽어옵니다.

4. 구글 빅쿼리 (Google BigQuery)

최근 많이 사용하는 BigQuery 같은 클라우드 데이터 웨어하우스(SaaS)에서는 이 과정이 훨씬 더 빈번하게 일어납니다.

데이터 엔지니어링 트렌드가 ELT (일단 적재하고 나중에 변환)로 바뀌면서, 로그나 설정값을 일단 Base64나 통 JSON으로 빅쿼리에 던져놓고 필요할 때 쿼리로 뜯어보는 경우가 많기 때문입니다.

✅ 조회 및 디코딩 (Safe functions)

빅쿼리는 FROM_BASE64 결과가 BYTES 타입이므로 STRING으로 변환해줘야 합니다. 이때 한글 깨짐이나 데이터 오류를 방지하기 위해 SAFE_CONVERT_BYTES_TO_STRING을 쓰면 쿼리가 중간에 죽지 않고 안전합니다.

SELECT 
    id,
    -- 1. Base64 디코딩 (BYTES -> STRING 변환 필수)
    SAFE_CONVERT_BYTES_TO_STRING(FROM_BASE64(log_blob)) AS json_str,

    -- 2. JSON 값 추출 (JSON_VALUE: 스칼라 값 추출)
    JSON_VALUE(
        SAFE_CONVERT_BYTES_TO_STRING(FROM_BASE64(log_blob)), 
        '$.ui_mode'
    ) AS ui_mode
FROM `project.dataset.tb_user_log`
WHERE id = 101;

🔥 핵심: 콤마(,) 하나로 끝내는 배열 풀기 (UNNEST)

빅쿼리는 아예 배열(Array)과 구조체(Struct)를 네이티브로 지원합니다. 특히 CROSS JOIN을 명시하지 않고 콤마(,) 만으로도 묵시적 조인(Correlated Cross Join)이 가능합니다.

JSON 안의 tags 리스트(["A", "B", "C"])를 행으로 펼치는 쿼리는 다음과 같습니다.

SELECT 
    t.id,
    tag -- UNNEST로 풀려난 태그 값
FROM 
    `project.dataset.tb_user_log` t, -- 콤마(,)가 CROSS JOIN 역할을 함
    UNNEST(
        JSON_VALUE_ARRAY( -- JSON 문자열 배열을 빅쿼리 ARRAY로 변환
            SAFE_CONVERT_BYTES_TO_STRING(FROM_BASE64(t.log_blob)),
            '$.tags'
        )
    ) AS tag
WHERE t.id = 101;

빅쿼리 특징:

  • JSON_VALUE_ARRAY: JSON 안의 리스트를 빅쿼리의 ARRAY<STRING> 타입으로 바로 바꿔줍니다.
  • UNNEST(...): 배열을 행(Row)으로 펼칩니다.
  • 콤마(,) Join: FROM 테이블, UNNEST(...) 구문으로 가장 직관적인 평탄화가 가능합니다.


5. Snowflake & AWS Athena (Cloud Data Warehouse)

클라우드 데이터 플랫폼의 양대 산맥인 Snowflake와 AWS Athena도 빠질 수 없습니다. 이들은 대용량 로그 분석에 최적화되어 있어 Base64/JSON 파싱 기능을 아주 강력하게 지원합니다.

❄️ Snowflake (스노우플레이크)

Snowflake는 VARIANT라는 반정형 데이터 타입을 핵심으로 내세우기 때문에, JSON 처리가 타 DB 대비 압도적으로 편합니다.

✅ 조회 및 디코딩

BASE64_DECODE_STRING 함수 한 번이면 끝납니다. 이후 PARSE_JSON을 통해 VARIANT 객체로 만들고, :(콜론) 표기법으로 바로 접근합니다.

SELECT 
    ID,
    -- 1. 디코딩 및 JSON 파싱
    PARSE_JSON(BASE64_DECODE_STRING(LOG_BLOB)) AS JSON_OBJ,

    -- 2. 값 추출 (콜론 표기법 사용)
    PARSE_JSON(BASE64_DECODE_STRING(LOG_BLOB)):ui_mode::STRING AS UI_MODE
FROM TB_USER_LOG
WHERE ID = 101;

🔥 핵심: LATERAL FLATTEN

Snowflake에서는 UNNEST 대신 FLATTEN이라는 함수를 사용합니다.

SELECT 
    t.ID,
    f.VALUE::STRING AS TAG_NAME -- FLATTEN된 결과는 'VALUE' 컬럼에 들어감
FROM 
    TB_USER_LOG t,
    LATERAL FLATTEN(
        input => PARSE_JSON(BASE64_DECODE_STRING(t.LOG_BLOB)):tags
    ) f
WHERE t.ID = 101;

🌩️ AWS Athena (Presto / Trino 기반)

Athena는 Presto 엔진을 사용하므로, AWS Redshift Spectrum이나 다른 Presto 기반 DW와 문법이 유사합니다.

✅ 조회 및 디코딩

Athena는 FROM_BASE64 결과가 VARBINARY이므로, VARCHAR로 캐스팅하는 과정이 필요합니다.

SELECT 
    id,
    -- 1. 디코딩 (Varbinary -> Varchar)
    CAST(FROM_BASE64(log_blob) AS VARCHAR) AS json_str,

    -- 2. 값 추출
    JSON_EXTRACT_SCALAR(
        CAST(FROM_BASE64(log_blob) AS VARCHAR),
        '$.ui_mode'
    ) AS ui_mode
FROM tb_user_log
WHERE id = 101;

🔥 핵심: CROSS JOIN UNNEST

Athena에서는 CROSS JOIN UNNEST를 사용하며, JSON 배열을 ARRAY 타입으로 먼저 변환(JSON_PARSE 후 CAST)해줘야 합니다.

SELECT 
    t.id,
    tag_name
FROM 
    tb_user_log t
    CROSS JOIN UNNEST(
        CAST(
            JSON_EXTRACT(CAST(FROM_BASE64(t.log_blob) AS VARCHAR), '$.tags') 
            AS ARRAY(VARCHAR)
        )
    ) AS t(tag_name) -- t(컬럼명) 형태로 별칭 지정
WHERE t.id = 101;

💡 개념 정리: UNNEST vs FLATTEN, 도대체 뭐가 다른가요?

SQL을 짜다 보면 어디는 UNNEST를 쓰고, 어디는 FLATTEN을 써서 헷갈리실 겁니다. 결론부터 말하면 "목적은 같지만, 제공하는 정보의 깊이가 다르다"고 볼 수 있습니다.

1. UNNEST (PostgreSQL, BigQuery, Athena)

  • 의미: "둥지(Nest)에서 꺼낸다"는 뜻입니다.
  • 동작: 배열 ['A', 'B']를 풀면 딱 값인 A, B만 뱉어냅니다.
  • 특징: 심플합니다. 값만 필요할 때 씁니다. 만약 배열의 순서(Index)가 필요하면 WITH OFFSET 같은 추가 문법을 써야 합니다.

2. FLATTEN (Snowflake)

  • 의미: "납작하게 편다"는 뜻입니다.
  • 동작: Snowflake의 FLATTEN은 단순히 값만 뱉는 게 아니라, 하나의 테이블 뷰를 만들어줍니다.
  • 특징: FLATTEN을 실행하면 자동으로 다음 컬럼들을 제공합니다.
    • VALUE: 배열 안의 실제 값
    • INDEX: 배열의 순서 (0, 1, 2...)
    • KEY: (Object인 경우) 키 이름
    • PATH: 데이터의 경로
    • SEQ: 입력 레코드의 시퀀스
  • 요약: UNNEST보다 훨씬 수다스럽고 친절합니다. 별도의 함수 없이도 인덱스나 경로를 바로 알 수 있어 복잡한 JSON 분석에 더 유리합니다.

한 줄 요약:

  • Postgre/BigQuery/Athena: UNNEST로 푼다. (배열 → 행)
  • Snowflake: FLATTEN으로 푼다. (배열 → 행 + 메타데이터 풍부함)

마치며

Base64로 감춰진 데이터를 SQL로 다루는 것은 '비상용 칼'과 같습니다. 매우 유용하지만 조심해서 써야 합니다.

  1. 성능 이슈: 위 쿼리들은 인덱스를 전혀 타지 못합니다. 디코딩과 파싱 비용이 비싸기 때문에, 수십만 건 전체 조회(SELECT *)는 DB CPU를 폭발시킬 수 있습니다.
  2. 백업 필수: UPDATE 쿼리를 날리기 전에, 반드시 SELECT 문으로 변경될 JSON 모양을 미리 확인(Preview)하고 실행하는 습관을 들여야 합니다.
728x90
반응형

'DB > SQL' 카테고리의 다른 글

[GA활용] 전환율 구하기 기본  (0) 2025.02.17
[Concat] 문자열 다중 조합  (0) 2025.02.17
[Aggregation] 일간/주간 집계  (0) 2025.02.14
[Cohort-Pivot] 2개월 차에도 구매한 고객 찾기  (0) 2025.02.11
[SubQuery] 다양한 서브쿼리  (0) 2025.02.05
'DB/SQL' 카테고리의 다른 글
  • [GA활용] 전환율 구하기 기본
  • [Concat] 문자열 다중 조합
  • [Aggregation] 일간/주간 집계
  • [Cohort-Pivot] 2개월 차에도 구매한 고객 찾기
M.PS
M.PS
  • M.PS
    전직 퀘스트 필기노트
    M.PS
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • With AI (1)
      • About Statistics (8)
      • About Analytics (23)
        • Preprocessing (2)
        • Machine Learning (4)
        • NLP (1)
        • Metrics (0)
        • Time Series (3)
        • Visulalization (1)
        • Analytics Method (6)
        • GA4 (2)
        • Growth Hacking (1)
      • About Dev(Python) (14)
        • Pandas (3)
        • Scipy & Numpy (0)
        • Python comprehension (8)
      • DB (18)
        • DB Design& Construction (4)
        • SQL (13)
      • Linux (0)
      • IT Info for NonMajor (12)
        • K-ICT Online Academy (1)
        • SI Project Tip (1)
      • Side Study (2)
        • Datarian SQL (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    머신러닝
    snowflake
    sql
    json_format
    추천시스템
    unnest
    데이터리안
    데이터분석
    jupyter lab
    데이터마이닝
    데이터리안 분석 캠프
    bigquery
    sql 분석 캠프
    실전반
    Flatten
    GIT
    A/B테스트
    통계분석
    확률과통계
    PostgreSQL
  • 최근 댓글

  • 최근 글

  • 반응형
    250x250
  • hELLO· Designed By정상우.v4.10.3
M.PS
[JSON Parse] JSON에 들어가있는 데이터 사용하기
상단으로

티스토리툴바