DB/MSSQL

MS SQL Server의 고유 함수 완벽 가이드

shaprimanDev 2025. 5. 24. 21:59
반응형

 

Microsoft SQL Server는 Windows 환경에 최적화된 강력한 기업용 데이터베이스 시스템으로, .NET 생태계와의 완벽한 통합을 통해 독특하고 강력한 함수들을 제공합니다. 이러한 함수들은 비즈니스 애플리케이션 개발에서 복잡한 로직을 간단하게 처리하고, 개발 생산성을 크게 향상시킵니다. 엔터프라이즈 환경에서 자주 사용되는 MS SQL Server만의 독특한 함수들을 실제 사용 예제와 함께 상세히 살펴보겠습니다.

1. NEWID()와 NEWSEQUENTIALID() - 기업용 고유 식별자 생성

MS SQL Server의 GUID 생성 함수들은 분산 시스템과 엔터프라이즈 환경에서 데이터 무결성을 보장하는 핵심 도구입니다.

NEWID() - 무작위 GUID 생성

NEWID()는 RFC 4122 표준을 준수하는 완전히 무작위적인 GUID를 생성합니다.

SELECT NEWID() AS random_guid;
-- 결과 예: 6F9619FF-8B86-D011-B42D-00C04FC964FF

-- 데이터 타입: uniqueidentifier (16바이트)
DECLARE @guid uniqueidentifier = NEWID();
SELECT @guid AS my_guid;

NEWSEQUENTIALID() - 순차적 GUID 생성

NEWSEQUENTIALID()는 인덱스 성능을 고려한 순차적 GUID를 생성하여 페이지 분할을 최소화합니다.

-- 테이블 생성 시에만 사용 가능 (DEFAULT 제약 조건)
CREATE TABLE Orders (
    OrderID uniqueidentifier DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
    CustomerID int,
    OrderDate datetime2 DEFAULT GETDATE(),
    TotalAmount decimal(10,2)
);

-- 데이터 삽입 (OrderID는 자동 생성)
INSERT INTO Orders (CustomerID, TotalAmount)
VALUES (1001, 50000.00), (1002, 75000.00);

-- 결과 확인
SELECT OrderID, CustomerID, OrderDate, TotalAmount
FROM Orders
ORDER BY OrderDate;

실무 활용 예제

예제 1: 문서 관리 시스템

-- 문서 버전 관리 테이블
CREATE TABLE DocumentVersions (
    VersionID uniqueidentifier DEFAULT NEWID() PRIMARY KEY,
    DocumentID int,
    VersionNumber int,
    Content nvarchar(max),
    CreatedBy int,
    CreatedDate datetime2 DEFAULT GETDATE(),
    FileSize bigint,
    CheckSum varchar(64)
);

-- 문서 버전 생성 프로시저
CREATE PROCEDURE CreateDocumentVersion
    @DocumentID int,
    @Content nvarchar(max),
    @CreatedBy int,
    @FileSize bigint
AS
BEGIN
    DECLARE @VersionID uniqueidentifier = NEWID();
    DECLARE @VersionNumber int;
    
    -- 다음 버전 번호 계산
    SELECT @VersionNumber = ISNULL(MAX(VersionNumber), 0) + 1
    FROM DocumentVersions
    WHERE DocumentID = @DocumentID;
    
    -- 새 버전 생성
    INSERT INTO DocumentVersions (
        VersionID, DocumentID, VersionNumber, 
        Content, CreatedBy, FileSize, CheckSum
    ) VALUES (
        @VersionID, @DocumentID, @VersionNumber,
        @Content, @CreatedBy, @FileSize, 
        CONVERT(varchar(64), HASHBYTES('SHA2_256', @Content), 2)
    );
    
    -- 생성된 버전 ID 반환
    SELECT @VersionID AS NewVersionID, @VersionNumber AS VersionNumber;
END;

2. IIF() - 간결한 조건부 로직

IIF() 함수는 SQL Server 2012부터 도입된 함수로, 복잡한 CASE 문을 간단한 삼항 연산자 형태로 표현할 수 있게 해줍니다.

기본 문법

IIF(boolean_expression, true_value, false_value)

실무 활용 예제

예제 1: 직원 성과 평가 시스템

-- 직원 성과 데이터 조회
SELECT 
    EmployeeID,
    EmployeeName,
    SalesAmount,
    SalesTarget,
    -- 목표 달성 여부
    IIF(SalesAmount >= SalesTarget, '목표 달성', '목표 미달성') AS Achievement,
    -- 성과 등급
    IIF(SalesAmount >= SalesTarget * 1.2, 'A', 
        IIF(SalesAmount >= SalesTarget, 'B', 'C')) AS Grade,
    -- 보너스 계산
    IIF(SalesAmount >= SalesTarget, 
        SalesAmount * 0.1, 
        0) AS BonusAmount,
    -- 상태 표시
    IIF(IsActive = 1, '재직', '퇴사') AS EmploymentStatus
FROM Employees
WHERE DepartmentID = 10;

예제 2: 재고 관리 시스템

-- 상품 재고 상태 분석
SELECT 
    ProductID,
    ProductName,
    CurrentStock,
    MinimumStock,
    MaximumStock,
    -- 재고 상태
    IIF(CurrentStock <= MinimumStock, '부족', 
        IIF(CurrentStock >= MaximumStock, '과다', '적정')) AS StockStatus,
    -- 주문 필요 여부
    IIF(CurrentStock <= MinimumStock, '주문 필요', '주문 불필요') AS OrderNeeded,
    -- 권장 주문 수량
    IIF(CurrentStock <= MinimumStock, 
        MaximumStock - CurrentStock, 
        0) AS RecommendedOrderQty,
    -- 재고 회전율 등급
    IIF(TurnoverRate >= 12, '높음',
        IIF(TurnoverRate >= 6, '보통', '낮음')) AS TurnoverGrade
FROM Products
WHERE CategoryID IN (1, 2, 3);

3. CHOOSE() - 인덱스 기반 값 선택

CHOOSE() 함수는 인덱스 번호에 따라 값 목록에서 해당 위치의 값을 선택하는 독특한 함수입니다.

기본 문법

CHOOSE(index, value1, value2, value3, ...)
-- index는 1부터 시작

실무 활용 예제

예제 1: 다국어 지원 시스템

-- 다국어 메시지 처리
CREATE FUNCTION GetLocalizedMessage(
    @MessageCode int,
    @LanguageCode int  -- 1: 한국어, 2: 영어, 3: 일본어, 4: 중국어
)
RETURNS nvarchar(200)
AS
BEGIN
    DECLARE @Message nvarchar(200);
    
    SET @Message = CASE @MessageCode
        WHEN 1001 THEN CHOOSE(@LanguageCode, '로그인 성공', 'Login Success', 'ログイン成功', '登录成功')
        WHEN 1002 THEN CHOOSE(@LanguageCode, '로그인 실패', 'Login Failed', 'ログイン失敗', '登录失败')
        WHEN 1003 THEN CHOOSE(@LanguageCode, '권한이 없습니다', 'Access Denied', 'アクセス拒否', '权限不足')
        WHEN 1004 THEN CHOOSE(@LanguageCode, '데이터가 저장되었습니다', 'Data Saved', 'データが保存されました', '数据已保存')
        ELSE CHOOSE(@LanguageCode, '알 수 없는 오류', 'Unknown Error', '不明なエラー', '未知错误')
    END;
    
    RETURN @Message;
END;

-- 사용 예제
SELECT 
    dbo.GetLocalizedMessage(1001, 1) AS Korean,
    dbo.GetLocalizedMessage(1001, 2) AS English,
    dbo.GetLocalizedMessage(1001, 3) AS Japanese,
    dbo.GetLocalizedMessage(1001, 4) AS Chinese;

예제 2: 비즈니스 규칙 엔진

-- 고객 등급별 할인율 적용
SELECT 
    CustomerID,
    CustomerName,
    CustomerGrade,  -- 1: 브론즈, 2: 실버, 3: 골드, 4: 플래티넘, 5: 다이아몬드
    OrderAmount,
    -- 등급별 할인율
    CHOOSE(CustomerGrade, 0.02, 0.05, 0.08, 0.12, 0.15) AS DiscountRate,
    -- 등급별 배송비
    CHOOSE(CustomerGrade, 3000, 2000, 1000, 0, 0) AS ShippingFee,
    -- 등급별 적립률
    CHOOSE(CustomerGrade, 0.01, 0.015, 0.02, 0.025, 0.03) AS PointRate,
    -- 할인 적용 후 금액
    OrderAmount * (1 - CHOOSE(CustomerGrade, 0.02, 0.05, 0.08, 0.12, 0.15)) AS DiscountedAmount
FROM Orders o
JOIN Customers c ON o.CustomerID = c.CustomerID
WHERE o.OrderDate >= DATEADD(month, -1, GETDATE());

4. FORMAT() - .NET 스타일 데이터 포맷팅

FORMAT() 함수는 .NET Framework의 강력한 포맷팅 기능을 SQL Server에서 사용할 수 있게 해주는 독특한 함수입니다.

다양한 포맷팅 예제

예제 1: 비즈니스 리포트 생성

-- 월별 매출 리포트
SELECT 
    FORMAT(OrderDate, 'yyyy-MM') AS SalesMonth,
    FORMAT(OrderDate, 'MMMM yyyy', 'ko-KR') AS SalesMonthKorean,
    COUNT(*) AS OrderCount,
    -- 통화 포맷팅
    FORMAT(SUM(TotalAmount), 'C', 'ko-KR') AS TotalSalesKRW,
    FORMAT(SUM(TotalAmount), 'C', 'en-US') AS TotalSalesUSD,
    -- 숫자 포맷팅
    FORMAT(AVG(TotalAmount), 'N2', 'ko-KR') AS AverageOrderAmount,
    -- 퍼센트 포맷팅
    FORMAT(
        SUM(TotalAmount) * 1.0 / 
        (SELECT SUM(TotalAmount) FROM Orders WHERE YEAR(OrderDate) = YEAR(GETDATE())), 
        'P2', 'ko-KR'
    ) AS MonthlyContribution
FROM Orders
WHERE YEAR(OrderDate) = YEAR(GETDATE())
GROUP BY FORMAT(OrderDate, 'yyyy-MM'), FORMAT(OrderDate, 'MMMM yyyy', 'ko-KR')
ORDER BY SalesMonth;

예제 2: 고객 정보 포맷팅

-- 고객 연락처 및 정보 포맷팅
SELECT 
    CustomerID,
    CustomerName,
    -- 전화번호 포맷팅 (한국 형식)
    FORMAT(CAST(Phone AS bigint), '000-0000-0000') AS FormattedPhone,
    -- 생년월일 포맷팅
    FORMAT(BirthDate, 'yyyy년 MM월 dd일', 'ko-KR') AS BirthDateKorean,
    FORMAT(BirthDate, 'MMMM dd, yyyy', 'en-US') AS BirthDateEnglish,
    -- 나이 계산 및 포맷팅
    FORMAT(DATEDIFF(year, BirthDate, GETDATE()), 'N0') + '세' AS Age,
    -- 등록일 포맷팅
    FORMAT(RegisterDate, 'yyyy-MM-dd HH:mm:ss') AS RegisterDateTime,
    -- 고객 번호 포맷팅 (앞자리 0 추가)
    FORMAT(CustomerID, '000000') AS FormattedCustomerID
FROM Customers
WHERE IsActive = 1;

5. STRING_SPLIT()과 STRING_AGG() - 문자열 처리의 강력한 도구

SQL Server 2016부터 도입된 이 함수들은 문자열 데이터를 효율적으로 처리하는 현대적인 방법을 제공합니다.

STRING_SPLIT() 활용 예제

예제 1: 태그 기반 상품 검색

-- 태그 기반 상품 검색 시스템
CREATE PROCEDURE SearchProductsByTags
    @SearchTags nvarchar(500)  -- 예: 'electronics,smartphone,samsung'
AS
BEGIN
    -- 검색할 태그들을 테이블로 변환
    WITH SearchTagsTable AS (
        SELECT TRIM(value) AS TagName
        FROM STRING_SPLIT(@SearchTags, ',')
        WHERE TRIM(value) <> ''
    ),
    -- 태그별 상품 매칭
    ProductMatches AS (
        SELECT 
            p.ProductID,
            p.ProductName,
            p.Price,
            p.CategoryID,
            COUNT(st.TagName) AS MatchingTagCount
        FROM Products p
        JOIN ProductTags pt ON p.ProductID = pt.ProductID
        JOIN Tags t ON pt.TagID = t.TagID
        JOIN SearchTagsTable st ON t.TagName = st.TagName
        GROUP BY p.ProductID, p.ProductName, p.Price, p.CategoryID
    )
    -- 매칭된 태그 수에 따라 정렬하여 결과 반환
    SELECT 
        pm.ProductID,
        pm.ProductName,
        FORMAT(pm.Price, 'C', 'ko-KR') AS FormattedPrice,
        pm.MatchingTagCount,
        -- 해당 상품의 모든 태그
        STRING_AGG(t.TagName, ', ') AS AllTags
    FROM ProductMatches pm
    JOIN ProductTags pt ON pm.ProductID = pt.ProductID
    JOIN Tags t ON pt.TagID = t.TagID
    GROUP BY pm.ProductID, pm.ProductName, pm.Price, pm.MatchingTagCount
    ORDER BY pm.MatchingTagCount DESC, pm.ProductName;
END;

-- 사용 예제
EXEC SearchProductsByTags 'electronics,smartphone,samsung';

STRING_AGG() 활용 예제

예제 2: 조직 구조 리포트

-- 부서별 직원 현황 리포트
SELECT 
    d.DepartmentName,
    d.DepartmentCode,
    COUNT(e.EmployeeID) AS EmployeeCount,
    -- 직원 목록 (이름만)
    STRING_AGG(e.EmployeeName, ', ') AS EmployeeList,
    -- 직원 목록 (직급 포함)
    STRING_AGG(
        CONCAT(e.EmployeeName, '(', p.PositionName, ')'), 
        ' | '
    ) AS DetailedEmployeeList,
    -- 급여 통계
    FORMAT(AVG(e.Salary), 'C', 'ko-KR') AS AverageSalary,
    FORMAT(SUM(e.Salary), 'C', 'ko-KR') AS TotalSalary,
    -- 직급별 인원수
    STRING_AGG(
        CONCAT(p.PositionName, ': ', COUNT(e.EmployeeID)), 
        ', '
    ) AS PositionSummary
FROM Departments d
LEFT JOIN Employees e ON d.DepartmentID = e.DepartmentID
LEFT JOIN Positions p ON e.PositionID = p.PositionID
WHERE e.IsActive = 1
GROUP BY d.DepartmentID, d.DepartmentName, d.DepartmentCode
ORDER BY d.DepartmentName;

6. DATEDIFF()와 EOMONTH() - 날짜 처리의 정밀함

MS SQL Server의 날짜 함수들은 정밀한 날짜 계산과 비즈니스 로직 구현에 필수적입니다.

실무 활용 예제

예제 1: 고객 생애 가치 분석

-- 고객 행동 분석 및 생애 가치 계산
SELECT 
    c.CustomerID,
    c.CustomerName,
    c.RegisterDate,
    -- 가입 기간 계산
    DATEDIFF(day, c.RegisterDate, GETDATE()) AS DaysSinceRegistration,
    DATEDIFF(month, c.RegisterDate, GETDATE()) AS MonthsSinceRegistration,
    DATEDIFF(year, c.RegisterDate, GETDATE()) AS YearsSinceRegistration,
    
    -- 주문 관련 정보
    COUNT(o.OrderID) AS TotalOrders,
    ISNULL(SUM(o.TotalAmount), 0) AS TotalSpent,
    
    -- 최근 주문 정보
    MAX(o.OrderDate) AS LastOrderDate,
    DATEDIFF(day, MAX(o.OrderDate), GETDATE()) AS DaysSinceLastOrder,
    
    -- 고객 활동성 분석
    IIF(DATEDIFF(day, MAX(o.OrderDate), GETDATE()) <= 30, '활성', 
        IIF(DATEDIFF(day, MAX(o.OrderDate), GETDATE()) <= 90, '비활성', '휴면')) AS CustomerStatus,
    
    -- 월평균 구매 금액
    CASE 
        WHEN DATEDIFF(month, c.RegisterDate, GETDATE()) > 0 
        THEN ISNULL(SUM(o.TotalAmount), 0) / DATEDIFF(month, c.RegisterDate, GETDATE())
        ELSE ISNULL(SUM(o.TotalAmount), 0)
    END AS MonthlyAverageSpend,
    
    -- 생애 가치 등급
    CHOOSE(
        CASE 
            WHEN ISNULL(SUM(o.TotalAmount), 0) >= 1000000 THEN 5
            WHEN ISNULL(SUM(o.TotalAmount), 0) >= 500000 THEN 4
            WHEN ISNULL(SUM(o.TotalAmount), 0) >= 200000 THEN 3
            WHEN ISNULL(SUM(o.TotalAmount), 0) >= 50000 THEN 2
            ELSE 1
        END,
        'Bronze', 'Silver', 'Gold', 'Platinum', 'Diamond'
    ) AS CustomerTier
FROM Customers c
LEFT JOIN Orders o ON c.CustomerID = o.CustomerID
GROUP BY c.CustomerID, c.CustomerName, c.RegisterDate
ORDER BY TotalSpent DESC;

예제 2: 월별 매출 분석 및 예측

-- 월별 매출 추이 및 다음 달 예측
WITH MonthlySales AS (
    SELECT 
        YEAR(OrderDate) AS SalesYear,
        MONTH(OrderDate) AS SalesMonth,
        EOMONTH(OrderDate) AS MonthEnd,
        COUNT(*) AS OrderCount,
        SUM(TotalAmount) AS MonthlySales,
        AVG(TotalAmount) AS AverageOrderValue
    FROM Orders
    WHERE OrderDate >= DATEADD(year, -2, GETDATE())
    GROUP BY YEAR(OrderDate), MONTH(OrderDate), EOMONTH(OrderDate)
),
SalesWithGrowth AS (
    SELECT 
        *,
        LAG(MonthlySales) OVER (ORDER BY SalesYear, SalesMonth) AS PreviousMonthSales,
        LAG(MonthlySales, 12) OVER (ORDER BY SalesYear, SalesMonth) AS SameMonthLastYear
    FROM MonthlySales
)
SELECT 
    SalesYear,
    SalesMonth,
    FORMAT(MonthEnd, 'yyyy년 MM월', 'ko-KR') AS MonthDisplay,
    OrderCount,
    FORMAT(MonthlySales, 'C', 'ko-KR') AS FormattedMonthlySales,
    FORMAT(AverageOrderValue, 'C', 'ko-KR') AS FormattedAOV,
    
    -- 전월 대비 증감
    IIF(PreviousMonthSales IS NOT NULL,
        FORMAT((MonthlySales - PreviousMonthSales) / PreviousMonthSales, 'P2', 'ko-KR'),
        'N/A') AS MonthOverMonthGrowth,
    
    -- 전년 동월 대비 증감
    IIF(SameMonthLastYear IS NOT NULL,
        FORMAT((MonthlySales - SameMonthLastYear) / SameMonthLastYear, 'P2', 'ko-KR'),
        'N/A') AS YearOverYearGrowth,
    
    -- 월별 성과 등급
    CHOOSE(
        CASE 
            WHEN MonthlySales >= 10000000 THEN 5
            WHEN MonthlySales >= 7000000 THEN 4
            WHEN MonthlySales >= 5000000 THEN 3
            WHEN MonthlySales >= 3000000 THEN 2
            ELSE 1
        END,
        '매우 저조', '저조', '보통', '우수', '매우 우수'
    ) AS PerformanceGrade
FROM SalesWithGrowth
ORDER BY SalesYear DESC, SalesMonth DESC;

7. HASHBYTES()와 보안 함수들 - 엔터프라이즈 보안

MS SQL Server는 데이터 보안과 무결성을 위한 강력한 암호화 및 해시 함수들을 제공합니다.

실무 활용 예제

예제: 사용자 인증 및 데이터 무결성 시스템

-- 사용자 인증 테이블
CREATE TABLE UserAccounts (
    UserID int IDENTITY(1,1) PRIMARY KEY,
    Username nvarchar(50) UNIQUE NOT NULL,
    Email nvarchar(100) UNIQUE NOT NULL,
    PasswordHash varbinary(64) NOT NULL,
    PasswordSalt uniqueidentifier DEFAULT NEWID() NOT NULL,
    CreatedDate datetime2 DEFAULT GETDATE(),
    LastLoginDate datetime2,
    FailedLoginAttempts int DEFAULT 0,
    IsLocked bit DEFAULT 0
);

-- 안전한 패스워드 해시 생성 함수
CREATE FUNCTION GeneratePasswordHash(
    @Password nvarchar(100),
    @Salt uniqueidentifier
)
RETURNS varbinary(64)
AS
BEGIN
    RETURN HASHBYTES('SHA2_512', @Password + CAST(@Salt AS nvarchar(36)));
END;

-- 사용자 등록 프로시저
CREATE PROCEDURE RegisterUser
    @Username nvarchar(50),
    @Email nvarchar(100),
    @Password nvarchar(100)
AS
BEGIN
    DECLARE @Salt uniqueidentifier = NEWID();
    DECLARE @PasswordHash varbinary(64) = dbo.GeneratePasswordHash(@Password, @Salt);
    
    INSERT INTO UserAccounts (Username, Email, PasswordHash, PasswordSalt)
    VALUES (@Username, @Email, @PasswordHash, @Salt);
    
    SELECT SCOPE_IDENTITY() AS NewUserID;
END;

-- 사용자 로그인 검증 프로시저
CREATE PROCEDURE AuthenticateUser
    @Username nvarchar(50),
    @Password nvarchar(100)
AS
BEGIN
    DECLARE @UserID int;
    DECLARE @StoredHash varbinary(64);
    DECLARE @Salt uniqueidentifier;
    DECLARE @FailedAttempts int;
    DECLARE @IsLocked bit;
    
    -- 사용자 정보 조회
    SELECT 
        @UserID = UserID,
        @StoredHash = PasswordHash,
        @Salt = PasswordSalt,
        @FailedAttempts = FailedLoginAttempts,
        @IsLocked = IsLocked
    FROM UserAccounts
    WHERE Username = @Username;
    
    -- 사용자 존재 여부 확인
    IF @UserID IS NULL
    BEGIN
        SELECT 'USER_NOT_FOUND' AS Result;
        RETURN;
    END
    
    -- 계정 잠금 확인
    IF @IsLocked = 1
    BEGIN
        SELECT 'ACCOUNT_LOCKED' AS Result;
        RETURN;
    END
    
    -- 패스워드 검증
    IF dbo.GeneratePasswordHash(@Password, @Salt) = @StoredHash
    BEGIN
        -- 로그인 성공
        UPDATE UserAccounts 
        SET LastLoginDate = GETDATE(), 
            FailedLoginAttempts = 0
        WHERE UserID = @UserID;
        
        SELECT 'SUCCESS' AS Result, @UserID AS UserID;
    END
    ELSE
    BEGIN
        -- 로그인 실패
        SET @FailedAttempts = @FailedAttempts + 1;
        
        UPDATE UserAccounts 
        SET FailedLoginAttempts = @FailedAttempts,
            IsLocked = IIF(@FailedAttempts >= 5, 1, 0)
        WHERE UserID = @UserID;
        
        SELECT IIF(@FailedAttempts >= 5, 'ACCOUNT_LOCKED', 'INVALID_PASSWORD') AS Result;
    END
END;

MS SQL Server 고유 함수의 기업 가치

MS SQL Server의 고유 함수들은 엔터프라이즈 환경에서 요구되는 복잡한 비즈니스 로직, 보안 요구사항, 그리고 성능 최적화를 효과적으로 해결합니다. NEWID()와 NEWSEQUENTIALID()는 분산 시스템에서의 데이터 무결성을 보장하고, IIF()와 CHOOSE()는 복잡한 비즈니스 규칙을 간결하게 표현합니다.

 

특히 FORMAT() 함수를 통한 .NET 스타일의 데이터 포맷팅, STRING_SPLIT()과 STRING_AGG()를 활용한 현대적 문자열 처리, 그리고 HASHBYTES()를 통한 보안 강화는 현대 비즈니스 애플리케이션의 요구사항을 완벽히 충족합니다.

 

이러한 함수들은 단순히 코딩의 편의성을 제공하는 것을 넘어서, 기업의 핵심 비즈니스 로직을 데이터베이스 레벨에서 효율적으로 구현할 수 있게 해줍니다. MS SQL Server의 지속적인 발전과 함께 새로운 함수들이 계속 추가되고 있으므로, 최신 버전의 기능들을 적극적으로 활용하여 더욱 강력하고 안전한 엔터프라이즈 애플리케이션을 구축할 수 있습니다.

반응형