반응형
    
    
    
  서론
자신이 작업한 코드를 포함한 모든 결과물은 Legacy가 된다.
다만, 개선하려는 시점의 주제가 무엇이냐에 따라 알고리즘 개선이 될지 비즈니스 로직 개선이 될지 결정될 수 있다.
실무를 수행하면서 2년 전 mybatis로 개발된 SQL에서 시스템 고도화에 따라 사용할 수 없거나 불필요한 쿼리가 발견되어 최적화를 위해 해결하던 도중 간단한 이론에 대해 정리해본다.
이론적 배경
- SQL 쿼리는 어떻게 동작하는가?
- 1) FROM : 어느 테이블에서 찾을 것인가?
- 2) JOIN : 어떤 테이블을 연관지을 것인가?
- 3) ON : 연관 조건은 어떤 것인가?
- 4) WHERE : 어떤 필터를 적용할 것인가?
- 5) GROUP BY : 값을 기준으로 그룹화를 수행할 것인가?
- 6) HAVING : WHERE과 유사하지만 그룹에 대한 필터를 적용할 것인가?
- 7) SELECT : 어떤 정보를 호출할 것인가?
- 8) ORDER BY : 어떤 순서로 호출할 것인가?
- 9) LIMIT : 어디까지 호출할 것인가?
 

- 서브쿼리는 무엇인가?
- 위에서 언급한 SQL 쿼리가 한 개의 SELECT 문 내에 여러개 존재할 수 있는 부분을 의미한다.
- 실제 오래된 한방 쿼리의 경우, 분석 단계에서 서브쿼리가 다수 엮여있어 난해한 부분을 맞이할 수 있다.
 

준비
- 해당 쿼리에 대한 비즈니스 로직에 대한 이해가 충분한가?
- 무작정 쿼리를 분석하는 습관은 좋지 않을 수 있다.
- 해당 Query가 왜 구성되었는지 먼저 파악하는 것이 중요하며, 이는 겉으로는 불필요한 Logic이 반영되어 있다고 하더라도 비즈니스 관점에서 필요한 내용이 적용되었을 가능성이 있다.
- 예를 들면, 3년 전 초기 서비스의 인증일자와 현재 인증일자를 관리하는 Table 및 Column이 다른 경우에 동일 목적을 가지는 데이터를 조회하고 활용하기 위해 서브쿼리를 활용할 수 있다.
 
- 작성된 쿼리에 대해서 분석을 완료하였는가?
- 1차원적으로는 위에서 언급한 SQL이 어떤 식으로 동작하는지 파악하는 것이다.
- 단일 쿼리의 경우 순서가 명확하게 보일 수 있어 분석하기 쉽지만, 서브쿼리가 4개 이상 구성되어 있는 복합 쿼리일 경우에는 무엇을 먼저 분석해야되는지 헷갈릴 경우가 있다.
 
간단한 예제
- MySQL SQL
 - 실무에서 활용되는 코드는 변수를 활용하여 조건을 추가할 수 있기에 명세가 조금 다르다.
- 하지만, 임의 값으로 동일한 SQL을 구성하여 테스트 시 동작에 대한 검증을 수행할 수 있다.
 
SELECT
    main.order_id,
    main.customer_id,
    main.order_date,
    main.customer_name,
    main.shipping_address,
    main.payment_method,
    main.total_order_value,
    main.total_items,
    main.earliest_delivery,
    main.delivery_duration,
    main.latest_delivery
FROM
    (
        SELECT
            o.order_id,
            o.customer_id,
            o.order_date,
            c.customer_name,
            s.shipping_address,
            pm.payment_method,
            (
                SELECT SUM(quantity * unit_price)
                FROM order_items
                WHERE order_id = o.order_id
            ) AS total_order_value,
            (
                SELECT COUNT(*)
                FROM order_items
                WHERE order_id = o.order_id
            ) AS total_items,
            (
                SELECT MIN(delivery_date)
                FROM shipments
                WHERE order_id = o.order_id
            ) AS earliest_delivery,
            (
                SELECT DATEDIFF(MIN(delivery_date), o.order_date)
                FROM shipments
                WHERE order_id = o.order_id
            ) AS delivery_duration,
            (
                SELECT MAX(delivery_date)
                FROM shipments
                WHERE order_id = o.order_id
            ) AS latest_delivery
        FROM
            orders o
        LEFT JOIN customers c ON o.customer_id = c.customer_id
        LEFT JOIN shipments s ON o.order_id = s.order_id
        LEFT JOIN payment_methods pm ON o.payment_method_id = pm.payment_method_id
        LEFT JOIN order_status os ON o.order_status_id = os.order_status_id
    ) AS main
WHERE
    main.order_date >= '2024-01-01'
    AND main.total_order_value > 1000
    AND main.total_items > 5
ORDER BY
    main.order_date DESC;
- SQL Mybatis 적용
 - 실제 코드에서는 조건에 따라 유연하게 결과 값을 조절하기 때문에 Mybatis 등 SQL 관련 기능을 사용할 수 있다.
- 이에, 단순 코드를 가지고 분석하기에는 변수의 적용 범위가 즉시 보이지 않을 수 있어 해당 변수의 Scope를 확인해야 한다.
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.OrderMapper">
    <!-- 복잡한 주문 정보 조회 -->
    <select id="selectComplexOrderInfo" parameterType="map" resultType="map">
        SELECT
            main.order_id,
            main.customer_id,
            main.order_date,
            main.customer_name,
            main.shipping_address,
            main.payment_method,
            main.total_order_value,
            main.total_items,
            main.earliest_delivery,
            main.delivery_duration,
            main.latest_delivery
        FROM
            (
                SELECT
                    o.order_id,
                    o.customer_id,
                    o.order_date,
                    c.customer_name,
                    s.shipping_address,
                    pm.payment_method,
                    (
                        SELECT SUM(quantity * unit_price)
                        FROM order_items
                        WHERE order_id = o.order_id
                    ) AS total_order_value,
                    (
                        SELECT COUNT(*)
                        FROM order_items
                        WHERE order_id = o.order_id
                    ) AS total_items,
                    (
                        SELECT MIN(delivery_date)
                        FROM shipments
                        WHERE order_id = o.order_id
                    ) AS earliest_delivery,
                    (
                        SELECT DATEDIFF(MIN(delivery_date), o.order_date)
                        FROM shipments
                        WHERE order_id = o.order_id
                    ) AS delivery_duration,
                    (
                        SELECT MAX(delivery_date)
                        FROM shipments
                        WHERE order_id = o.order_id
                    ) AS latest_delivery
                FROM
                    orders o
                LEFT JOIN customers c ON o.customer_id = c.customer_id
                LEFT JOIN shipments s ON o.order_id = s.order_id
                LEFT JOIN payment_methods pm ON o.payment_method_id = pm.payment_method_id
                LEFT JOIN order_status os ON o.order_status_id = os.order_status_id
            ) AS main
        <where>
            <if test="orderDate != null">
                AND main.order_date >= #{orderDate}
            </if>
            <if test="minOrderValue != null">
                AND main.total_order_value > #{minOrderValue}
            </if>
            <if test="minItems != null">
                AND main.total_items > #{minItems}
            </if>
        </where>
        ORDER BY
            main.order_date DESC
    </select>
</mapper>결론
- Legacy라는 단어는 명확하게 기존 개발된 코드를 설명하는 최적 단어라고 생각한다.
- 경우에 따라, 훌륭한 유산이 될거나 아무도 건드리지 못하는 코드가 될 수 있다고 생각한다.
- 화이팅 하자.
참고 및 인용 출처
- ALEX XU. (2023.11.). Visualizing a SQL query. https://blog.bytebytego.com/p/ep50-visualizing-a-sql-query.
- Tcp School. (2018). 서브쿼리. https://www.tcpschool.com/mysql/mysql_multipleTable_subquery#google_vignette.
728x90
    
    
  반응형
    
    
    
  'Develop' 카테고리의 다른 글
| [AI] 프롬프트로 웹 테트리스 구현 (0) | 2024.05.28 | 
|---|---|
| 계좌이체 자동화 프로그램 (0) | 2024.05.26 | 
| [입금이체] 웹뷰 환경에서의 앱링크 미동작 이슈 (0) | 2024.05.25 | 
| [설계] 웹앱 서비스 계좌/입금이체 기능 추가의 건 (0) | 2024.05.22 | 
| [Python] Subprocess, 터미널 명령어 활용 방법 (1) | 2024.05.03 | 
| Python3 가상환경 만들기 (venv) (1) | 2022.05.09 | 
| 지도 검색 알고리즘 (0) | 2022.05.09 | 
| 커서 페이지네이션 구현 (2) | 2022.05.09 | 
 
                    
                   
                    
                   
                    
                  