메뉴 닫기

MSSQL 사용자 정의 함수(UDF)로 반복 로직 최적화와 코드 가독성 향상

💾 MSSQL 사용자 정의 함수(UDF)로 반복 로직 최적화와 코드 가독성 향상

⚡ 재사용 가능한 SQL 코드로 개발 효율을 높이는 방법을 알아봅니다

데이터베이스 개발을 하다 보면 비슷한 계산식이나 조건문을 여러 쿼리에서 반복적으로 사용하게 되는 경우가 많습니다.
그럴 때마다 같은 코드를 복사해 붙여넣다 보면 유지보수가 어려워지고, 코드의 일관성도 깨질 수 있죠.
이 문제를 깔끔하게 해결해 주는 도구가 바로 MSSQL 사용자 정의 함수(UDF)입니다.
UDF를 활용하면 자주 사용하는 로직을 하나의 함수로 정의해 두고 필요할 때마다 호출할 수 있어, 개발 속도와 코드 품질을 동시에 높일 수 있습니다.
뿐만 아니라, 함수 형태로 작성된 로직은 수정이 필요할 때 한 곳만 변경하면 되어 유지보수 효율도 크게 향상됩니다.

이번 글에서는 MSSQL 사용자 정의 함수의 개념부터, 실제 작성 방법과 사용 예시, 그리고 효율적인 활용 팁까지 폭넓게 다뤄보겠습니다.
SQL 개발 경험이 많지 않은 분들도 쉽게 이해할 수 있도록 단계별로 설명하며, 실무에서 바로 적용할 수 있는 예제 코드도 함께 제공하니 끝까지 읽어보시면 분명 도움이 될 것입니다.



💡 사용자 정의 함수(UDF)란?

MSSQL에서 사용자 정의 함수(User Defined Function, UDF)는 반복해서 사용하는 계산식이나 로직을 함수로 캡슐화해 재사용성과 가독성을 높이기 위한 객체입니다.
SELECT, WHERE, JOIN, ORDER BY 등 다양한 구문에서 호출할 수 있어 중복된 표현을 줄이고 코드 일관성을 유지하는 데 유리합니다.
동일한 규칙을 여러 쿼리에서 공유해야 할 때 특히 효과적이며, 한 곳만 수정해도 전체 영향 범위에 변경 사항이 반영되므로 유지보수 비용을 크게 낮출 수 있습니다.

UDF는 입력 매개변수를 받아서 결과를 반환한다는 점에서 내장 함수와 동일한 사용감을 제공합니다.
차이는 로직을 우리가 직접 정의한다는 데 있습니다.
업무 규칙 검증, 포맷 일치, 복잡한 수식 계산, 날짜·시간 변환, 표준화된 스코어 산출처럼 반복 등장하는 도메인 규칙을 함수로 통일해 두면 개발자가 바뀌어도 로직 품질과 해석 가능성이 일정하게 유지됩니다.
또한 테스트가 쉬워 단위 테스트 기반의 안정적인 데이터 처리 파이프라인을 구성하는 기반이 됩니다.

  • 🔁중복 로직을 하나의 함수로 모아 재사용성을 높입니다.
  • 🧩쿼리 본문이 간결해져 가독성과 리뷰 효율이 향상됩니다.
  • 🛡️단일 변경 지점으로 유지보수 리스크를 줄입니다.
CODE BLOCK
-- ✔️ 간단한 스칼라 UDF 예시: 세전 금액과 세율로 세후 금액 계산
CREATE OR ALTER FUNCTION dbo.fn_CalcNetAmount
(
    @gross  DECIMAL(18,2),
    @taxPct DECIMAL(5,2)  -- 예: 10.0 = 10%
)
RETURNS DECIMAL(18,2)
AS
BEGIN
    DECLARE @net DECIMAL(18,2);
    SET @net = @gross * (1 - (@taxPct/100.0));
    RETURN @net;
END;
GO

-- 사용 예시
SELECT dbo.fn_CalcNetAmount(100000, 10.0) AS NetAmount;

💎 핵심 포인트:
반복되는 계산이나 로직을 함수로 정의하면 여러 쿼리에서 일관되게 재사용할 수 있습니다.
수정이 필요할 때 함수만 고치면 되어 코드 가독성과 유지보수성이 함께 향상됩니다.

⚠️ 주의: UDF는 결과를 반환하는 읽기 전용 로직에 적합합니다.
트랜잭션 제어, 임시 테이블 사용 등 일부 작업은 제한될 수 있으므로 데이터 변경(INSERT, UPDATE, DELETE) 로직은 저장 프로시저로 분리하는 것이 안전합니다.

🛠️ UDF의 주요 종류와 특징

MSSQL에서 사용자 정의 함수(UDF)는 크게 스칼라 함수(Scalar Function)테이블 반환 함수(Table-Valued Function, TVF) 두 가지로 나눌 수 있습니다.
각 함수 유형은 반환값 형태와 사용 방식에 따라 적합한 용도가 달라집니다.
함수 종류를 이해하고 상황에 맞춰 선택하면 성능 저하를 줄이고, 더 직관적인 SQL 코드를 작성할 수 있습니다.

🔹 스칼라 함수(Scalar Function)

스칼라 함수는 단일 값을 반환합니다.
날짜 계산, 문자열 포맷팅, 특정 수식 처리처럼 하나의 결과값만 필요한 경우에 사용합니다.
WHERE, SELECT 절 등 다양한 위치에서 활용 가능하지만, 대량 데이터 처리 시 성능에 영향을 줄 수 있어 주의가 필요합니다.

📊 테이블 반환 함수(Table-Valued Function)

테이블 반환 함수는 하나 이상의 행과 열을 결과로 반환합니다.
주로 필터링된 데이터 세트, 조인 대상 임시 테이블, 복잡한 집계 결과를 반환할 때 활용됩니다.
인라인(Inline)과 멀티 스테이트먼트(Multi-statement) 형태로 구분되며, 인라인 방식이 성능상 유리합니다.

함수 종류 특징
스칼라 함수 단일 값 반환, 다양한 절에서 활용 가능
테이블 반환 함수 다수의 행과 열 반환, 인라인/멀티 방식 존재

💡 TIP: 데이터 범위가 크고 성능이 중요한 경우 인라인 테이블 반환 함수를 고려하세요.
실행 계획이 최적화되어 성능 저하를 줄일 수 있습니다.

💬 UDF의 선택은 데이터 규모와 사용 목적에 따라 달라져야 합니다.
단순 계산에는 스칼라 함수를, 복잡한 데이터 반환에는 테이블 반환 함수를 사용하는 것이 일반적입니다.



⚙️ UDF 작성 방법과 기본 문법

UDF는 입력 매개변수와 반환 형식을 명확히 정의하고, 읽기 전용 로직을 본문에 기술하는 구조로 작성합니다.
함수는 데이터베이스 스키마(예: dbo)에 속하며, 이름 충돌을 피하고 의미를 드러내는 규칙적인 네이밍이 중요합니다.
또한 테스트 가능한 작은 단위로 쪼개 두면 재사용성이 높아지고 변경 영향 범위도 예측하기 쉬워집니다.
아래는 스칼라 UDF, 인라인 TVF, 멀티 스테이트먼트 TVF의 기본 문법과 사용 예시입니다.

✏️ 스칼라 UDF 기본 문법

스칼라 함수는 스칼라(단일) 값을 반환합니다.
입력값 검증, 문자열 포맷, 날짜 계산처럼 결과가 하나인 연산을 함수로 감싸면 쿼리가 간결해집니다.

CODE BLOCK
-- 스칼라 UDF: 전화번호에서 숫자만 남기는 정규화 예시
CREATE OR ALTER FUNCTION dbo.fn_NormalizePhone (@raw NVARCHAR(50))
RETURNS NVARCHAR(50)
AS
BEGIN
    DECLARE @clean NVARCHAR(50) = N'';
    DECLARE @i INT = 1;
    WHILE @i <= LEN(@raw)
    BEGIN
        IF SUBSTRING(@raw, @i, 1) LIKE N'[0-9]'
            SET @clean += SUBSTRING(@raw, @i, 1);
        SET @i += 1;
    END
    RETURN @clean;
END;
GO

-- 사용 예: WHERE, SELECT 등 다양한 절에서 호출 가능
SELECT dbo.fn_NormalizePhone(N'010-1234-5678') AS CleanPhone;

📎 인라인 테이블 반환 함수(Inline TVF)

인라인 TVF는 RETURNS TABLE AS RETURN (SELECT …) 형식으로, 하나의 SELECT 결과를 그대로 테이블 형태로 반환합니다.
최적화기와 잘 통합되어 실행 계획이 유리한 편이므로, 성능이 중요한 경우 우선 고려합니다.

CODE BLOCK
-- 인라인 TVF: 특정 기간의 활성 고객만 반환
CREATE OR ALTER FUNCTION dbo.fn_ActiveCustomers
(
    @from DATE,
    @to   DATE
)
RETURNS TABLE
AS
RETURN
(
    SELECT c.CustomerID, c.Name, c.Status, c.LastOrderDate
    FROM   dbo.Customers AS c
    WHERE  c.Status = 'Active'
      AND  c.LastOrderDate BETWEEN @from AND @to
);
GO

-- 사용 예: JOIN, APPLY로 연결
SELECT o.OrderID, c.Name
FROM dbo.Orders AS o
JOIN dbo.fn_ActiveCustomers('2025-01-01','2025-12-31') AS c
  ON o.CustomerID = c.CustomerID;

🧩 멀티 스테이트먼트 TVF(MSTVF)

멀티 스테이트먼트 TVF는 테이블 변수에 데이터를 누적해 반환합니다.
분기나 임시 계산이 필요한 복잡한 로직을 구현할 수 있지만, 인라인 TVF 대비 최적화 여지가 제한될 수 있습니다.

CODE BLOCK
-- MSTVF: 주문 합계와 등급을 계산해 반환
CREATE OR ALTER FUNCTION dbo.fn_OrderSummary (@customerId INT)
RETURNS @t TABLE
(
    CustomerID INT,
    OrderCount INT,
    TotalAmount DECIMAL(18,2),
    Grade NVARCHAR(10)
)
AS
BEGIN
    INSERT INTO @t (CustomerID, OrderCount, TotalAmount, Grade)
    SELECT 
        @customerId,
        COUNT(*),
        SUM(o.Amount),
        CASE 
            WHEN SUM(o.Amount) >= 1000000 THEN N'VIP'
            WHEN SUM(o.Amount) >= 300000  THEN N'Gold'
            ELSE N'Silver'
        END
    FROM dbo.Orders AS o
    WHERE o.CustomerID = @customerId;

    RETURN;
END;
GO

-- 사용 예: APPLY로 행별 확장
SELECT c.Name, s.OrderCount, s.TotalAmount, s.Grade
FROM dbo.Customers AS c
CROSS APPLY dbo.fn_OrderSummary(c.CustomerID) AS s;

  • 🧾함수 이름과 스키마를 먼저 정합니다.
  • 🧮입력 매개변수반환 형식(스칼라/테이블)을 정의합니다.
  • 🔍로직은 읽기 전용으로 구성하고, 외부 상태 의존을 최소화합니다.
  • 🧪단위 테스트용 샘플 케이스를 준비해 경계값을 검증합니다.

💎 핵심 포인트:
간단한 결과 셋은 인라인 TVF로, 단일 값 계산은 스칼라 UDF로, 분기와 누적 처리가 많다면 MSTVF로 구현하면 구조가 명확해집니다.

⚠️ 주의: 멀티 스테이트먼트 TVF는 테이블 변수를 사용하므로 대량 처리 시 최적화가 제한될 수 있습니다.
가능하면 인라인 TVF를 우선 검토하고, 스칼라 UDF는 대량 행에 대한 행별 호출을 최소화하도록 쿼리 구조를 설계하세요.

💬 함수는 작게, 목적은 명확하게.
이름은 행위 중심으로 짓고(예: fn_GetX, fn_CalcY), 입력과 출력 계약을 문서화하면 협업과 유지보수가 수월해집니다.

📂 실무에서 자주 쓰이는 UDF 예시

실제 데이터베이스 개발 환경에서는 비즈니스 규칙, 데이터 변환, 포맷 표준화를 위해 UDF를 폭넓게 활용합니다.
아래 예시들은 현업에서 자주 쓰이는 패턴으로, 바로 복사해 적용할 수 있는 코드입니다.
각 함수는 특정 목적에 맞게 작성되었으며, 다양한 쿼리 구문에서 호출 가능하다는 장점이 있습니다.

📅 날짜 포맷 변환 함수

YYYYMMDD 형태의 문자열을 YYYY-MM-DD 형식으로 변환하는 스칼라 함수입니다.
데이터 마이그레이션이나 보고서 생성 시 유용하게 활용됩니다.

CODE BLOCK
CREATE OR ALTER FUNCTION dbo.fn_FormatDate (@yyyymmdd CHAR(8))
RETURNS CHAR(10)
AS
BEGIN
    RETURN LEFT(@yyyymmdd,4) + '-' + SUBSTRING(@yyyymmdd,5,2) + '-' + RIGHT(@yyyymmdd,2);
END;
GO

SELECT dbo.fn_FormatDate('20250811'); -- 결과: 2025-08-11

💰 세금 계산 함수

세전 금액과 세율을 받아 세후 금액을 반환하는 함수입니다.
회계, 결제 시스템, 매출 분석 등에 활용할 수 있습니다.

CODE BLOCK
CREATE OR ALTER FUNCTION dbo.fn_CalcNetAmount
(
    @gross DECIMAL(18,2),
    @taxPct DECIMAL(5,2)
)
RETURNS DECIMAL(18,2)
AS
BEGIN
    RETURN @gross * (1 - (@taxPct/100.0));
END;
GO

SELECT dbo.fn_CalcNetAmount(100000, 10); -- 결과: 90000.00

🔍 이메일 도메인 추출 함수

사용자의 이메일 주소에서 도메인 부분만 추출하는 함수입니다.
마케팅, 통계 분석, 사용자 그룹 분류 작업에 유용합니다.

CODE BLOCK
CREATE OR ALTER FUNCTION dbo.fn_GetEmailDomain (@email NVARCHAR(320))
RETURNS NVARCHAR(255)
AS
BEGIN
    RETURN RIGHT(@email, LEN(@email) - CHARINDEX('@', @email));
END;
GO

SELECT dbo.fn_GetEmailDomain('user@example.com'); -- 결과: example.com

  • 📌자주 쓰이는 계산식과 데이터 변환 로직을 UDF로 표준화합니다.
  • 📌입력값 검증 로직을 포함해 에러 발생 가능성을 줄입니다.
  • 📌함수 호출 시 성능 영향을 고려해 대량 처리는 주의합니다.

💎 핵심 포인트:
실무에서는 표준화된 함수를 통해 데이터 품질을 보장하고, 개발 속도와 유지보수 효율을 동시에 높일 수 있습니다.

⚠️ 주의: 복잡한 계산 로직이 포함된 UDF는 실행 계획을 확인해 성능을 점검하고, 인덱스 활용 여부를 반드시 검토하세요.



🚀 UDF 성능 최적화와 주의사항

UDF는 반복 로직을 재사용하고 코드 가독성을 높여 주지만, 대량 데이터를 처리할 때는 호출 방식과 작성 스타일에 따라 성능 차이가 크게 발생할 수 있습니다.
특히 스칼라 UDF를 행마다 호출하는 패턴은 CPU 사용량을 높이고 최적화기를 제한하기 쉽습니다.
반대로 인라인 TVF는 최적화기에 의해 다른 쿼리와 함께 계획이 재작성되므로 집합 기반 처리에 유리합니다.
아래의 원칙과 예시를 참고해, 읽기 쉬우면서도 빠른 함수를 설계해 보세요.

⚡ 집합(Set)-기반으로 재작성하기

가능하다면 스칼라 UDF를 대량 행에 직접 적용하지 말고, 인라인 TVF 또는 CROSS APPLY를 활용해 집합 기반으로 바꾸는 것이 좋습니다.
이렇게 하면 쿼리 최적화기가 조인, 필터, 인덱스 사용을 통합적으로 고려할 수 있습니다.

CODE BLOCK
-- ❌ 행별 스칼라 UDF 호출(비권장: 대량 데이터에서 느릴 수 있음)
SELECT o.OrderID,
       dbo.fn_CalcNetAmount(o.Amount, 10.0) AS NetAmount
FROM dbo.Orders AS o;

-- ✅ 집합 기반: 인라인 TVF + JOIN / APPLY
CREATE OR ALTER FUNCTION dbo.fn_OrderWithNetAmount
(
    @taxPct DECIMAL(5,2)
)
RETURNS TABLE
AS
RETURN
(
    SELECT o.OrderID,
           o.Amount,
           o.Amount * (1 - (@taxPct/100.0)) AS NetAmount
    FROM dbo.Orders AS o
);
GO

SELECT t.OrderID, t.NetAmount
FROM dbo.fn_OrderWithNetAmount(10.0) AS t;

🧠 최적화기와 실행 계획을 고려하기

인라인 TVF는 호출 쿼리의 일부처럼 계획이 합쳐져 인덱스 탐색, 필터 푸시다운 등 최적화 혜택을 받습니다.
반면 멀티 스테이트먼트 TVF는 테이블 변수를 반환하므로 카디널리티 추정이 제한되어 과소/과대 추정이 발생할 수 있습니다.
필요하다면 MSTVF 내부 로직을 단순화하거나, 중간 결과를 임시 테이블로 물리화하여 인덱스를 보완하는 전략을 고려하세요.

전략 권장 이유
인라인 TVF 우선 계획 병합으로 인덱스 활용, 필터 푸시다운 가능
MSTVF 최소화 테이블 변수 기반의 추정 한계 회피

🔒 SCHEMABINDING과 결정성(Determinism)

스칼라/TVF 모두 WITH SCHEMABINDING을 지정하면 종속 개체의 스키마 변경으로부터 함수를 보호할 수 있고, 퍼시스티드 계산 열이나 인덱싱에 필요한 전제(결정성 확보)에 도움이 됩니다.
비결정적 함수(GETDATE, NEWID 등)를 피하고 입력 매개변수만으로 결과가 결정되도록 작성하세요.

CODE BLOCK
-- 스키마 바인딩 예시(결정적 로직 권장)
CREATE OR ALTER FUNCTION dbo.fn_CalcDiscount
(
    @amount DECIMAL(18,2),
    @tier   TINYINT
)
RETURNS DECIMAL(18,2)
WITH SCHEMABINDING
AS
BEGIN
    RETURN CASE @tier
             WHEN 3 THEN @amount * 0.90
             WHEN 2 THEN @amount * 0.95
             ELSE @amount
           END;
END;
GO

🧪 입력 검증과 예외 처리 패턴

함수는 예외를 던지기 어렵기 때문에, NULL/경계값 처리와 잘못된 입력 방어 로직을 명시적으로 구현하세요.
가능하면 명확한 기본값을 반환하거나 의미 있는 NULL을 반환해 상위 쿼리에서 처리할 수 있게 합니다.

  • 📏대량 처리에는 인라인 TVF를 우선 고려합니다.
  • 🧮스칼라 UDF는 행별 호출 최소화 후 APPLY/조인으로 대체합니다.
  • 🧱WITH SCHEMABINDING을 검토하고 비결정적 함수를 피합니다.
  • 🔍실행 계획과 통계를 점검하며 인덱스를 함께 설계합니다.
  • 🧪NULL, 경계값, 비정상 입력에 대한 테스트 케이스를 갖춥니다.

⚠️ 주의: 멀티 스테이트먼트 TVF는 규모가 커질수록 성능 편차가 커질 수 있습니다.
필요 시 로직을 인라인 TVF로 단순화하거나, 중간 결과를 임시 테이블로 물리화하고 적절한 인덱스를 부여해 병목을 완화하세요.

💎 핵심 포인트:
가독성을 높이는 리팩터링과 성능 최적화는 양립할 수 있습니다.
행별 호출을 줄이고 집합 기반으로 바꾸며, 스키마 바인딩과 결정성을 확보하면 UDF는 빠르고 안전한 재사용 컴포넌트가 됩니다.

자주 묻는 질문 (FAQ)

MSSQL UDF는 어떤 상황에서 가장 유용하게 쓰이나요?
반복되는 계산, 공통 로직, 복잡한 조건문을 여러 쿼리에서 재사용해야 할 때 특히 효과적입니다. 유지보수성을 높이고 코드 중복을 줄일 수 있습니다.
스칼라 UDF와 테이블 값 반환 함수의 차이는 무엇인가요?
스칼라 UDF는 단일 값만 반환하고, 테이블 값 반환 함수(TVF)는 결과 집합을 반환합니다. TVF는 인라인과 멀티 스테이트먼트 방식으로 나뉘며 최적화 방식에 차이가 있습니다.
UDF 사용 시 성능 저하를 피하려면 어떻게 해야 하나요?
대량 데이터에서는 스칼라 UDF 대신 인라인 TVF를 활용하고, 집합 기반 쿼리로 변환하는 것이 좋습니다. 실행 계획과 인덱스 전략을 함께 점검하세요.
WITH SCHEMABINDING 옵션은 왜 쓰는 건가요?
함수가 참조하는 개체의 스키마 변경을 막고, 결정성을 확보하여 퍼시스티드 계산 열이나 인덱싱에 활용할 수 있게 합니다.
멀티 스테이트먼트 TVF의 단점은 무엇인가요?
테이블 변수를 반환하기 때문에 카디널리티 추정이 제한되고, 최적화기가 인덱스와 조인 전략을 효과적으로 적용하기 어려울 수 있습니다.
UDF에서 예외 처리를 어떻게 구현하나요?
TRY…CATCH 구문을 직접 사용할 수 없으므로, NULL 처리나 기본값 반환, 입력값 검증을 통한 방어적 코딩이 필요합니다.
UDF를 사용하면 인덱스를 못 쓰게 되나요?
스칼라 UDF는 인덱스 시크를 방해할 수 있지만, 인라인 TVF는 최적화기에 의해 인덱스를 정상적으로 활용할 수 있습니다.
성능 테스트는 어떻게 하는 것이 좋을까요?
실행 계획 비교, SET STATISTICS IO/TIME 옵션을 통한 리소스 사용량 분석, 실제 운영 환경과 유사한 데이터 볼륨 테스트를 병행하는 것이 좋습니다.

🧾 UDF로 완성하는 재사용 가능한 SQL 설계

MSSQL 사용자 정의 함수(UDF)는 반복되는 계산과 로직을 표준화해 여러 쿼리에서 재사용하도록 돕는 핵심 도구입니다.
스칼라 UDF는 단일 값을, 테이블 반환 함수는 결과 집합을 반환해 용도에 맞는 선택이 가능합니다.
대량 처리 상황에서는 인라인 TVF를 우선 적용해 최적화기와의 통합 이점을 활용하세요.
멀티 스테이트먼트 TVF는 분기 많은 복잡 로직에 유용하지만 추정 한계가 있어 신중한 사용이 필요합니다.
WITH SCHEMABINDING과 결정성을 확보하면 퍼시스티드 계산 열과 인덱싱에도 유리합니다.
입력값 검증과 NULL 처리 규칙을 명확히 정의해 예외 상황을 방어하고, 실행 계획과 통계를 통해 성능을 꾸준히 점검하세요.
결국 UDF는 가독성과 유지보수성, 그리고 성능을 함께 잡는 실무형 SQL 설계의 토대가 됩니다.


🏷️ 관련 태그 : MSSQL, UDF, 사용자정의함수, 스칼라함수, 테이블값함수, 인라인TVF, 멀티스테이트먼트TVF, SQL성능최적화, SCHEMABINDING, SQL서버