CodeGym /행동 /SQL SELF /JSONB로 복잡한 쿼리 예시

JSONB로 복잡한 쿼리 예시

SQL SELF
레벨 34 , 레슨 2
사용 가능

이전 강의에서 우리는 JSONB의 기본: 생성, 수정, 데이터 추출 방법을 배웠어. 이제 진짜 실전이야 — 이 데이터 타입의 진짜 파워를 보여주는 복잡한 쿼리들을 해볼 차례지.

온라인 쇼핑몰의 상품 카탈로그를 상상해봐. 각 상품에는 기본 정보(이름, ID)가 있지만, 특성은 완전히 다를 수 있어: 노트북엔 RAM과 CPU, 옷에는 사이즈와 소재, 책에는 작가와 장르가 있지. 이걸 전부 별도 테이블에 저장한다고? 불편하지. JSONB에 저장하면? 완전 딱이야! 근데 특정 브랜드의 상품만 찾거나, 가격순으로 정렬하거나, 카테고리별 통계를 내고 싶으면 어떻게 해야 할까? 일반 컬럼이 아니라 JSON 구조 안에 숨어있는 데이터로 작업하려면?

오늘은 진짜 예시로 알아볼 거야: 간단한 필터링부터 그룹화, 집계까지 복합 쿼리를 다뤄볼 거야. JSONB가 PostgreSQL을 얼마나 유연하게 만들어주는지 직접 볼 수 있을 거야.

JSONB 데이터 필터링

필터링은 마치 차 거름망 같아: 필요한 것만 남기고 나머지는 버리는 거지. JSONB에서는 더 재밌어, 왜냐면 일반 컬럼뿐 아니라 JSON 구조 깊숙한 곳의 데이터도 필터링할 수 있으니까.

JSONB 필터링 연산자:

  • @> — "JSONB-포함". JSONB 객체가 지정한 서브셋을 포함하는지 확인해.
  • ? — "키 존재". JSONB 객체에 해당 키가 있는지 확인해.
  • ?| — "키 중 하나라도 존재". 지정한 키 중 하나라도 있으면 true야.
  • ?& — "모든 키 존재". 지정한 모든 키가 다 있으면 true야.

예시: 키와 값으로 필터링하기. 예를 들어, products 테이블에 details라는 JSONB 컬럼이 있고, 여기에 상품 정보를 저장한다고 해보자:

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    details JSONB
);

데이터 예시:

INSERT INTO products (name, details) VALUES
('Laptop', '{"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}'),
('Smartphone', '{"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}'),
('Tablet', '{"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}');

결과:

id name details
1 Laptop {"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}
3 Tablet {"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}

brand가 "Apple"인 모든 상품을 찾으려면:

SELECT *
FROM products 
WHERE details @> '{"brand": "Apple"}';

결과:

id name details
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}

모든 상품 중 specs라는 키가 있는 걸 찾으려면 ? 연산자를 써봐:

SELECT *
FROM products 
WHERE details ? 'specs';

결과:

id name details
1 Laptop {"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}
3 Tablet {"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}

모든 행에 details 필드와 specs 키가 들어있지.

JSONB 데이터 정렬

가끔은 일반 컬럼이 아니라 JSONB 안에 있는 값으로 정렬하고 싶을 때가 있어. 이럴 땐 ->> (텍스트 값 추출) 연산자랑 CAST로 타입 변환을 써주면 돼.

예시: 상품을 가격순으로 정렬해보자:

SELECT *
FROM products 
ORDER BY (details->>'price')::INTEGER;

결과:

id name details
3 Tablet {"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}
1 Laptop {"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}

JSONB 데이터 그룹화

그룹화는 데이터를 집계해서 통계를 낼 때 써. 예를 들어, 브랜드별로 상품 개수를 알고 싶을 때 말이야.

예시: 브랜드별 상품 개수 세기:

SELECT
    details->>'brand' AS brand,
    COUNT(*) AS product_count
FROM products
GROUP BY details->>'brand';

결과:

brand product_count
Dell 1
Apple 1
Samsung 1

실전 예시

필터링과 그룹화. 브랜드별로 가격이 600보다 비싼 상품 개수를 세어보자:

SELECT
    details->>'brand' AS brand,
    COUNT(*) AS product_count
FROM products
WHERE (details->>'price')::INTEGER > 600
GROUP BY details->>'brand';

결과:

brand product_count
Dell 1
Apple 1

그룹화 후 정렬. 이제 브랜드별 상품 개수로 정렬해보자:

SELECT
    details->>'brand' AS brand,
    COUNT(*) AS product_count
FROM products
GROUP BY details->>'brand'
ORDER BY product_count DESC;

복합 쿼리: 필터링, 정렬, 그룹화

브랜드별로 600보다 비싼 상품이 있는 브랜드만 찾고, 각 브랜드에서 가장 저렴한 상품을 뽑고 싶다고 해보자. 이렇게 하면 돼:

WITH filtered_products AS (
    SELECT *
    FROM products
    WHERE (details->>'price')::INTEGER > 600
)
SELECT
    details->>'brand' AS brand,
    MIN((details->>'price')::INTEGER) AS min_price
FROM filtered_products
GROUP BY details->>'brand'
ORDER BY min_price;

결과:

brand min_price
Apple 800
Dell 1200

자주 하는 실수와 팁

실수: 연산자 잘못 사용하기. ->->> 헷갈리지 마: ->는 객체를, ->>는 텍스트 값을 반환해.

실수: 성능 문제. 복잡한 쿼리를 자주 한다면 JSONB 컬럼에 GIN 인덱스를 만들어줘.

실수: 타입 문제. JSONB에서 꺼낸 값은 문자열이니까 CAST로 타입 변환 꼭 해줘야 해.

인덱스 만드는 예시:

CREATE INDEX idx_products_details ON products USING GIN (details);

이제 details @> '{"brand": "Apple"}' 같은 필터링이 훨씬 빨라질 거야.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION