์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŽฏ Oracle PL/SQL ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ ์™„์ „์ •๋ณต! ๐Ÿš€

๐ŸŽฏ Oracle PL/SQL ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ ์™„์ „์ •๋ณต! ๐Ÿš€

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž๋™ํ™”์˜ ํ•ต์‹ฌ ๋ฌด๊ธฐ, ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ’ช

๐ŸŒŸ ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ, ์™œ ๋ฐฐ์›Œ์•ผ ํ• ๊นŒ?

์•ˆ๋…•! ์˜ค๋Š˜์€ Oracle ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ง„์งœ ํŒŒ์›Œ๋ฅผ ๋А๋‚„ ์ˆ˜ ์žˆ๋Š” PL/SQL ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊ฒŒ. ๐Ÿ˜Š

ํ˜น์‹œ ๋งค๋ฒˆ ๋˜‘๊ฐ™์€ SQL ์ฟผ๋ฆฌ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์ž‘์„ฑํ•˜๋‹ค๊ฐ€ "์•„, ์ด๊ฑฐ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋‚˜?" ํ•˜๊ณ  ์ƒ๊ฐํ•ด๋ณธ ์  ์žˆ์–ด? ๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ญ”๊ฐ€๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ์ ์€? ๋ฐ”๋กœ ๊ทธ๋Ÿด ๋•Œ ํ•„์š”ํ•œ ๊ฒŒ ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ์•ผ! ๐ŸŽฏ

์‹ค์ œ๋กœ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ํšŒ์›๊ฐ€์ž…, ๊ฑฐ๋ž˜ ์ฒ˜๋ฆฌ, ๋กœ๊ทธ ๊ธฐ๋ก ๋“ฑ ์ˆ˜๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์ด ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ๋กœ ์ž๋™ํ™”๋˜์–ด ์žˆ์–ด. ์ด๊ฑธ ๋ฐฐ์šฐ๋ฉด ๋„ˆ๋„ ํ”„๋กœ ๊ฐœ๋ฐœ์ž์ฒ˜๋Ÿผ ํšจ์œจ์ ์ธ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋ผ! ๐Ÿ’ผ

๐Ÿ“š PL/SQL ๊ธฐ๋ณธ ๊ฐœ๋…๋ถ€ํ„ฐ ์ฐจ๊ทผ์ฐจ๊ทผ

PL/SQL์ด ๋ญ์•ผ?

PL/SQL์€ Procedural Language/SQL์˜ ์•ฝ์ž์•ผ. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด SQL์— ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋Šฅ์„ ๋”ํ•œ ๊ฑฐ์ง€! ๐Ÿ”ง

์ผ๋ฐ˜ SQL์€ "์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€", "์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•ด" ๊ฐ™์€ ๋‹จ์ˆœ ๋ช…๋ น๋งŒ ๊ฐ€๋Šฅํ•ด. ํ•˜์ง€๋งŒ PL/SQL์€ ๋ณ€์ˆ˜ ์„ ์–ธ, ์กฐ๊ฑด๋ฌธ(IF), ๋ฐ˜๋ณต๋ฌธ(LOOP), ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊นŒ์ง€ ๊ฐ€๋Šฅํ•œ ์™„์ „ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์•ผ. ๋งˆ์น˜ SQL์ด ๋ ˆ๊ณ  ๋ธ”๋ก์ด๋ผ๋ฉด, PL/SQL์€ ๊ทธ ๋ธ”๋ก๋“ค์„ ์กฐ๋ฆฝํ•ด์„œ ๋ฉ‹์ง„ ์ž‘ํ’ˆ์„ ๋งŒ๋“œ๋Š” ์„ค๊ณ„๋„ ๊ฐ™์€ ๊ฑฐ์ง€! ๐Ÿ—๏ธ
๐Ÿ’ก PL/SQL์˜ ํ•ต์‹ฌ ์žฅ์ 

โœ… ์„ฑ๋Šฅ ํ–ฅ์ƒ: ์—ฌ๋Ÿฌ SQL ๋ฌธ์„ ํ•œ ๋ฒˆ์— ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์„œ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ ๊ฐ์†Œ
โœ… ๋ชจ๋“ˆํ™”: ๋ณต์žกํ•œ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ
โœ… ๋ณด์•ˆ ๊ฐ•ํ™”: ์ง์ ‘ ํ…Œ์ด๋ธ” ์ ‘๊ทผ ๋Œ€์‹  ํ”„๋กœ์‹œ์ €๋ฅผ ํ†ตํ•œ ๊ฐ„์ ‘ ์ ‘๊ทผ
โœ… ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด DB์— ์ง‘์ค‘๋˜์–ด ๊ด€๋ฆฌ ํŽธ๋ฆฌ
โœ… ํŠธ๋žœ์žญ์…˜ ์ œ์–ด: COMMIT, ROLLBACK ๋“ฑ์„ ์ฝ”๋“œ ๋‚ด์—์„œ ์ž์œ ๋กญ๊ฒŒ ์ œ์–ด

PL/SQL ๋ธ”๋ก ๊ตฌ์กฐ ์ดํ•ดํ•˜๊ธฐ

PL/SQL์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ธ”๋ก ๋‹จ์œ„๋กœ ์ž‘๋™ํ•ด. ๋งˆ์น˜ ๋ ˆ์‹œํ”ผ์ฒ˜๋Ÿผ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜๋Š” ๊ตฌ์กฐ์•ผ! ๐Ÿ“
DECLARE
    -- ์„ ์–ธ๋ถ€: ๋ณ€์ˆ˜, ์ƒ์ˆ˜, ์ปค์„œ ๋“ฑ์„ ์„ ์–ธ
    v_employee_name VARCHAR2(100);
    v_salary NUMBER;
BEGIN
    -- ์‹คํ–‰๋ถ€: ์‹ค์ œ ๋กœ์ง์ด ๋“ค์–ด๊ฐ€๋Š” ๋ถ€๋ถ„
    SELECT employee_name, salary 
    INTO v_employee_name, v_salary
    FROM employees 
    WHERE employee_id = 100;
    
    DBMS_OUTPUT.PUT_LINE('์ง์›๋ช…: ' || v_employee_name);
EXCEPTION
    -- ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ถ€: ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ฒ˜๋ฆฌ
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('ํ•ด๋‹น ์ง์›์ด ์—†์Šต๋‹ˆ๋‹ค.');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('์˜ค๋ฅ˜ ๋ฐœ์ƒ: ' || SQLERRM);
END;
/
์ด ๊ตฌ์กฐ๋Š” ๋งˆ์น˜ ์š”๋ฆฌํ•  ๋•Œ "์žฌ๋ฃŒ ์ค€๋น„(DECLARE) โ†’ ์กฐ๋ฆฌ(BEGIN) โ†’ ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ๋Œ€์ฒ˜(EXCEPTION)" ์ˆœ์„œ์™€ ๋˜‘๊ฐ™์•„! ๐Ÿณ
DECLARE ๋ณ€์ˆ˜ ์„ ์–ธ v_name VARCHAR2 v_count NUMBER v_date DATE BEGIN ์‹คํ–‰ ๋กœ์ง SELECT ์ฟผ๋ฆฌ ์‹คํ–‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ EXCEPTION ์˜ˆ์™ธ ์ฒ˜๋ฆฌ NO_DATA_FOUND TOO_MANY_ROWS OTHERS PL/SQL ๋ธ”๋ก ๊ตฌ์กฐ ์ˆœ์ฐจ์  ์‹คํ–‰ ํ๋ฆ„

๐Ÿ”ง ํ”„๋กœ์‹œ์ €(Procedure) ์™„์ „ ์ •๋ณต

ํ”„๋กœ์‹œ์ €๊ฐ€ ๋ญ๊ธธ๋ž˜?

ํ”„๋กœ์‹œ์ €๋Š” ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” PL/SQL ๋ธ”๋ก์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด๋‘” ๊ฑฐ์•ผ. ๋งˆ์น˜ ์ž์ฃผ ์“ฐ๋Š” ์š”๋ฆฌ ๋ ˆ์‹œํ”ผ๋ฅผ ๋…ธํŠธ์— ์ ์–ด๋‘๋Š” ๊ฒƒ์ฒ˜๋Ÿผ! ๐Ÿ“–

ํ•œ ๋ฒˆ ๋งŒ๋“ค์–ด๋‘๋ฉด ์–ธ์ œ๋“ ์ง€ ์ด๋ฆ„๋งŒ ๋ถˆ๋Ÿฌ์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด "ํšŒ์›๊ฐ€์ž… ์ฒ˜๋ฆฌ", "๊ธ‰์—ฌ ๊ณ„์‚ฐ", "์žฌ๊ณ  ์—…๋ฐ์ดํŠธ" ๊ฐ™์€ ๋ณต์žกํ•œ ์ž‘์—…๋“ค์„ ํ”„๋กœ์‹œ์ €๋กœ ๋งŒ๋“ค์–ด๋‘๋ฉด ์—„์ฒญ ํŽธํ•ด์ ธ! ๐ŸŽ‰
๐ŸŽฏ ํ”„๋กœ์‹œ์ € vs ํ•จ์ˆ˜ ์ฐจ์ด์ 

๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ํ—ท๊ฐˆ๋ คํ•˜๋Š” ๋ถ€๋ถ„์ธ๋ฐ, ๊ฐ„๋‹จํžˆ ์ •๋ฆฌํ•˜๋ฉด:

ํ”„๋กœ์‹œ์ €: ์ž‘์—… ์ˆ˜ํ–‰์ด ๋ชฉ์ , ๋ฐ˜ํ™˜๊ฐ’ ์—†๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ (OUT ํŒŒ๋ผ๋ฏธํ„ฐ)
ํ•จ์ˆ˜: ๊ฐ’ ๊ณ„์‚ฐ์ด ๋ชฉ์ , ๋ฐ˜๋“œ์‹œ ํ•˜๋‚˜์˜ ๊ฐ’์„ RETURN์œผ๋กœ ๋ฐ˜ํ™˜

ํ”„๋กœ์‹œ์ €๋Š” "์ผ์„ ํ•ด!", ํ•จ์ˆ˜๋Š” "๊ฐ’์„ ๊ณ„์‚ฐํ•ด!"๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ! ๐Ÿ’ก

ํ”„๋กœ์‹œ์ € ์ƒ์„ฑ ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

์ž, ์ด์ œ ์‹ค์ œ๋กœ ํ”„๋กœ์‹œ์ €๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ๊ธฐ๋ณธ ๋ฌธ๋ฒ•์€ ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ์–ด:
CREATE OR REPLACE PROCEDURE ํ”„๋กœ์‹œ์ €๋ช…
(
    ํŒŒ๋ผ๋ฏธํ„ฐ1 IN ๋ฐ์ดํ„ฐํƒ€์ž…,
    ํŒŒ๋ผ๋ฏธํ„ฐ2 OUT ๋ฐ์ดํ„ฐํƒ€์ž…,
    ํŒŒ๋ผ๋ฏธํ„ฐ3 IN OUT ๋ฐ์ดํ„ฐํƒ€์ž…
)
IS
    -- ๋ณ€์ˆ˜ ์„ ์–ธ
    v_local_var VARCHAR2(100);
BEGIN
    -- ์‹คํ–‰ ๋กœ์ง
    ์‹คํ–‰๋ฌธ๋“ค;
EXCEPTION
    -- ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
    WHEN ์˜ˆ์™ธ๋ช… THEN
        ์ฒ˜๋ฆฌ๋ฌธ;
END ํ”„๋กœ์‹œ์ €๋ช…;
/
ํŒŒ๋ผ๋ฏธํ„ฐ ๋ชจ๋“œ ์„ค๋ช…:

๐Ÿ”น IN: ์ž…๋ ฅ ์ „์šฉ (๊ธฐ๋ณธ๊ฐ’, ์ƒ๋žต ๊ฐ€๋Šฅ). ํ”„๋กœ์‹œ์ € ์•ˆ์—์„œ ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ
๐Ÿ”น OUT: ์ถœ๋ ฅ ์ „์šฉ. ํ”„๋กœ์‹œ์ €๊ฐ€ ๊ฐ’์„ ๋Œ๋ ค์ค„ ๋•Œ ์‚ฌ์šฉ
๐Ÿ”น IN OUT: ์ž…์ถœ๋ ฅ ๊ฒธ์šฉ. ๊ฐ’์„ ๋ฐ›์•„์„œ ์ˆ˜์ •ํ•œ ํ›„ ๋‹ค์‹œ ๋Œ๋ ค์คŒ

์‹ค์ „ ์˜ˆ์ œ 1: ์ง์› ์ •๋ณด ์กฐํšŒ ํ”„๋กœ์‹œ์ €

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์ œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž! ์ง์› ID๋ฅผ ์ž…๋ ฅ๋ฐ›์•„์„œ ์ด๋ฆ„๊ณผ ๊ธ‰์—ฌ๋ฅผ ์กฐํšŒํ•˜๋Š” ํ”„๋กœ์‹œ์ €์•ผ:
CREATE OR REPLACE PROCEDURE get_employee_info
(
    p_emp_id IN NUMBER,
    p_emp_name OUT VARCHAR2,
    p_salary OUT NUMBER
)
IS
BEGIN
    SELECT employee_name, salary
    INTO p_emp_name, p_salary
    FROM employees
    WHERE employee_id = p_emp_id;
    
    DBMS_OUTPUT.PUT_LINE('์กฐํšŒ ์™„๋ฃŒ: ' || p_emp_name);
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        p_emp_name := '์—†์Œ';
        p_salary := 0;
        DBMS_OUTPUT.PUT_LINE('ํ•ด๋‹น ์ง์›์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('์˜ค๋ฅ˜ ๋ฐœ์ƒ: ' || SQLERRM);
        RAISE;
END get_employee_info;
/
์‹คํ–‰ ๋ฐฉ๋ฒ•:
DECLARE
    v_name VARCHAR2(100);
    v_sal NUMBER;
BEGIN
    get_employee_info(100, v_name, v_sal);
    DBMS_OUTPUT.PUT_LINE('์ง์›๋ช…: ' || v_name || ', ๊ธ‰์—ฌ: ' || v_sal);
END;
/
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ง์› ID 100๋ฒˆ์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด! ๊ฐ„๋‹จํ•˜์ง€? ๐Ÿ˜Š

์‹ค์ „ ์˜ˆ์ œ 2: ๊ธ‰์—ฌ ์ธ์ƒ ํ”„๋กœ์‹œ์ €

์ด๋ฒˆ์—” ์ข€ ๋” ์‹ค์šฉ์ ์ธ ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ? ํŠน์ • ๋ถ€์„œ์˜ ์ง์›๋“ค ๊ธ‰์—ฌ๋ฅผ ์ผ๊ด„ ์ธ์ƒํ•˜๋Š” ํ”„๋กœ์‹œ์ €์•ผ:
CREATE OR REPLACE PROCEDURE raise_salary_by_dept
(
    p_dept_id IN NUMBER,
    p_raise_percent IN NUMBER,
    p_affected_count OUT NUMBER
)
IS
    v_min_salary NUMBER := 3000000;  -- ์ตœ์†Œ ๊ธ‰์—ฌ
    v_max_raise NUMBER := 20;        -- ์ตœ๋Œ€ ์ธ์ƒ๋ฅ  20%
BEGIN
    -- ์ธ์ƒ๋ฅ  ๊ฒ€์ฆ
    IF p_raise_percent <= 0 OR p_raise_percent > v_max_raise THEN
        RAISE_APPLICATION_ERROR(-20001, 
            '์ธ์ƒ๋ฅ ์€ 0๋ณด๋‹ค ํฌ๊ณ  ' || v_max_raise || '% ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.');
    END IF;
    
    -- ๊ธ‰์—ฌ ์ธ์ƒ ์‹คํ–‰
    UPDATE employees
    SET salary = salary * (1 + p_raise_percent / 100),
        last_update_date = SYSDATE
    WHERE department_id = p_dept_id
      AND salary >= v_min_salary;
    
    -- ์˜ํ–ฅ๋ฐ›์€ ํ–‰ ์ˆ˜ ์ €์žฅ
    p_affected_count := SQL%ROWCOUNT;
    
    -- ์ปค๋ฐ‹
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE(p_affected_count || '๋ช…์˜ ๊ธ‰์—ฌ๊ฐ€ ์ธ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
    
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('๊ธ‰์—ฌ ์ธ์ƒ ์‹คํŒจ: ' || SQLERRM);
        RAISE;
END raise_salary_by_dept;
/
์ด ํ”„๋กœ์‹œ์ €์˜ ํ•ต์‹ฌ ํฌ์ธํŠธ๋“ค์„ ์‚ดํŽด๋ณด์ž:
๐ŸŽฏ ํ•ต์‹ฌ ํฌ์ธํŠธ

1๏ธโƒฃ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ: ์ธ์ƒ๋ฅ ์ด ์œ ํšจํ•œ ๋ฒ”์œ„์ธ์ง€ ๋จผ์ € ์ฒดํฌ
2๏ธโƒฃ SQL%ROWCOUNT: ๋งˆ์ง€๋ง‰ SQL๋ฌธ์ด ์˜ํ–ฅ์„ ์ค€ ํ–‰์˜ ๊ฐœ์ˆ˜๋ฅผ ์•Œ๋ ค์คŒ
3๏ธโƒฃ ํŠธ๋žœ์žญ์…˜ ์ œ์–ด: ์„ฑ๊ณตํ•˜๋ฉด COMMIT, ์‹คํŒจํ•˜๋ฉด ROLLBACK
4๏ธโƒฃ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ: RAISE_APPLICATION_ERROR๋กœ ๋ช…ํ™•ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ
5๏ธโƒฃ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง: ์ตœ์†Œ ๊ธ‰์—ฌ ์ด์ƒ์ธ ์ง์›๋งŒ ์ธ์ƒ (์กฐ๊ฑด๋ถ€ ์ฒ˜๋ฆฌ)

์‹ค์ „ ์˜ˆ์ œ 3: ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง - ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ํ”„๋กœ์‹œ์ €

์ด์ œ ์ง„์งœ ์‹ค๋ฌด์—์„œ ์“ธ ๋ฒ•ํ•œ ๋ณต์žกํ•œ ํ”„๋กœ์‹œ์ €๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ! ์ฃผ๋ฌธ์„ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ ์žฌ๊ณ  ํ™•์ธ, ์žฌ๊ณ  ์ฐจ๊ฐ, ์ฃผ๋ฌธ ๊ธฐ๋ก, ํฌ์ธํŠธ ์ ๋ฆฝ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ํ”„๋กœ์‹œ์ €์•ผ:
CREATE OR REPLACE PROCEDURE process_order
(
    p_customer_id IN NUMBER,
    p_product_id IN NUMBER,
    p_quantity IN NUMBER,
    p_order_id OUT NUMBER,
    p_result_msg OUT VARCHAR2
)
IS
    v_stock_quantity NUMBER;
    v_product_price NUMBER;
    v_total_amount NUMBER;
    v_point_earned NUMBER;
    
    -- ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ
    e_insufficient_stock EXCEPTION;
    e_invalid_quantity EXCEPTION;
BEGIN
    -- 1. ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ
    IF p_quantity <= 0 THEN
        RAISE e_invalid_quantity;
    END IF;
    
    -- 2. ์žฌ๊ณ  ํ™•์ธ
    SELECT stock_quantity, price
    INTO v_stock_quantity, v_product_price
    FROM products
    WHERE product_id = p_product_id
    FOR UPDATE;  -- ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ์œ„ํ•œ ํ–‰ ์ž ๊ธˆ
    
    IF v_stock_quantity < p_quantity THEN
        RAISE e_insufficient_stock;
    END IF;
    
    -- 3. ์ด ๊ธˆ์•ก ๊ณ„์‚ฐ
    v_total_amount := v_product_price * p_quantity;
    v_point_earned := TRUNC(v_total_amount * 0.01);  -- 1% ํฌ์ธํŠธ ์ ๋ฆฝ
    
    -- 4. ์ฃผ๋ฌธ ์ƒ์„ฑ
    INSERT INTO orders (order_id, customer_id, product_id, quantity, 
                        total_amount, order_date, status)
    VALUES (orders_seq.NEXTVAL, p_customer_id, p_product_id, p_quantity,
            v_total_amount, SYSDATE, 'COMPLETED')
    RETURNING order_id INTO p_order_id;
    
    -- 5. ์žฌ๊ณ  ์ฐจ๊ฐ
    UPDATE products
    SET stock_quantity = stock_quantity - p_quantity,
        last_update_date = SYSDATE
    WHERE product_id = p_product_id;
    
    -- 6. ํฌ์ธํŠธ ์ ๋ฆฝ
    UPDATE customers
    SET point_balance = point_balance + v_point_earned,
        total_purchase_amount = total_purchase_amount + v_total_amount
    WHERE customer_id = p_customer_id;
    
    -- 7. ๋กœ๊ทธ ๊ธฐ๋ก
    INSERT INTO order_logs (log_id, order_id, action, action_date)
    VALUES (order_logs_seq.NEXTVAL, p_order_id, 'ORDER_COMPLETED', SYSDATE);
    
    COMMIT;
    
    p_result_msg := '์ฃผ๋ฌธ ์™„๋ฃŒ! ์ฃผ๋ฌธ๋ฒˆํ˜ธ: ' || p_order_id || 
                    ', ์ ๋ฆฝ ํฌ์ธํŠธ: ' || v_point_earned;
    
EXCEPTION
    WHEN e_invalid_quantity THEN
        ROLLBACK;
        p_result_msg := '์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰์€ 1๊ฐœ ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.';
        p_order_id := -1;
        
    WHEN e_insufficient_stock THEN
        ROLLBACK;
        p_result_msg := '์žฌ๊ณ  ๋ถ€์กฑ! ํ˜„์žฌ ์žฌ๊ณ : ' || v_stock_quantity || '๊ฐœ';
        p_order_id := -1;
        
    WHEN NO_DATA_FOUND THEN
        ROLLBACK;
        p_result_msg := '์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ƒํ’ˆ์ด๊ฑฐ๋‚˜ ๊ณ ๊ฐ์ž…๋‹ˆ๋‹ค.';
        p_order_id := -1;
        
    WHEN OTHERS THEN
        ROLLBACK;
        p_result_msg := '์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ์‹คํŒจ: ' || SQLERRM;
        p_order_id := -1;
        -- ๊ด€๋ฆฌ์ž์—๊ฒŒ ์•Œ๋ฆผ (์‹ค์ œ๋กœ๋Š” ๋ณ„๋„ ์•Œ๋ฆผ ํ”„๋กœ์‹œ์ € ํ˜ธ์ถœ)
        INSERT INTO error_logs (error_id, error_msg, error_date)
        VALUES (error_logs_seq.NEXTVAL, SQLERRM, SYSDATE);
        COMMIT;  -- ์—๋Ÿฌ ๋กœ๊ทธ๋Š” ๋ณ„๋„๋กœ ์ปค๋ฐ‹
END process_order;
/
์™€! ์ด ํ”„๋กœ์‹œ์ €๋Š” ์ •๋ง ๋งŽ์€ ์ผ์„ ํ•˜๊ณ  ์žˆ์–ด! ํ•˜๋‚˜์”ฉ ๋œฏ์–ด๋ณด์ž:
โœจ ์ด ํ”„๋กœ์‹œ์ €์˜ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค

๐Ÿ”ธ FOR UPDATE: ์žฌ๊ณ  ์กฐํšŒ ์‹œ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ๋™์‹œ์— ์ˆ˜์ • ๋ชปํ•˜๊ฒŒ ํ–‰ ์ž ๊ธˆ
๐Ÿ”ธ RETURNING INTO: INSERT ํ›„ ์ƒ์„ฑ๋œ ID๋ฅผ ๋ฐ”๋กœ ๋ณ€์ˆ˜์— ์ €์žฅ
๐Ÿ”ธ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ: ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ์œ„๋ฐ˜์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
๐Ÿ”ธ ์›์ž์„ฑ ๋ณด์žฅ: ๋ชจ๋“  ์ž‘์—…์ด ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜ ๋ชจ๋‘ ์‹คํŒจ (All or Nothing)
๐Ÿ”ธ ์—๋Ÿฌ ๋กœ๊น…: ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ์—๋„ ๋กœ๊ทธ๋Š” ๋ณ„๋„๋กœ ์ €์žฅ
๐Ÿ”ธ ๋ช…ํ™•ํ•œ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜: OUT ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ๋ฌธ๋ฒˆํ˜ธ์™€ ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ

ํ”„๋กœ์‹œ์ € ๋””๋ฒ„๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง

ํ”„๋กœ์‹œ์ €๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ๊ฒ ์ง€? ๋””๋ฒ„๊น… ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ”
-- 1. DBMS_OUTPUT์œผ๋กœ ๋””๋ฒ„๊น… ์ •๋ณด ์ถœ๋ ฅ
SET SERVEROUTPUT ON;  -- ๋จผ์ € ์ด๊ฑธ ์‹คํ–‰ํ•ด์•ผ ์ถœ๋ ฅ์ด ๋ณด์—ฌ!

CREATE OR REPLACE PROCEDURE debug_example
IS
    v_step VARCHAR2(100);
BEGIN
    v_step := 'Step 1: ๋ฐ์ดํ„ฐ ์กฐํšŒ';
    DBMS_OUTPUT.PUT_LINE(v_step || ' ์‹œ์ž‘');
    -- ์‹ค์ œ ๋กœ์ง
    DBMS_OUTPUT.PUT_LINE(v_step || ' ์™„๋ฃŒ');
    
    v_step := 'Step 2: ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ';
    DBMS_OUTPUT.PUT_LINE(v_step || ' ์‹œ์ž‘');
    -- ์‹ค์ œ ๋กœ์ง
    DBMS_OUTPUT.PUT_LINE(v_step || ' ์™„๋ฃŒ');
END;
/

-- 2. ํ”„๋กœ์‹œ์ € ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
DECLARE
    v_start_time TIMESTAMP;
    v_end_time TIMESTAMP;
    v_result VARCHAR2(1000);
    v_order_id NUMBER;
BEGIN
    v_start_time := SYSTIMESTAMP;
    
    process_order(1001, 2001, 5, v_order_id, v_result);
    
    v_end_time := SYSTIMESTAMP;
    
    DBMS_OUTPUT.PUT_LINE('์‹คํ–‰ ์‹œ๊ฐ„: ' || 
        TO_CHAR((v_end_time - v_start_time) DAY(0) TO SECOND(3)));
    DBMS_OUTPUT.PUT_LINE('๊ฒฐ๊ณผ: ' || v_result);
END;
/
โš ๏ธ ํ”„๋กœ์‹œ์ € ์ž‘์„ฑ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

1. ๊ณผ๋„ํ•œ COMMIT/ROLLBACK ๊ธˆ์ง€: ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ ํŠธ๋žœ์žญ์…˜์„ ์ œ์–ดํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒŒ ์ข‹์„ ๋•Œ๋„ ์žˆ์–ด
2. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•„์ˆ˜: ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ํ˜ธ์ถœํ•œ ์ชฝ๊นŒ์ง€ ์˜ํ–ฅ์„ ์ค˜
3. ์„ฑ๋Šฅ ๊ณ ๋ ค: ๋ถˆํ•„์š”ํ•œ SELECT ๋ฌธ์ด๋‚˜ ๋ฐ˜๋ณต๋ฌธ์€ ์„ฑ๋Šฅ ์ €ํ•˜์˜ ์ฃผ๋ฒ”
4. ๋ช…๋ช… ๊ทœ์น™: ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” p_, ๋ณ€์ˆ˜๋Š” v_, ์ปค์„œ๋Š” c_ ๊ฐ™์€ ์ ‘๋‘์‚ฌ ์‚ฌ์šฉ ๊ถŒ์žฅ
5. ์ฃผ์„ ์ž‘์„ฑ: ๋ณต์žกํ•œ ๋กœ์ง์€ ๋ฐ˜๋“œ์‹œ ์ฃผ์„์œผ๋กœ ์„ค๋ช…ํ•ด๋‘ฌ์•ผ ๋‚˜์ค‘์— ๊ณ ์ƒ ์•ˆ ํ•ด!

โšก ํŠธ๋ฆฌ๊ฑฐ(Trigger) ์™„์ „ ์ •๋ณต

ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ญ์•ผ?

ํŠธ๋ฆฌ๊ฑฐ๋Š” ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๋Š” PL/SQL ๋ธ”๋ก์ด์•ผ! ๋งˆ์น˜ "๋ฌธ์ด ์—ด๋ฆฌ๋ฉด ์ž๋™์œผ๋กœ ๋ถˆ์ด ์ผœ์ง€๋Š”" ์„ผ์„œ๋“ฑ์ฒ˜๋Ÿผ ๋ง์ด์•ผ. ๐Ÿ’ก

ํ”„๋กœ์‹œ์ €๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ํ˜ธ์ถœํ•ด์•ผ ํ•˜์ง€๋งŒ, ํŠธ๋ฆฌ๊ฑฐ๋Š” INSERT, UPDATE, DELETE ๊ฐ™์€ DML ์ž‘์—…์ด ๋ฐœ์ƒํ•˜๋ฉด ์ž๋™์œผ๋กœ ์‹คํ–‰๋ผ. ์ด๊ฒŒ ํŠธ๋ฆฌ๊ฑฐ์˜ ๊ฐ€์žฅ ํฐ ํŠน์ง•์ด์ž ์žฅ์ ์ด์ง€! ๐ŸŽฏ
๐ŸŽช ํŠธ๋ฆฌ๊ฑฐ์˜ ํ™œ์šฉ ์‚ฌ๋ก€

โœ… ๊ฐ์‚ฌ(Audit) ๋กœ๊ทธ: ๋ˆ„๊ฐ€ ์–ธ์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ–ˆ๋Š”์ง€ ์ž๋™ ๊ธฐ๋ก
โœ… ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ: ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์— ๋งž์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ๋ฐฉ์ง€
โœ… ์ž๋™ ๊ณ„์‚ฐ: ํ•ฉ๊ณ„, ํ‰๊ท  ๋“ฑ์„ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ
โœ… ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ: ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ด€๊ณ„๋ฅผ ์ž๋™์œผ๋กœ ์œ ์ง€
โœ… ์•Œ๋ฆผ ๋ฐœ์†ก: ํŠน์ • ์กฐ๊ฑด ๋งŒ์กฑ ์‹œ ์ž๋™์œผ๋กœ ์•Œ๋ฆผ ์ „์†ก
โœ… ์ด๋ ฅ ๊ด€๋ฆฌ: ๋ณ€๊ฒฝ ์ „ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ์ด๋ ฅ ํ…Œ์ด๋ธ”์— ๋ฐฑ์—…

ํŠธ๋ฆฌ๊ฑฐ ์ข…๋ฅ˜์™€ ํƒ€์ด๋ฐ

ํŠธ๋ฆฌ๊ฑฐ๋Š” ์‹คํ–‰ ์‹œ์ ๊ณผ ๋ ˆ๋ฒจ์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ์ข…๋ฅ˜๋กœ ๋‚˜๋‰˜์–ด:
๊ตฌ๋ถ„ ์ข…๋ฅ˜ ์„ค๋ช… ์‚ฌ์šฉ ์˜ˆ
์‹คํ–‰ ์‹œ์  BEFORE DML ์‹คํ–‰ ์ „์— ํŠธ๋ฆฌ๊ฑฐ ์‹คํ–‰ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ๊ฐ’ ๋ณ€๊ฒฝ
AFTER DML ์‹คํ–‰ ํ›„์— ํŠธ๋ฆฌ๊ฑฐ ์‹คํ–‰ ๋กœ๊ทธ ๊ธฐ๋ก, ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
์‹คํ–‰ ๋ ˆ๋ฒจ ROW ์˜ํ–ฅ๋ฐ›๋Š” ๊ฐ ํ–‰๋งˆ๋‹ค ์‹คํ–‰ ํ–‰๋ณ„ ๊ฒ€์ฆ, ์ด๋ ฅ ์ €์žฅ
STATEMENT DML ๋ฌธ๋‹น ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ ์ „์ฒด ์ž‘์—… ๋กœ๊ทธ
์ด๋ฒคํŠธ INSERT ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ์‹œ ์‹ ๊ทœ ํšŒ์› ํ™˜์˜ ๋ฉ”์‹œ์ง€
UPDATE ๋ฐ์ดํ„ฐ ์ˆ˜์ • ์‹œ ๋ณ€๊ฒฝ ์ด๋ ฅ ์ €์žฅ
DELETE ๋ฐ์ดํ„ฐ ์‚ญ์ œ ์‹œ ์‚ญ์ œ ์ „ ๋ฐฑ์—…

ํŠธ๋ฆฌ๊ฑฐ ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

ํŠธ๋ฆฌ๊ฑฐ์˜ ๊ธฐ๋ณธ ๋ฌธ๋ฒ•์„ ์‚ดํŽด๋ณด์ž:
CREATE OR REPLACE TRIGGER ํŠธ๋ฆฌ๊ฑฐ๋ช…
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON ํ…Œ์ด๋ธ”๋ช…
[FOR EACH ROW]
[WHEN (์กฐ๊ฑด)]
DECLARE
    -- ๋ณ€์ˆ˜ ์„ ์–ธ
BEGIN
    -- ์‹คํ–‰ ๋กœ์ง
    -- :NEW.์ปฌ๋Ÿผ๋ช… - ์ƒˆ๋กœ์šด ๊ฐ’ (INSERT, UPDATE)
    -- :OLD.์ปฌ๋Ÿผ๋ช… - ์ด์ „ ๊ฐ’ (UPDATE, DELETE)
EXCEPTION
    -- ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
END;
/
ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ ์„ค๋ช…:

๐Ÿ”น :NEW: INSERT๋‚˜ UPDATE ํ›„์˜ ์ƒˆ๋กœ์šด ๊ฐ’
๐Ÿ”น :OLD: UPDATE๋‚˜ DELETE ์ „์˜ ์ด์ „ ๊ฐ’
๐Ÿ”น FOR EACH ROW: ํ–‰ ๋ ˆ๋ฒจ ํŠธ๋ฆฌ๊ฑฐ (์ƒ๋žตํ•˜๋ฉด ๋ฌธ์žฅ ๋ ˆ๋ฒจ)
๐Ÿ”น WHEN: ํŠน์ • ์กฐ๊ฑด์„ ๋งŒ์กฑํ•  ๋•Œ๋งŒ ํŠธ๋ฆฌ๊ฑฐ ์‹คํ–‰

์‹ค์ „ ์˜ˆ์ œ 1: ๊ฐ์‚ฌ ๋กœ๊ทธ ํŠธ๋ฆฌ๊ฑฐ

๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ฐ์‚ฌ ๋กœ๊ทธ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ์ง์› ์ •๋ณด๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ํŠธ๋ฆฌ๊ฑฐ์•ผ:
-- ๋จผ์ € ๊ฐ์‚ฌ ๋กœ๊ทธ ํ…Œ์ด๋ธ” ์ƒ์„ฑ
CREATE TABLE employee_audit_log (
    log_id NUMBER PRIMARY KEY,
    employee_id NUMBER,
    action_type VARCHAR2(10),
    old_name VARCHAR2(100),
    new_name VARCHAR2(100),
    old_salary NUMBER,
    new_salary NUMBER,
    changed_by VARCHAR2(100),
    changed_date TIMESTAMP
);

CREATE SEQUENCE audit_log_seq START WITH 1;

-- ๊ฐ์‚ฌ ๋กœ๊ทธ ํŠธ๋ฆฌ๊ฑฐ ์ƒ์„ฑ
CREATE OR REPLACE TRIGGER trg_employee_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
    v_action VARCHAR2(10);
    v_user VARCHAR2(100);
BEGIN
    -- ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
    v_user := USER;
    
    -- ์–ด๋–ค ์ž‘์—…์ธ์ง€ ํŒ๋‹จ
    IF INSERTING THEN
        v_action := 'INSERT';
        INSERT INTO employee_audit_log 
        VALUES (audit_log_seq.NEXTVAL, :NEW.employee_id, v_action,
                NULL, :NEW.employee_name, NULL, :NEW.salary,
                v_user, SYSTIMESTAMP);
                
    ELSIF UPDATING THEN
        v_action := 'UPDATE';
        INSERT INTO employee_audit_log 
        VALUES (audit_log_seq.NEXTVAL, :NEW.employee_id, v_action,
                :OLD.employee_name, :NEW.employee_name,
                :OLD.salary, :NEW.salary,
                v_user, SYSTIMESTAMP);
                
    ELSIF DELETING THEN
        v_action := 'DELETE';
        INSERT INTO employee_audit_log 
        VALUES (audit_log_seq.NEXTVAL, :OLD.employee_id, v_action,
                :OLD.employee_name, NULL, :OLD.salary, NULL,
                v_user, SYSTIMESTAMP);
    END IF;
    
EXCEPTION
    WHEN OTHERS THEN
        -- ๋กœ๊ทธ ๊ธฐ๋ก ์‹คํŒจํ•ด๋„ ์›๋ž˜ ์ž‘์—…์€ ๊ณ„์† ์ง„ํ–‰
        DBMS_OUTPUT.PUT_LINE('๊ฐ์‚ฌ ๋กœ๊ทธ ๊ธฐ๋ก ์‹คํŒจ: ' || SQLERRM);
END;
/
์ด ํŠธ๋ฆฌ๊ฑฐ์˜ ๋ฉ‹์ง„ ์ ๋“ค:
โœจ ๊ฐ์‚ฌ ๋กœ๊ทธ ํŠธ๋ฆฌ๊ฑฐ์˜ ์žฅ์ 

1๏ธโƒฃ ์ž๋™ํ™”: ๊ฐœ๋ฐœ์ž๊ฐ€ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„ ๋ชจ๋“  ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ž๋™ ๊ธฐ๋ก
2๏ธโƒฃ ์™„์ „์„ฑ: ์–ด๋–ค ๊ฒฝ๋กœ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋“  100% ์บก์ฒ˜
3๏ธโƒฃ ์ถ”์ ์„ฑ: ๋ˆ„๊ฐ€, ์–ธ์ œ, ๋ฌด์—‡์„ ๋ณ€๊ฒฝํ–ˆ๋Š”์ง€ ์™„๋ฒฝํ•˜๊ฒŒ ์ถ”์ 
4๏ธโƒฃ ๋ณด์•ˆ: ๋ถ€์ • ์ ‘๊ทผ์ด๋‚˜ ์‹ค์ˆ˜๋กœ ์ธํ•œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ๊ฐ์ง€
5๏ธโƒฃ ๋ณต๊ตฌ: ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์ด์ „ ๊ฐ’์œผ๋กœ ๋ณต๊ตฌ ๊ฐ€๋Šฅ

์‹ค์ „ ์˜ˆ์ œ 2: ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ํŠธ๋ฆฌ๊ฑฐ

์ด๋ฒˆ์—” ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ๊ฑธ ๋ง‰๋Š” ๊ฒ€์ฆ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:
CREATE OR REPLACE TRIGGER trg_validate_employee
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
DECLARE
    v_age NUMBER;
    v_dept_exists NUMBER;
BEGIN
    -- 1. ๊ธ‰์—ฌ ๊ฒ€์ฆ
    IF :NEW.salary < 0 THEN
        RAISE_APPLICATION_ERROR(-20001, '๊ธ‰์—ฌ๋Š” 0๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
    END IF;
    
    IF :NEW.salary > 100000000 THEN
        RAISE_APPLICATION_ERROR(-20002, '๊ธ‰์—ฌ๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค. ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.');
    END IF;
    
    -- 2. ๋‚˜์ด ๊ฒ€์ฆ
    v_age := TRUNC(MONTHS_BETWEEN(SYSDATE, :NEW.birth_date) / 12);
    
    IF v_age < 18 THEN
        RAISE_APPLICATION_ERROR(-20003, '๋งŒ 18์„ธ ๋ฏธ๋งŒ์€ ์ฑ„์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
    END IF;
    
    IF v_age > 70 THEN
        RAISE_APPLICATION_ERROR(-20004, '์ •๋…„์„ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค.');
    END IF;
    
    -- 3. ๋ถ€์„œ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
    IF :NEW.department_id IS NOT NULL THEN
        SELECT COUNT(*)
        INTO v_dept_exists
        FROM departments
        WHERE department_id = :NEW.department_id;
        
        IF v_dept_exists = 0 THEN
            RAISE_APPLICATION_ERROR(-20005, '์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ถ€์„œ์ž…๋‹ˆ๋‹ค.');
        END IF;
    END IF;
    
    -- 4. ์ด๋ฉ”์ผ ํ˜•์‹ ๊ฒ€์ฆ
    IF :NEW.email IS NOT NULL THEN
        IF NOT REGEXP_LIKE(:NEW.email, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') THEN
            RAISE_APPLICATION_ERROR(-20006, '์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค.');
        END IF;
    END IF;
    
    -- 5. ์ „ํ™”๋ฒˆํ˜ธ ํ˜•์‹ ์ž๋™ ์ •๋ฆฌ
    IF :NEW.phone_number IS NOT NULL THEN
        :NEW.phone_number := REGEXP_REPLACE(:NEW.phone_number, '[^0-9]', '');
    END IF;
    
    -- 6. ์ž๋™์œผ๋กœ ์ƒ์„ฑ์ผ์‹œ ์„ค์ • (INSERT ์‹œ์—๋งŒ)
    IF INSERTING THEN
        :NEW.created_date := SYSDATE;
        :NEW.created_by := USER;
    END IF;
    
    -- 7. ์ž๋™์œผ๋กœ ์ˆ˜์ •์ผ์‹œ ์„ค์ • (UPDATE ์‹œ์—๋งŒ)
    IF UPDATING THEN
        :NEW.last_update_date := SYSDATE;
        :NEW.last_update_by := USER;
    END IF;
    
END;
/
์™€! ์ด ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ •๋ง ๋งŽ์€ ๊ฒ€์ฆ์„ ํ•˜๊ณ  ์žˆ์–ด! ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ ์ผ์ผ์ด ๊ฒ€์ฆํ•˜์ง€ ์•Š์•„๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ ˆ๋ฒจ์—์„œ ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ›ก๏ธ

์‹ค์ „ ์˜ˆ์ œ 3: ์ž๋™ ๊ณ„์‚ฐ ๋ฐ ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ ํŠธ๋ฆฌ๊ฑฐ

์ฃผ๋ฌธ์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ๊ณ ๊ฐ์˜ ์ด ๊ตฌ๋งค๊ธˆ์•ก๊ณผ ๊ตฌ๋งคํšŸ์ˆ˜๋ฅผ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํŠธ๋ฆฌ๊ฑฐ์•ผ:
CREATE OR REPLACE TRIGGER trg_update_customer_stats
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
DECLARE
    v_customer_id NUMBER;
    v_amount_change NUMBER := 0;
    v_count_change NUMBER := 0;
BEGIN
    -- ์–ด๋–ค ์ž‘์—…์ธ์ง€์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๊ฐ’ ๊ณ„์‚ฐ
    IF INSERTING THEN
        v_customer_id := :NEW.customer_id;
        v_amount_change := :NEW.total_amount;
        v_count_change := 1;
        
    ELSIF UPDATING THEN
        v_customer_id := :NEW.customer_id;
        v_amount_change := :NEW.total_amount - :OLD.total_amount;
        -- ์ฃผ๋ฌธ ์ˆ˜๋Š” ๋ณ€๊ฒฝ ์—†์Œ
        
    ELSIF DELETING THEN
        v_customer_id := :OLD.customer_id;
        v_amount_change := -:OLD.total_amount;
        v_count_change := -1;
    END IF;
    
    -- ๊ณ ๊ฐ ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
    UPDATE customers
    SET total_purchase_amount = total_purchase_amount + v_amount_change,
        total_order_count = total_order_count + v_count_change,
        last_order_date = CASE 
            WHEN INSERTING OR UPDATING THEN SYSDATE 
            ELSE last_order_date 
        END,
        customer_grade = CASE
            WHEN total_purchase_amount + v_amount_change >= 10000000 THEN 'VIP'
            WHEN total_purchase_amount + v_amount_change >= 5000000 THEN 'GOLD'
            WHEN total_purchase_amount + v_amount_change >= 1000000 THEN 'SILVER'
            ELSE 'BRONZE'
        END
    WHERE customer_id = v_customer_id;
    
EXCEPTION
    WHEN OTHERS THEN
        -- ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ ์‹คํŒจ ์‹œ ๋กœ๊ทธ ๊ธฐ๋ก
        INSERT INTO error_logs (error_id, error_msg, error_date)
        VALUES (error_logs_seq.NEXTVAL, 
                '๊ณ ๊ฐ ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ' || SQLERRM, SYSDATE);
        -- ์›๋ž˜ ์ž‘์—…์€ ๊ณ„์† ์ง„ํ–‰ (์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์Œ)
END;
/
์ด ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๊ณ ๊ฐ์˜ ๋“ฑ๊ธ‰๊นŒ์ง€ ์ž๋™์œผ๋กœ ์žฌ๊ณ„์‚ฐํ•ด์ค˜! ์‹ค๋ฌด์—์„œ ์ •๋ง ์œ ์šฉํ•˜๊ฒŒ ์“ฐ์ด๋Š” ํŒจํ„ด์ด์•ผ. ๐ŸŽ–๏ธ

์‹ค์ „ ์˜ˆ์ œ 4: ๋ณตํ•ฉ ์ด๋ฒคํŠธ ํŠธ๋ฆฌ๊ฑฐ (COMPOUND TRIGGER)

Oracle 11g๋ถ€ํ„ฐ ์ง€์›๋˜๋Š” ๋ณตํ•ฉ ํŠธ๋ฆฌ๊ฑฐ๋Š” ์—ฌ๋Ÿฌ ํƒ€์ด๋ฐ ํฌ์ธํŠธ๋ฅผ ํ•˜๋‚˜์˜ ํŠธ๋ฆฌ๊ฑฐ์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ์„ฑ๋Šฅ๋„ ๋” ์ข‹๊ณ  ์ฝ”๋“œ๋„ ๊น”๋”ํ•ด์ ธ!
CREATE OR REPLACE TRIGGER trg_employee_complex
FOR INSERT OR UPDATE OR DELETE ON employees
COMPOUND TRIGGER
    
    -- ํŒจํ‚ค์ง€ ๋ ˆ๋ฒจ ๋ณ€์ˆ˜ (๋ชจ๋“  ํƒ€์ด๋ฐ ํฌ์ธํŠธ์—์„œ ๊ณต์œ )
    TYPE t_emp_ids IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    v_affected_emp_ids t_emp_ids;
    v_counter PLS_INTEGER := 0;
    
    -- BEFORE STATEMENT: DML ๋ฌธ ์‹คํ–‰ ์ „ (ํ•œ ๋ฒˆ๋งŒ)
    BEFORE STATEMENT IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('=== ์ง์› ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ์ž‘ ===');
        v_counter := 0;
    END BEFORE STATEMENT;
    
    -- BEFORE EACH ROW: ๊ฐ ํ–‰ ์ฒ˜๋ฆฌ ์ „
    BEFORE EACH ROW IS
    BEGIN
        IF INSERTING THEN
            -- ์‹ ๊ทœ ์ง์›๋ฒˆํ˜ธ ์ž๋™ ์ƒ์„ฑ
            IF :NEW.employee_id IS NULL THEN
                :NEW.employee_id := employees_seq.NEXTVAL;
            END IF;
            -- ์ž…์‚ฌ์ผ ์ž๋™ ์„ค์ •
            IF :NEW.hire_date IS NULL THEN
                :NEW.hire_date := SYSDATE;
            END IF;
        END IF;
        
        IF UPDATING THEN
            -- ๊ธ‰์—ฌ ์ธ์ƒ๋ฅ  ์ œํ•œ (ํ•œ ๋ฒˆ์— 50% ์ด์ƒ ์ธ์ƒ ๋ถˆ๊ฐ€)
            IF :NEW.salary > :OLD.salary * 1.5 THEN
                RAISE_APPLICATION_ERROR(-20010, 
                    '๊ธ‰์—ฌ๋Š” ํ•œ ๋ฒˆ์— 50% ์ด์ƒ ์ธ์ƒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
            END IF;
        END IF;
    END BEFORE EACH ROW;
    
    -- AFTER EACH ROW: ๊ฐ ํ–‰ ์ฒ˜๋ฆฌ ํ›„
    AFTER EACH ROW IS
    BEGIN
        v_counter := v_counter + 1;
        
        IF INSERTING OR UPDATING THEN
            v_affected_emp_ids(v_counter) := :NEW.employee_id;
        ELSIF DELETING THEN
            v_affected_emp_ids(v_counter) := :OLD.employee_id;
        END IF;
    END AFTER EACH ROW;
    
    -- AFTER STATEMENT: DML ๋ฌธ ์‹คํ–‰ ํ›„ (ํ•œ ๋ฒˆ๋งŒ)
    AFTER STATEMENT IS
        v_dept_id NUMBER;
    BEGIN
        DBMS_OUTPUT.PUT_LINE('์ด ' || v_counter || '๊ฑด์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
        
        -- ์˜ํ–ฅ๋ฐ›์€ ๋ถ€์„œ์˜ ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
        FOR i IN 1..v_counter LOOP
            SELECT department_id INTO v_dept_id
            FROM employees
            WHERE employee_id = v_affected_emp_ids(i);
            
            -- ๋ถ€์„œ๋ณ„ ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ (์ค‘๋ณต ์ œ๊ฑฐ๋Š” ๋ณ„๋„ ๋กœ์ง ํ•„์š”)
            UPDATE departments
            SET employee_count = (
                SELECT COUNT(*) FROM employees 
                WHERE department_id = v_dept_id
            ),
            avg_salary = (
                SELECT AVG(salary) FROM employees 
                WHERE department_id = v_dept_id
            )
            WHERE department_id = v_dept_id;
        END LOOP;
        
        DBMS_OUTPUT.PUT_LINE('=== ์ง์› ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์™„๋ฃŒ ===');
        
    EXCEPTION
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ' || SQLERRM);
    END AFTER STATEMENT;
    
END trg_employee_complex;
/
๋ณตํ•ฉ ํŠธ๋ฆฌ๊ฑฐ์˜ ์žฅ์ :
๐Ÿš€ ๋ณตํ•ฉ ํŠธ๋ฆฌ๊ฑฐ์˜ ๊ฐ•๋ ฅํ•จ

โœจ ๋ณ€์ˆ˜ ๊ณต์œ : ๋ชจ๋“  ํƒ€์ด๋ฐ ํฌ์ธํŠธ์—์„œ ๊ฐ™์€ ๋ณ€์ˆ˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โœจ ์„ฑ๋Šฅ ํ–ฅ์ƒ: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•ด์„œ ์˜ค๋ฒ„ํ—ค๋“œ ๊ฐ์†Œ
โœจ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ: ๊ด€๋ จ ๋กœ์ง์ด ํ•œ ๊ณณ์— ๋ชจ์—ฌ์žˆ์–ด ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›€
โœจ ๋ฎคํ…Œ์ดํŒ… ํ…Œ์ด๋ธ” ๋ฌธ์ œ ํ•ด๊ฒฐ: AFTER STATEMENT์—์„œ ์ง‘๊ณ„ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
โœจ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ์—ฌ๋Ÿฌ ํ–‰์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ชจ์•„์„œ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ

ํŠธ๋ฆฌ๊ฑฐ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

ํŠธ๋ฆฌ๊ฑฐ๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ๋…์ด ๋  ์ˆ˜ ์žˆ์–ด. ์ฃผ์˜์‚ฌํ•ญ์„ ๊ผญ ์•Œ์•„๋‘์ž! โš ๏ธ
๐Ÿšจ ํŠธ๋ฆฌ๊ฑฐ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

1. ๋ฎคํ…Œ์ดํŒ… ํ…Œ์ด๋ธ” ๋ฌธ์ œ
ํ–‰ ๋ ˆ๋ฒจ ํŠธ๋ฆฌ๊ฑฐ์—์„œ ๊ฐ™์€ ํ…Œ์ด๋ธ”์„ SELECT/UPDATEํ•˜๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
โ†’ ํ•ด๊ฒฐ: ๋ณตํ•ฉ ํŠธ๋ฆฌ๊ฑฐ ์‚ฌ์šฉ ๋˜๋Š” ์ž„์‹œ ํ…Œ์ด๋ธ” ํ™œ์šฉ

2. ๋ฌดํ•œ ๋ฃจํ”„ ์œ„ํ—˜
ํŠธ๋ฆฌ๊ฑฐ A๊ฐ€ ํ…Œ์ด๋ธ” B๋ฅผ ์ˆ˜์ • โ†’ ํŠธ๋ฆฌ๊ฑฐ B๊ฐ€ ํ…Œ์ด๋ธ” A๋ฅผ ์ˆ˜์ • โ†’ ๋ฌดํ•œ ๋ฐ˜๋ณต
โ†’ ํ•ด๊ฒฐ: ํŠธ๋ฆฌ๊ฑฐ ์ฒด์ธ ์ตœ์†Œํ™”, ์กฐ๊ฑด๋ฌธ์œผ๋กœ ์žฌ๊ท€ ๋ฐฉ์ง€

3. ์„ฑ๋Šฅ ์ €ํ•˜
๋ชจ๋“  DML๋งˆ๋‹ค ํŠธ๋ฆฌ๊ฑฐ ์‹คํ–‰์œผ๋กœ ์„ฑ๋Šฅ ์˜ํ–ฅ
โ†’ ํ•ด๊ฒฐ: ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋งŒ ์‚ฌ์šฉ, ๋ณต์žกํ•œ ๋กœ์ง์€ ํ”„๋กœ์‹œ์ €๋กœ ๋ถ„๋ฆฌ

4. ๋””๋ฒ„๊น… ์–ด๋ ค์›€
ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ž๋™ ์‹คํ–‰๋˜์–ด ๋ฌธ์ œ ์›์ธ ํŒŒ์•…์ด ์–ด๋ ค์›€
โ†’ ํ•ด๊ฒฐ: ์ถฉ๋ถ„ํ•œ ๋กœ๊น…, ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑ

5. ํŠธ๋žœ์žญ์…˜ ์ œ์–ด ์ œํ•œ
ํŠธ๋ฆฌ๊ฑฐ ๋‚ด์—์„œ COMMIT/ROLLBACK ์‚ฌ์šฉ ๋ถˆ๊ฐ€
โ†’ ํ•ด๊ฒฐ: ์ž์œจ ํŠธ๋žœ์žญ์…˜(PRAGMA AUTONOMOUS_TRANSACTION) ์‚ฌ์šฉ

์ž์œจ ํŠธ๋žœ์žญ์…˜ ํ™œ์šฉ

ํŠธ๋ฆฌ๊ฑฐ์—์„œ ๋…๋ฆฝ์ ์ธ ํŠธ๋žœ์žญ์…˜์ด ํ•„์š”ํ•  ๋•Œ ์ž์œจ ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•ด:
CREATE OR REPLACE TRIGGER trg_error_logging
AFTER INSERT OR UPDATE OR DELETE ON critical_data
FOR EACH ROW
DECLARE
    PRAGMA AUTONOMOUS_TRANSACTION;  -- ์ž์œจ ํŠธ๋žœ์žญ์…˜ ์„ ์–ธ
BEGIN
    -- ์ด ํŠธ๋ฆฌ๊ฑฐ์˜ ์ž‘์—…์€ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜๊ณผ ๋…๋ฆฝ์ 
    INSERT INTO audit_log (log_id, action, log_date)
    VALUES (audit_seq.NEXTVAL, 
            CASE 
                WHEN INSERTING THEN 'INSERT'
                WHEN UPDATING THEN 'UPDATE'
                WHEN DELETING THEN 'DELETE'
            END,
            SYSDATE);
    
    COMMIT;  -- ์ž์œจ ํŠธ๋žœ์žญ์…˜์ด๋ฏ€๋กœ COMMIT ๊ฐ€๋Šฅ!
    
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;  -- ๋กœ๊ทธ ๊ธฐ๋ก ์‹คํŒจํ•ด๋„ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜์— ์˜ํ–ฅ ์—†์Œ
END;
/
์ž์œจ ํŠธ๋žœ์žญ์…˜์€ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜์–ด๋„ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์•„. ๋กœ๊ทธ ๊ธฐ๋ก์ด๋‚˜ ์•Œ๋ฆผ ๋ฐœ์†ก ๊ฐ™์€ ์ž‘์—…์— ์œ ์šฉํ•ด! ๐Ÿ“
ํ”„๋กœ์‹œ์ € vs ํŠธ๋ฆฌ๊ฑฐ ๋น„๊ต PROCEDURE ๋ช…์‹œ์  ํ˜ธ์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ ์ „๋‹ฌ ๊ฐ€๋Šฅ ๋ฐ˜ํ™˜๊ฐ’ ์žˆ์Œ (OUT) ํŠธ๋žœ์žญ์…˜ ์ œ์–ด ๊ฐ€๋Šฅ ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์žฌ์‚ฌ์šฉ์„ฑ ๋†’์Œ ์‚ฌ์šฉ ์˜ˆ์‹œ EXECUTE process_order (1001, 2001, 5, v_order_id, v_result); TRIGGER ์ž๋™ ์‹คํ–‰ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋™์ž‘ ๋ฐ˜ํ™˜๊ฐ’ ์—†์Œ ํŠธ๋žœ์žญ์…˜ ์ œ์–ด ์ œํ•œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ/๋กœ๊น… ํˆฌ๋ช…ํ•œ ์ž๋™ํ™” ์‚ฌ์šฉ ์˜ˆ์‹œ INSERT INTO orders ... โ†’ ํŠธ๋ฆฌ๊ฑฐ ์ž๋™ ์‹คํ–‰ โ†’ ๋กœ๊ทธ ๊ธฐ๋ก ์™„๋ฃŒ VS

๐ŸŽจ ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ ๊ณ ๊ธ‰ ํ™œ์šฉ ํŒจํ„ด

ํŒจํ„ด 1: ํ”„๋กœ์‹œ์ €์—์„œ ํŠธ๋ฆฌ๊ฑฐ ์ œ์–ดํ•˜๊ธฐ

๋•Œ๋กœ๋Š” ํ”„๋กœ์‹œ์ € ์‹คํ–‰ ์ค‘์— ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ผ์‹œ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•ด์„œ์ง€:
CREATE OR REPLACE PROCEDURE bulk_update_employees
(
    p_dept_id IN NUMBER,
    p_raise_percent IN NUMBER
)
IS
BEGIN
    -- ํŠธ๋ฆฌ๊ฑฐ ๋น„ํ™œ์„ฑํ™”
    EXECUTE IMMEDIATE 'ALTER TRIGGER trg_employee_audit DISABLE';
    
    -- ๋Œ€๋Ÿ‰ ์—…๋ฐ์ดํŠธ ์ˆ˜ํ–‰
    UPDATE employees
    SET salary = salary * (1 + p_raise_percent / 100)
    WHERE department_id = p_dept_id;
    
    COMMIT;
    
    -- ํŠธ๋ฆฌ๊ฑฐ ๋‹ค์‹œ ํ™œ์„ฑํ™”
    EXECUTE IMMEDIATE 'ALTER TRIGGER trg_employee_audit ENABLE';
    
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || '๊ฑด ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ');
    
EXCEPTION
    WHEN OTHERS THEN
        -- ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ์—๋„ ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋ฐ˜๋“œ์‹œ ๋‹ค์‹œ ํ™œ์„ฑํ™”
        EXECUTE IMMEDIATE 'ALTER TRIGGER trg_employee_audit ENABLE';
        RAISE;
END;
/
โš ๏ธ ์ฃผ์˜! ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๊ฐ์‚ฌ ๋กœ๊ทธ๋‚˜ ๊ฒ€์ฆ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์•„. ์ •๋ง ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ด!

ํŒจํ„ด 2: ๋™์  SQL ํ™œ์šฉ

์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์ด๋‚˜ ์ปฌ๋Ÿผ์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ ๋™์  SQL์„ ์‚ฌ์šฉํ•ด:
CREATE OR REPLACE PROCEDURE dynamic_table_backup
(
    p_table_name IN VARCHAR2,
    p_where_clause IN VARCHAR2 DEFAULT NULL
)
IS
    v_sql VARCHAR2(4000);
    v_backup_table VARCHAR2(100);
    v_count NUMBER;
BEGIN
    -- ๋ฐฑ์—… ํ…Œ์ด๋ธ”๋ช… ์ƒ์„ฑ
    v_backup_table := p_table_name || '_BACKUP_' || 
                      TO_CHAR(SYSDATE, 'YYYYMMDD_HH24MISS');
    
    -- 1. ๋ฐฑ์—… ํ…Œ์ด๋ธ” ์ƒ์„ฑ
    v_sql := 'CREATE TABLE ' || v_backup_table || 
             ' AS SELECT * FROM ' || p_table_name;
    
    IF p_where_clause IS NOT NULL THEN
        v_sql := v_sql || ' WHERE ' || p_where_clause;
    ELSE
        v_sql := v_sql || ' WHERE 1=1';
    END IF;
    
    EXECUTE IMMEDIATE v_sql;
    
    -- 2. ๋ฐฑ์—…๋œ ํ–‰ ์ˆ˜ ํ™•์ธ
    v_sql := 'SELECT COUNT(*) FROM ' || v_backup_table;
    EXECUTE IMMEDIATE v_sql INTO v_count;
    
    DBMS_OUTPUT.PUT_LINE('๋ฐฑ์—… ์™„๋ฃŒ: ' || v_backup_table);
    DBMS_OUTPUT.PUT_LINE('๋ฐฑ์—…๋œ ํ–‰ ์ˆ˜: ' || v_count);
    
    -- 3. ๋ฐฑ์—… ๋กœ๊ทธ ๊ธฐ๋ก
    INSERT INTO backup_log (log_id, original_table, backup_table, 
                            row_count, backup_date)
    VALUES (backup_log_seq.NEXTVAL, p_table_name, v_backup_table,
            v_count, SYSDATE);
    
    COMMIT;
    
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('๋ฐฑ์—… ์‹คํŒจ: ' || SQLERRM);
        RAISE;
END;
/

-- ์‚ฌ์šฉ ์˜ˆ์‹œ
BEGIN
    dynamic_table_backup('EMPLOYEES', 'department_id = 10');
END;
/
๋™์  SQL์€ ์œ ์—ฐํ•˜์ง€๋งŒ SQL ์ธ์ ์…˜ ์œ„ํ—˜์ด ์žˆ์œผ๋‹ˆ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์„ ์ฒ ์ €ํžˆ ํ•ด์•ผ ํ•ด! ๐Ÿ”’

ํŒจํ„ด 3: ์ปค์„œ๋ฅผ ํ™œ์šฉํ•œ ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

์—ฌ๋Ÿฌ ํ–‰์„ ์ˆœํšŒํ•˜๋ฉด์„œ ๋ณต์žกํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•  ๋•Œ ์ปค์„œ๋ฅผ ์‚ฌ์šฉํ•ด:
CREATE OR REPLACE PROCEDURE process_monthly_sales
(
    p_year IN NUMBER,
    p_month IN NUMBER
)
IS
    -- ์ปค์„œ ์„ ์–ธ
    CURSOR c_sales IS
        SELECT customer_id, SUM(total_amount) as monthly_total
        FROM orders
        WHERE EXTRACT(YEAR FROM order_date) = p_year
          AND EXTRACT(MONTH FROM order_date) = p_month
        GROUP BY customer_id
        HAVING SUM(total_amount) > 0
        ORDER BY monthly_total DESC;
    
    v_rank NUMBER := 0;
    v_bonus_rate NUMBER;
    v_bonus_amount NUMBER;
    v_total_processed NUMBER := 0;
BEGIN
    DBMS_OUTPUT.PUT_LINE('=== ' || p_year || '๋…„ ' || p_month || '์›” ๋งค์ถœ ์ฒ˜๋ฆฌ ์‹œ์ž‘ ===');
    
    -- ์ปค์„œ FOR LOOP (์ž๋™์œผ๋กœ OPEN, FETCH, CLOSE)
    FOR rec IN c_sales LOOP
        v_rank := v_rank + 1;
        
        -- ์ˆœ์œ„์— ๋”ฐ๋ฅธ ๋ณด๋„ˆ์Šค์œจ ๊ฒฐ์ •
        v_bonus_rate := CASE
            WHEN v_rank <= 10 THEN 0.05    -- ์ƒ์œ„ 10๋ช…: 5%
            WHEN v_rank <= 50 THEN 0.03    -- ์ƒ์œ„ 50๋ช…: 3%
            WHEN v_rank <= 100 THEN 0.01   -- ์ƒ์œ„ 100๋ช…: 1%
            ELSE 0
        END;
        
        v_bonus_amount := rec.monthly_total * v_bonus_rate;
        
        -- ๋ณด๋„ˆ์Šค ํฌ์ธํŠธ ์ง€๊ธ‰
        IF v_bonus_amount > 0 THEN
            UPDATE customers
            SET point_balance = point_balance + v_bonus_amount,
                last_bonus_date = SYSDATE
            WHERE customer_id = rec.customer_id;
            
            -- ๋ณด๋„ˆ์Šค ์ง€๊ธ‰ ์ด๋ ฅ ๊ธฐ๋ก
            INSERT INTO bonus_history (bonus_id, customer_id, bonus_amount,
                                       bonus_type, bonus_date)
            VALUES (bonus_seq.NEXTVAL, rec.customer_id, v_bonus_amount,
                    'MONTHLY_SALES', SYSDATE);
            
            v_total_processed := v_total_processed + 1;
        END IF;
        
        -- 100๋ช…๋งˆ๋‹ค ์ค‘๊ฐ„ ์ปค๋ฐ‹ (๋Œ€๋Ÿ‰ ์ฒ˜๋ฆฌ ์‹œ ์„ฑ๋Šฅ ํ–ฅ์ƒ)
        IF MOD(v_rank, 100) = 0 THEN
            COMMIT;
            DBMS_OUTPUT.PUT_LINE(v_rank || '๋ช… ์ฒ˜๋ฆฌ ์™„๋ฃŒ...');
        END IF;
    END LOOP;
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('=== ์ฒ˜๋ฆฌ ์™„๋ฃŒ ===');
    DBMS_OUTPUT.PUT_LINE('์ด ์ฒ˜๋ฆฌ ๊ณ ๊ฐ ์ˆ˜: ' || v_total_processed);
    
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('์ฒ˜๋ฆฌ ์‹คํŒจ: ' || SQLERRM);
        RAISE;
END;
/
์ปค์„œ๋Š” ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋‹ˆ ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ์—๋Š” BULK COLLECT๋ฅผ ๊ณ ๋ คํ•ด๋ด! ๐Ÿš€

ํŒจํ„ด 4: BULK COLLECT๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”

์ˆ˜์ฒœ, ์ˆ˜๋งŒ ๊ฑด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” BULK COLLECT๊ฐ€ ํ›จ์”ฌ ๋นจ๋ผ:
CREATE OR REPLACE PROCEDURE bulk_process_orders
(
    p_status IN VARCHAR2,
    p_batch_size IN NUMBER DEFAULT 1000
)
IS
    TYPE t_order_ids IS TABLE OF orders.order_id%TYPE;
    TYPE t_customer_ids IS TABLE OF orders.customer_id%TYPE;
    TYPE t_amounts IS TABLE OF orders.total_amount%TYPE;
    
    v_order_ids t_order_ids;
    v_customer_ids t_customer_ids;
    v_amounts t_amounts;
    
    CURSOR c_orders IS
        SELECT order_id, customer_id, total_amount
        FROM orders
        WHERE status = p_status;
    
    v_total_processed NUMBER := 0;
BEGIN
    OPEN c_orders;
    
    LOOP
        -- ํ•œ ๋ฒˆ์— p_batch_size๋งŒํผ ๊ฐ€์ ธ์˜ค๊ธฐ
        FETCH c_orders BULK COLLECT INTO 
            v_order_ids, v_customer_ids, v_amounts
        LIMIT p_batch_size;
        
        EXIT WHEN v_order_ids.COUNT = 0;
        
        -- FORALL๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ (๊ฐœ๋ณ„ UPDATE๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฆ„)
        FORALL i IN 1..v_order_ids.COUNT
            UPDATE orders
            SET status = 'PROCESSED',
                processed_date = SYSDATE
            WHERE order_id = v_order_ids(i);
        
        -- ๊ณ ๊ฐ ํฌ์ธํŠธ ์ผ๊ด„ ์—…๋ฐ์ดํŠธ
        FORALL i IN 1..v_customer_ids.COUNT
            UPDATE customers
            SET point_balance = point_balance + (v_amounts(i) * 0.01)
            WHERE customer_id = v_customer_ids(i);
        
        v_total_processed := v_total_processed + v_order_ids.COUNT;
        
        COMMIT;  -- ๋ฐฐ์น˜๋งˆ๋‹ค ์ปค๋ฐ‹
        
        DBMS_OUTPUT.PUT_LINE(v_total_processed || '๊ฑด ์ฒ˜๋ฆฌ ์™„๋ฃŒ...');
    END LOOP;
    
    CLOSE c_orders;
    
    DBMS_OUTPUT.PUT_LINE('=== ์ „์ฒด ์ฒ˜๋ฆฌ ์™„๋ฃŒ: ' || v_total_processed || '๊ฑด ===');
    
EXCEPTION
    WHEN OTHERS THEN
        IF c_orders%ISOPEN THEN
            CLOSE c_orders;
        END IF;
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('์ฒ˜๋ฆฌ ์‹คํŒจ: ' || SQLERRM);
        RAISE;
END;
/
โšก BULK COLLECT์˜ ์„ฑ๋Šฅ ์ด์ 

์ผ๋ฐ˜ ๋ฃจํ”„: 10,000๊ฑด ์ฒ˜๋ฆฌ ์‹œ ์•ฝ 30์ดˆ
BULK COLLECT: 10,000๊ฑด ์ฒ˜๋ฆฌ ์‹œ ์•ฝ 3์ดˆ

์•ฝ 10๋ฐฐ ์ด์ƒ ๋น ๋ฆ„! ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ ํ•„์ˆ˜ ๊ธฐ๋ฒ•์ด์•ผ! ๐Ÿ’จ

ํŒจํ„ด 5: ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ๋กœ๊น… ์ „๋žต

์‹ค๋ฌด์—์„œ๋Š” ์—๋Ÿฌ๋ฅผ ์ž˜ ์ฒ˜๋ฆฌํ•˜๊ณ  ๊ธฐ๋กํ•˜๋Š” ๊ฒŒ ์ •๋ง ์ค‘์š”ํ•ด:
-- ์—๋Ÿฌ ๋กœ๊ทธ ํ…Œ์ด๋ธ”
CREATE TABLE error_logs (
    error_id NUMBER PRIMARY KEY,
    procedure_name VARCHAR2(100),
    error_code NUMBER,
    error_message VARCHAR2(4000),
    error_backtrace CLOB,
    error_date TIMESTAMP,
    additional_info VARCHAR2(4000)
);

CREATE SEQUENCE error_logs_seq;

-- ์—๋Ÿฌ ๋กœ๊น… ํ”„๋กœ์‹œ์ €
CREATE OR REPLACE PROCEDURE log_error
(
    p_procedure_name IN VARCHAR2,
    p_additional_info IN VARCHAR2 DEFAULT NULL
)
IS
    PRAGMA AUTONOMOUS_TRANSACTION;  -- ๋…๋ฆฝ ํŠธ๋žœ์žญ์…˜
BEGIN
    INSERT INTO error_logs (
        error_id, procedure_name, error_code, error_message,
        error_backtrace, error_date, additional_info
    ) VALUES (
        error_logs_seq.NEXTVAL,
        p_procedure_name,
        SQLCODE,
        SQLERRM,
        DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
        SYSTIMESTAMP,
        p_additional_info
    );
    
    COMMIT;  -- ์ž์œจ ํŠธ๋žœ์žญ์…˜์ด๋ฏ€๋กœ ๊ฐ€๋Šฅ
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        -- ๋กœ๊น… ์‹คํŒจ๋Š” ์กฐ์šฉํžˆ ๋ฌด์‹œ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€)
        NULL;
END;
/

-- ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์ ์šฉํ•œ ํ”„๋กœ์‹œ์ €
CREATE OR REPLACE PROCEDURE safe_update_employee
(
    p_emp_id IN NUMBER,
    p_new_salary IN NUMBER
)
IS
    v_old_salary NUMBER;
    v_dept_id NUMBER;
BEGIN
    -- 1. ํ˜„์žฌ ์ •๋ณด ์กฐํšŒ
    SELECT salary, department_id
    INTO v_old_salary, v_dept_id
    FROM employees
    WHERE employee_id = p_emp_id;
    
    -- 2. ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ
    IF p_new_salary < v_old_salary * 0.5 THEN
        RAISE_APPLICATION_ERROR(-20100, '๊ธ‰์—ฌ๋ฅผ 50% ์ด์ƒ ์‚ญ๊ฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
    END IF;
    
    IF p_new_salary > v_old_salary * 2 THEN
        RAISE_APPLICATION_ERROR(-20101, '๊ธ‰์—ฌ๋ฅผ 2๋ฐฐ ์ด์ƒ ์ธ์ƒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
    END IF;
    
    -- 3. ์—…๋ฐ์ดํŠธ ์ˆ˜ํ–‰
    UPDATE employees
    SET salary = p_new_salary,
        last_update_date = SYSDATE
    WHERE employee_id = p_emp_id;
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('๊ธ‰์—ฌ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต: ' || v_old_salary || ' โ†’ ' || p_new_salary);
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        log_error('safe_update_employee', 
                  '์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ง์› ID: ' || p_emp_id);
        DBMS_OUTPUT.PUT_LINE('์˜ค๋ฅ˜: ํ•ด๋‹น ์ง์›์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
        
    WHEN OTHERS THEN
        ROLLBACK;
        log_error('safe_update_employee',
                  'Employee ID: ' || p_emp_id || ', New Salary: ' || p_new_salary);
        DBMS_OUTPUT.PUT_LINE('์˜ค๋ฅ˜ ๋ฐœ์ƒ: ' || SQLERRM);
        RAISE;  -- ์ƒ์œ„๋กœ ์˜ˆ์™ธ ์ „ํŒŒ
END;
/
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์›์ธ์„ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ”

๐Ÿ› ๏ธ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์‡ผํ•‘๋ชฐ ์‹œ์Šคํ…œ ๊ตฌ์ถ•

์ด์ œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ ์‡ผํ•‘๋ชฐ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ๐Ÿ›’

1๋‹จ๊ณ„: ํ…Œ์ด๋ธ” ๊ตฌ์กฐ ์„ค๊ณ„

-- ๊ณ ๊ฐ ํ…Œ์ด๋ธ”
CREATE TABLE customers (
    customer_id NUMBER PRIMARY KEY,
    customer_name VARCHAR2(100) NOT NULL,
    email VARCHAR2(200) UNIQUE,
    phone VARCHAR2(20),
    point_balance NUMBER DEFAULT 0,
    customer_grade VARCHAR2(20) DEFAULT 'BRONZE',
    total_purchase_amount NUMBER DEFAULT 0,
    total_order_count NUMBER DEFAULT 0,
    created_date DATE DEFAULT SYSDATE,
    last_order_date DATE
);

-- ์ƒํ’ˆ ํ…Œ์ด๋ธ”
CREATE TABLE products (
    product_id NUMBER PRIMARY KEY,
    product_name VARCHAR2(200) NOT NULL,
    price NUMBER NOT NULL,
    stock_quantity NUMBER DEFAULT 0,
    category VARCHAR2(50),
    is_active VARCHAR2(1) DEFAULT 'Y',
    created_date DATE DEFAULT SYSDATE
);

-- ์ฃผ๋ฌธ ํ…Œ์ด๋ธ”
CREATE TABLE orders (
    order_id NUMBER PRIMARY KEY,
    customer_id NUMBER REFERENCES customers(customer_id),
    product_id NUMBER REFERENCES products(product_id),
    quantity NUMBER NOT NULL,
    total_amount NUMBER NOT NULL,
    used_points NUMBER DEFAULT 0,
    final_amount NUMBER,
    order_date DATE DEFAULT SYSDATE,
    status VARCHAR2(20) DEFAULT 'PENDING'
);

-- ์ฃผ๋ฌธ ์ด๋ ฅ ํ…Œ์ด๋ธ”
CREATE TABLE order_history (
    history_id NUMBER PRIMARY KEY,
    order_id NUMBER REFERENCES orders(order_id),
    old_status VARCHAR2(20),
    new_status VARCHAR2(20),
    changed_date TIMESTAMP DEFAULT SYSTIMESTAMP,
    changed_by VARCHAR2(100)
);

-- ์‹œํ€€์Šค ์ƒ์„ฑ
CREATE SEQUENCE customers_seq START WITH 1000;
CREATE SEQUENCE products_seq START WITH 2000;
CREATE SEQUENCE orders_seq START WITH 3000;
CREATE SEQUENCE order_history_seq START WITH 4000;

2๋‹จ๊ณ„: ํ•ต์‹ฌ ํ”„๋กœ์‹œ์ € ๊ตฌํ˜„

์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ํ”„๋กœ์‹œ์ € (ํฌ์ธํŠธ ์‚ฌ์šฉ ํฌํ•จ):
CREATE OR REPLACE PROCEDURE create_order
(
    p_customer_id IN NUMBER,
    p_product_id IN NUMBER,
    p_quantity IN NUMBER,
    p_use_points IN NUMBER DEFAULT 0,
    p_order_id OUT NUMBER,
    p_result_code OUT NUMBER,
    p_result_msg OUT VARCHAR2
)
IS
    v_product_price NUMBER;
    v_stock_quantity NUMBER;
    v_customer_points NUMBER;
    v_total_amount NUMBER;
    v_final_amount NUMBER;
    v_earned_points NUMBER;
    
    e_insufficient_stock EXCEPTION;
    e_insufficient_points EXCEPTION;
    e_invalid_quantity EXCEPTION;
BEGIN
    -- ์ดˆ๊ธฐํ™”
    p_result_code := 0;
    
    -- 1. ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ
    IF p_quantity <= 0 THEN
        RAISE e_invalid_quantity;
    END IF;
    
    -- 2. ์ƒํ’ˆ ์ •๋ณด ์กฐํšŒ (์žฌ๊ณ  ์ž ๊ธˆ)
    SELECT price, stock_quantity
    INTO v_product_price, v_stock_quantity
    FROM products
    WHERE product_id = p_product_id
      AND is_active = 'Y'
    FOR UPDATE;
    
    -- 3. ์žฌ๊ณ  ํ™•์ธ
    IF v_stock_quantity < p_quantity THEN
        RAISE e_insufficient_stock;
    END IF;
    
    -- 4. ๊ณ ๊ฐ ํฌ์ธํŠธ ์กฐํšŒ
    SELECT point_balance
    INTO v_customer_points
    FROM customers
    WHERE customer_id = p_customer_id
    FOR UPDATE;
    
    -- 5. ํฌ์ธํŠธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ
    IF p_use_points > v_customer_points THEN
        RAISE e_insufficient_points;
    END IF;
    
    -- 6. ๊ธˆ์•ก ๊ณ„์‚ฐ
    v_total_amount := v_product_price * p_quantity;
    v_final_amount := v_total_amount - p_use_points;
    
    IF v_final_amount < 0 THEN
        v_final_amount := 0;
    END IF;
    
    -- 7. ์ ๋ฆฝ ํฌ์ธํŠธ ๊ณ„์‚ฐ (์ตœ์ข… ๊ฒฐ์ œ๊ธˆ์•ก์˜ 1%)
    v_earned_points := TRUNC(v_final_amount * 0.01);
    
    -- 8. ์ฃผ๋ฌธ ์ƒ์„ฑ
    INSERT INTO orders (
        order_id, customer_id, product_id, quantity,
        total_amount, used_points, final_amount,
        order_date, status
    ) VALUES (
        orders_seq.NEXTVAL, p_customer_id, p_product_id, p_quantity,
        v_total_amount, p_use_points, v_final_amount,
        SYSDATE, 'COMPLETED'
    ) RETURNING order_id INTO p_order_id;
    
    -- 9. ์žฌ๊ณ  ์ฐจ๊ฐ
    UPDATE products
    SET stock_quantity = stock_quantity - p_quantity
    WHERE product_id = p_product_id;
    
    -- 10. ๊ณ ๊ฐ ์ •๋ณด ์—…๋ฐ์ดํŠธ
    UPDATE customers
    SET point_balance = point_balance - p_use_points + v_earned_points,
        total_purchase_amount = total_purchase_amount + v_final_amount,
        total_order_count = total_order_count + 1,
        last_order_date = SYSDATE,
        customer_grade = CASE
            WHEN total_purchase_amount + v_final_amount >= 10000000 THEN 'VIP'
            WHEN total_purchase_amount + v_final_amount >= 5000000 THEN 'GOLD'
            WHEN total_purchase_amount + v_final_amount >= 1000000 THEN 'SILVER'
            ELSE 'BRONZE'
        END
    WHERE customer_id = p_customer_id;
    
    COMMIT;
    
    p_result_msg := '์ฃผ๋ฌธ ์™„๋ฃŒ! ์ฃผ๋ฌธ๋ฒˆํ˜ธ: ' || p_order_id || 
                    ', ์‚ฌ์šฉ ํฌ์ธํŠธ: ' || p_use_points ||
                    ', ์ ๋ฆฝ ํฌ์ธํŠธ: ' || v_earned_points;
    
EXCEPTION
    WHEN e_invalid_quantity THEN
        ROLLBACK;
        p_result_code := -1;
        p_result_msg := '์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰์€ 1๊ฐœ ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.';
        
    WHEN e_insufficient_stock THEN
        ROLLBACK;
        p_result_code := -2;
        p_result_msg := '์žฌ๊ณ  ๋ถ€์กฑ! ํ˜„์žฌ ์žฌ๊ณ : ' || v_stock_quantity || '๊ฐœ';
        
    WHEN e_insufficient_points THEN
        ROLLBACK;
        p_result_code := -3;
        p_result_msg := 'ํฌ์ธํŠธ ๋ถ€์กฑ! ๋ณด์œ  ํฌ์ธํŠธ: ' || v_customer_points;
        
    WHEN NO_DATA_FOUND THEN
        ROLLBACK;
        p_result_code := -4;
        p_result_msg := '์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ƒํ’ˆ์ด๊ฑฐ๋‚˜ ๊ณ ๊ฐ์ž…๋‹ˆ๋‹ค.';
        
    WHEN OTHERS THEN
        ROLLBACK;
        p_result_code := -99;
        p_result_msg := '์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ์‹คํŒจ: ' || SQLERRM;
        log_error('create_order', 
                  'Customer: ' || p_customer_id || ', Product: ' || p_product_id);
END;
/

3๋‹จ๊ณ„: ํŠธ๋ฆฌ๊ฑฐ ๊ตฌํ˜„

์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ ์ด๋ ฅ ํŠธ๋ฆฌ๊ฑฐ:
CREATE OR REPLACE TRIGGER trg_order_status_history
AFTER UPDATE OF status ON orders
FOR EACH ROW
WHEN (OLD.status != NEW.status)
BEGIN
    INSERT INTO order_history (
        history_id, order_id, old_status, new_status,
        changed_date, changed_by
    ) VALUES (
        order_history_seq.NEXTVAL,
        :NEW.order_id,
        :OLD.status,
        :NEW.status,
        SYSTIMESTAMP,
        USER
    );
END;
/
์žฌ๊ณ  ๋ถ€์กฑ ์•Œ๋ฆผ ํŠธ๋ฆฌ๊ฑฐ:
CREATE OR REPLACE TRIGGER trg_low_stock_alert
AFTER UPDATE OF stock_quantity ON products
FOR EACH ROW
WHEN (NEW.stock_quantity < 10 AND OLD.stock_quantity >= 10)
DECLARE
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    -- ์žฌ๊ณ  ๋ถ€์กฑ ์•Œ๋ฆผ ๋กœ๊ทธ ๊ธฐ๋ก
    INSERT INTO stock_alerts (
        alert_id, product_id, product_name,
        current_stock, alert_date
    ) VALUES (
        stock_alerts_seq.NEXTVAL,
        :NEW.product_id,
        :NEW.product_name,
        :NEW.stock_quantity,
        SYSDATE
    );
    
    COMMIT;
    
    -- ์‹ค์ œ๋กœ๋Š” ์—ฌ๊ธฐ์„œ ๊ด€๋ฆฌ์ž์—๊ฒŒ ์ด๋ฉ”์ผ์ด๋‚˜ SMS ๋ฐœ์†ก
    DBMS_OUTPUT.PUT_LINE('โš ๏ธ ์žฌ๊ณ  ๋ถ€์กฑ ์•Œ๋ฆผ: ' || :NEW.product_name || 
                         ' (์žฌ๊ณ : ' || :NEW.stock_quantity || '๊ฐœ)');
END;
/

4๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ ๋ฐ ์‹คํ–‰

-- ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ
INSERT INTO customers (customer_id, customer_name, email, phone)
VALUES (customers_seq.NEXTVAL, 'ํ™๊ธธ๋™', 'hong@example.com', '010-1234-5678');

INSERT INTO products (product_id, product_name, price, stock_quantity, category)
VALUES (products_seq.NEXTVAL, '๋…ธํŠธ๋ถ', 1500000, 50, '์ „์ž์ œํ’ˆ');

INSERT INTO products (product_id, product_name, price, stock_quantity, category)
VALUES (products_seq.NEXTVAL, '๋งˆ์šฐ์Šค', 30000, 8, '์ „์ž์ œํ’ˆ');

COMMIT;

-- ์ฃผ๋ฌธ ํ…Œ์ŠคํŠธ
DECLARE
    v_order_id NUMBER;
    v_result_code NUMBER;
    v_result_msg VARCHAR2(1000);
BEGIN
    create_order(
        p_customer_id => 1000,
        p_product_id => 2000,
        p_quantity => 2,
        p_use_points => 0,
        p_order_id => v_order_id,
        p_result_code => v_result_code,
        p_result_msg => v_result_msg
    );
    
    DBMS_OUTPUT.PUT_LINE('๊ฒฐ๊ณผ ์ฝ”๋“œ: ' || v_result_code);
    DBMS_OUTPUT.PUT_LINE('๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€: ' || v_result_msg);
END;
/

-- ๊ณ ๊ฐ ์ •๋ณด ํ™•์ธ
SELECT customer_name, point_balance, customer_grade, 
       total_purchase_amount, total_order_count
FROM customers
WHERE customer_id = 1000;

-- ์žฌ๊ณ  ํ™•์ธ
SELECT product_name, stock_quantity
FROM products
WHERE product_id = 2000;
์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š” ์‡ผํ•‘๋ชฐ ์‹œ์Šคํ…œ์ด ์™„์„ฑ๋์–ด! ๐ŸŽ‰

์‹ค์ œ๋กœ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ๋“ค์ด ์ˆ˜๋ฐฑ ๊ฐœ์”ฉ ๋Œ์•„๊ฐ€๋ฉด์„œ ๊ฑฐ๋ž˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์–ด. ๋„ˆ๋„ ์ด์ œ ์ด๋Ÿฐ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋œ ๊ฑฐ์•ผ! ๐Ÿ’ช

๐Ÿ“Š ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ๋ชจ๋‹ˆํ„ฐ๋ง

ํ”„๋กœ์‹œ์ € ์„ฑ๋Šฅ ๋ถ„์„

ํ”„๋กœ์‹œ์ €๊ฐ€ ๋А๋ฆฌ๋‹ค๋ฉด ์–ด๋””๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ์ฐพ์•„์•ผ๊ฒ ์ง€? ์„ฑ๋Šฅ ๋ถ„์„ ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค์ค„๊ฒŒ:
-- 1. ์‹คํ–‰ ๊ณ„ํš ํ™•์ธ
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- 2. ํ”„๋กœ์‹œ์ € ์‹คํ–‰ ํ†ต๊ณ„ ํ™•์ธ
SELECT name, type, executions, 
       ROUND(elapsed_time/1000000, 2) as elapsed_sec,
       ROUND(cpu_time/1000000, 2) as cpu_sec
FROM v$sqlarea
WHERE sql_text LIKE '%process_order%'
ORDER BY elapsed_time DESC;

-- 3. ํ”„๋กœํŒŒ์ผ๋ง (DBMS_PROFILER ์‚ฌ์šฉ)
BEGIN
    DBMS_PROFILER.START_PROFILER('order_process_profile');
    
    -- ํ…Œ์ŠคํŠธํ•  ํ”„๋กœ์‹œ์ € ์‹คํ–‰
    FOR i IN 1..100 LOOP
        process_order(1001, 2001, 5, NULL, NULL);
    END LOOP;
    
    DBMS_PROFILER.STOP_PROFILER;
END;
/

-- ํ”„๋กœํŒŒ์ผ๋ง ๊ฒฐ๊ณผ ์กฐํšŒ
SELECT u.unit_name, d.line#, d.total_time,
       s.text
FROM dbms_profiler_units u
JOIN dbms_profiler_data d ON u.runid = d.runid 
                          AND u.unit_number = d.unit_number
JOIN user_source s ON u.unit_name = s.name 
                  AND d.line# = s.line
WHERE u.runid = (SELECT MAX(runid) FROM dbms_profiler_runs)
ORDER BY d.total_time DESC;

์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

๐Ÿš€ ํ”„๋กœ์‹œ์ €/ํŠธ๋ฆฌ๊ฑฐ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ

1. ์ธ๋ฑ์Šค ํ™œ์šฉ
โœ… WHERE ์ ˆ์— ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค ์ƒ์„ฑ
โœ… ์กฐ์ธ ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค ํ•„์ˆ˜
โœ… ํ•˜์ง€๋งŒ ์ธ๋ฑ์Šค๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด INSERT/UPDATE ์„ฑ๋Šฅ ์ €ํ•˜

2. SQL ์ตœ์ ํ™”
โœ… SELECT *๋ณด๋‹ค๋Š” ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๋งŒ ๋ช…์‹œ
โœ… ์„œ๋ธŒ์ฟผ๋ฆฌ๋ณด๋‹ค๋Š” JOIN ์‚ฌ์šฉ (๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ)
โœ… EXISTS vs IN: ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋Š” EXISTS๊ฐ€ ์œ ๋ฆฌ
โœ… UNION ALL vs UNION: ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ํ•„์š”์—†์œผ๋ฉด UNION ALL

3. ์ปค์„œ ์ตœ์ ํ™”
โœ… ๊ฐ€๋Šฅํ•˜๋ฉด BULK COLLECT ์‚ฌ์šฉ
โœ… LIMIT ์ ˆ๋กœ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ œ์–ด
โœ… ์ปค์„œ๋Š” ๋ฐ˜๋“œ์‹œ CLOSE (๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€)

4. ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
โœ… ๋Œ€๋Ÿ‰ ์ฒ˜๋ฆฌ ์‹œ ์ ์ ˆํ•œ ๋ฐฐ์น˜ ํฌ๊ธฐ๋กœ COMMIT
โœ… ๋ถˆํ•„์š”ํ•œ COMMIT/ROLLBACK ์ตœ์†Œํ™”
โœ… ๊ธด ํŠธ๋žœ์žญ์…˜์€ ๋ฝ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ฆ๊ฐ€

5. ํŠธ๋ฆฌ๊ฑฐ ์ตœ์ ํ™”
โœ… ํŠธ๋ฆฌ๊ฑฐ ๋‚ด ๋ณต์žกํ•œ ๋กœ์ง์€ ํ”„๋กœ์‹œ์ €๋กœ ๋ถ„๋ฆฌ
โœ… WHEN ์ ˆ๋กœ ๋ถˆํ•„์š”ํ•œ ์‹คํ–‰ ๋ฐฉ์ง€
โœ… ํŠธ๋ฆฌ๊ฑฐ ์ฒด์ธ ์ตœ์†Œํ™” (Aโ†’Bโ†’C ๊ฐ™์€ ์—ฐ์‡„ ์‹คํ–‰)
โœ… ์ž์œจ ํŠธ๋žœ์žญ์…˜์€ ๊ผญ ํ•„์š”ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉ

๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์ถ•

์‹ค์‹œ๊ฐ„์œผ๋กœ ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ์˜ ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:
-- ํ”„๋กœ์‹œ์ € ์‹คํ–‰ ํ†ต๊ณ„ ๋ทฐ
CREATE OR REPLACE VIEW v_procedure_stats AS
SELECT 
    object_name,
    object_type,
    status,
    created,
    last_ddl_time
FROM user_objects
WHERE object_type IN ('PROCEDURE', 'FUNCTION', 'TRIGGER')
ORDER BY last_ddl_time DESC;

-- ์—๋Ÿฌ ๋ฐœ์ƒ ํ†ต๊ณ„ ๋ทฐ
CREATE OR REPLACE VIEW v_error_summary AS
SELECT 
    procedure_name,
    error_code,
    COUNT(*) as error_count,
    MAX(error_date) as last_error_date
FROM error_logs
WHERE error_date >= SYSDATE - 7  -- ์ตœ๊ทผ 7์ผ
GROUP BY procedure_name, error_code
ORDER BY error_count DESC;

-- ์„ฑ๋Šฅ ๋ฌธ์ œ ํ”„๋กœ์‹œ์ € ์กฐํšŒ
SELECT * FROM v_procedure_stats WHERE status = 'INVALID';
SELECT * FROM v_error_summary WHERE error_count > 10;

๐ŸŽ“ ์‹ค๋ฌด ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์ฝ”๋”ฉ ์ปจ๋ฒค์…˜

ํŒ€์—์„œ ์ผํ•  ๋•Œ๋Š” ์ผ๊ด€๋œ ์ฝ”๋”ฉ ์Šคํƒ€์ผ์ด ์ •๋ง ์ค‘์š”ํ•ด:
๐Ÿ“ PL/SQL ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜ ๊ฐ€์ด๋“œ

1. ๋ช…๋ช… ๊ทœ์น™
โ€ข ํ”„๋กœ์‹œ์ €/ํ•จ์ˆ˜: ๋™์‚ฌ๋กœ ์‹œ์ž‘ (get_employee, update_salary)
โ€ข ํŠธ๋ฆฌ๊ฑฐ: trg_ ์ ‘๋‘์‚ฌ (trg_employee_audit)
โ€ข ํŒŒ๋ผ๋ฏธํ„ฐ: p_ ์ ‘๋‘์‚ฌ (p_emp_id, p_salary)
โ€ข ๋ณ€์ˆ˜: v_ ์ ‘๋‘์‚ฌ (v_total_amount, v_count)
โ€ข ์ปค์„œ: c_ ์ ‘๋‘์‚ฌ (c_employees, c_orders)
โ€ข ์ƒ์ˆ˜: c_ ์ ‘๋‘์‚ฌ + ๋Œ€๋ฌธ์ž (c_MAX_SALARY)
โ€ข ์˜ˆ์™ธ: e_ ์ ‘๋‘์‚ฌ (e_invalid_data)

2. ๋“ค์—ฌ์“ฐ๊ธฐ์™€ ํฌ๋งทํŒ…
โ€ข ๋“ค์—ฌ์“ฐ๊ธฐ: 4์นธ ๊ณต๋ฐฑ (ํƒญ ๋Œ€์‹  ์ŠคํŽ˜์ด์Šค)
โ€ข SQL ํ‚ค์›Œ๋“œ: ๋Œ€๋ฌธ์ž (SELECT, FROM, WHERE)
โ€ข ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ๋ช…: ์†Œ๋ฌธ์ž ๋˜๋Š” ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค
โ€ข ํ•œ ์ค„์— ํ•˜๋‚˜์˜ ๋ช…๋ น๋ฌธ

3. ์ฃผ์„ ์ž‘์„ฑ
โ€ข ํ”„๋กœ์‹œ์ € ์ƒ๋‹จ์— ๋ชฉ์ , ํŒŒ๋ผ๋ฏธํ„ฐ, ์ž‘์„ฑ์ž, ์ž‘์„ฑ์ผ ๋ช…์‹œ
โ€ข ๋ณต์žกํ•œ ๋กœ์ง์€ ๋ฐ˜๋“œ์‹œ ์ฃผ์„ ์ถ”๊ฐ€
โ€ข ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์€ ์ƒ์„ธํžˆ ์„ค๋ช…

4. ์—๋Ÿฌ ์ฒ˜๋ฆฌ
โ€ข ๋ชจ๋“  ํ”„๋กœ์‹œ์ €์— EXCEPTION ๋ธ”๋ก ํ•„์ˆ˜
โ€ข ์‚ฌ์šฉ์ž ์ •์˜ ์—๋Ÿฌ๋Š” -20000 ~ -20999 ๋ฒ”์œ„ ์‚ฌ์šฉ
โ€ข ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋Š” ๋ช…ํ™•ํ•˜๊ณ  ๊ตฌ์ฒด์ ์œผ๋กœ

5. ๋ณด์•ˆ
โ€ข ๋™์  SQL ์‚ฌ์šฉ ์‹œ SQL ์ธ์ ์…˜ ๋ฐฉ์ง€
โ€ข ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์•”ํ˜ธํ™”
โ€ข ๊ถŒํ•œ์€ ์ตœ์†Œํ•œ์œผ๋กœ๋งŒ ๋ถ€์—ฌ

์ฃผ์„ ํ…œํ”Œ๋ฆฟ ์˜ˆ์‹œ

/*******************************************************************************
* ํ”„๋กœ์‹œ์ €๋ช…: process_monthly_salary
* ์„ค๋ช…: ์›”๋ณ„ ๊ธ‰์—ฌ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ์ง€๊ธ‰ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
* 
* ํŒŒ๋ผ๋ฏธํ„ฐ:
*   p_year_month (IN)  : ์ฒ˜๋ฆฌํ•  ๋…„์›” (YYYYMM ํ˜•์‹)
*   p_dept_id (IN)     : ๋ถ€์„œ ID (NULL์ด๋ฉด ์ „์ฒด ๋ถ€์„œ)
*   p_result_count (OUT): ์ฒ˜๋ฆฌ๋œ ์ง์› ์ˆ˜
*   p_result_msg (OUT) : ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
*
* ์ž‘์„ฑ์ž: ํ™๊ธธ๋™
* ์ž‘์„ฑ์ผ: 2024-01-15
* ์ˆ˜์ •์ด๋ ฅ:
*   2024-01-20 ๊น€์ฒ ์ˆ˜ - ๋ณด๋„ˆ์Šค ๊ณ„์‚ฐ ๋กœ์ง ์ถ”๊ฐ€
*   2024-02-01 ์ด์˜ํฌ - ์„ฑ๋Šฅ ์ตœ์ ํ™” (BULK COLLECT ์ ์šฉ)
*
* ์‚ฌ์šฉ์˜ˆ์‹œ:
*   DECLARE
*       v_count NUMBER;
*       v_msg VARCHAR2(1000);
*   BEGIN
*       process_monthly_salary('202401', NULL, v_count, v_msg);
*       DBMS_OUTPUT.PUT_LINE(v_msg);
*   END;
*******************************************************************************/
CREATE OR REPLACE PROCEDURE process_monthly_salary
(
    p_year_month IN VARCHAR2,
    p_dept_id IN NUMBER DEFAULT NULL,
    p_result_count OUT NUMBER,
    p_result_msg OUT VARCHAR2
)
IS
    -- ๋ณ€์ˆ˜ ์„ ์–ธ
    v_base_date DATE;
    v_total_amount NUMBER := 0;
    
BEGIN
    -- ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง...
END process_monthly_salary;
/

๋ฒ„์ „ ๊ด€๋ฆฌ์™€ ๋ฐฐํฌ

ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ๋„ ์†Œ์Šค ์ฝ”๋“œ๋‹ˆ๊นŒ ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด:
๐Ÿ”„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด ๋ฒ„์ „ ๊ด€๋ฆฌ ์ „๋žต

1. ์†Œ์Šค ์ฝ”๋“œ ๊ด€๋ฆฌ
โ€ข Git ๊ฐ™์€ VCS์— ๋ชจ๋“  DDL ์Šคํฌ๋ฆฝํŠธ ์ €์žฅ
โ€ข ํŒŒ์ผ๋ช… ๊ทœ์น™: ๊ฐ์ฒดํƒ€์ž…_๊ฐ์ฒด๋ช…_๋ฒ„์ „.sql
์˜ˆ) PROC_process_order_v1.0.sql
โ€ข ๋ณ€๊ฒฝ ์ด๋ ฅ์„ ์ฃผ์„์œผ๋กœ ์ƒ์„ธํžˆ ๊ธฐ๋ก

2. ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ
โ€ข ๊ฐœ๋ฐœ โ†’ ํ…Œ์ŠคํŠธ โ†’ ์šด์˜ ์ˆœ์„œ๋กœ ๋ฐฐํฌ
โ€ข ๋กค๋ฐฑ ์Šคํฌ๋ฆฝํŠธ๋„ ํ•จ๊ป˜ ์ค€๋น„
โ€ข ๋ฐฐํฌ ์ „ ๋ฐ˜๋“œ์‹œ ๋ฐฑ์—…

3. ํ…Œ์ŠคํŠธ
โ€ข ๋‹จ์œ„ ํ…Œ์ŠคํŠธ: ๊ฐ ํ”„๋กœ์‹œ์ €๋ณ„ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ž‘์„ฑ
โ€ข ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ: ์ „์ฒด ํ”„๋กœ์„ธ์Šค ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ
โ€ข ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ: ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋กœ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ

4. ๋ฌธ์„œํ™”
โ€ข API ๋ฌธ์„œ: ๊ฐ ํ”„๋กœ์‹œ์ €์˜ ์‚ฌ์šฉ๋ฒ• ์ •๋ฆฌ
โ€ข ERD: ํ…Œ์ด๋ธ” ๊ด€๊ณ„๋„ ์ตœ์‹  ์ƒํƒœ ์œ ์ง€
โ€ข ํ”Œ๋กœ์šฐ์ฐจํŠธ: ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹œ๊ฐํ™”

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์˜ค๋А๋ผ ์ •๋ง ์ˆ˜๊ณ ํ–ˆ์–ด! ๐ŸŽ‰

์šฐ๋ฆฌ๊ฐ€ ํ•จ๊ป˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด์ž:

โœจ ํ•ต์‹ฌ ์š”์•ฝ

ํ”„๋กœ์‹œ์ €๋Š” ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ๋ชจ๋“ˆํ™”ํ•ด์„œ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์—ฌ์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์•ผ. ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ†ตํ•ด ์œ ์—ฐํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ , ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊น”๋”ํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ”ง

ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜์–ด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•˜๊ณ , ๊ฐ์‚ฌ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ , ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ํˆฌ๋ช…ํ•˜๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜. ํ•˜์ง€๋งŒ ๊ณผ๋„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ ๋ฌธ์ œ๋‚˜ ๋””๋ฒ„๊น… ์–ด๋ ค์›€์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ด. โšก

์‹ค๋ฌด์—์„œ๋Š” ํ”„๋กœ์‹œ์ €์™€ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ ์ ˆํžˆ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ํ”„๋กœ์‹œ์ €๋กœ ์ฃผ์š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๊ณ , ํŠธ๋ฆฌ๊ฑฐ๋กœ ์ž๋™ํ™”๊ฐ€ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑฐ์ง€. ๊ทธ๋ฆฌ๊ณ  ํ•ญ์ƒ ์„ฑ๋Šฅ, ๋ณด์•ˆ, ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ด! ๐Ÿ’ก

๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ

์ด์ œ ๊ธฐ๋ณธ๊ธฐ๋Š” ํƒ„ํƒ„ํ•ด์กŒ์œผ๋‹ˆ, ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๋ณด์ž:

1๏ธโƒฃ ํŒจํ‚ค์ง€(Package) ํ•™์Šต: ๊ด€๋ จ๋œ ํ”„๋กœ์‹œ์ €์™€ ํ•จ์ˆ˜๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ๊ด€๋ฆฌ
2๏ธโƒฃ ๊ณ ๊ธ‰ ์ปค์„œ ํ™œ์šฉ: REF CURSOR, ์ปค์„œ ๋ณ€์ˆ˜ ๋“ฑ
3๏ธโƒฃ ๋™์  SQL ๋งˆ์Šคํ„ฐ: EXECUTE IMMEDIATE์˜ ๊ณ ๊ธ‰ ํ™œ์šฉ
4๏ธโƒฃ ์„ฑ๋Šฅ ํŠœ๋‹: ์‹คํ–‰ ๊ณ„ํš ๋ถ„์„, ์ธ๋ฑ์Šค ์ตœ์ ํ™”
5๏ธโƒฃ ๋ณด์•ˆ ๊ฐ•ํ™”: ์•”ํ˜ธํ™”, ๊ถŒํ•œ ๊ด€๋ฆฌ, SQL ์ธ์ ์…˜ ๋ฐฉ์–ด
6๏ธโƒฃ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์‹ค์ œ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋ฉฐ ๊ฒฝํ—˜ ์Œ“๊ธฐ

๋งˆ์ง€๋ง‰ ์กฐ์–ธ

ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ์ด๋ก ๋งŒ์œผ๋กœ๋Š” ์ ˆ๋Œ€ ๋Š˜์ง€ ์•Š์•„. ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ์—๋Ÿฌ๋ฅผ ๋งŒ๋‚˜๊ณ , ํ•ด๊ฒฐํ•˜๋Š” ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•ด์•ผ ์ง„์งœ ์‹ค๋ ฅ์ด ์ƒ๊ฒจ. ๐Ÿ’ช

์ฒ˜์Œ์—๋Š” ๊ฐ„๋‹จํ•œ ํ”„๋กœ์‹œ์ €๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ, ์ ์  ๋ณต์žกํ•œ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด๋ณด์ž. ์—๋Ÿฌ๊ฐ€ ๋‚˜๋„ ์ขŒ์ ˆํ•˜์ง€ ๋ง๊ณ , ๊ทธ๊ฒŒ ๋ฐ”๋กœ ๋ฐฐ์šฐ๋Š” ๊ณผ์ •์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด. ๋ชจ๋“  ๊ณ ์ˆ˜๋“ค๋„ ๋‹ค ๊ทธ ๊ณผ์ •์„ ๊ฑฐ์ณค์–ด! ๐Ÿ˜Š

๊ทธ๋ฆฌ๊ณ  ํ•ญ์ƒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋Š” "๋‚˜์ค‘์— ์ด ์ฝ”๋“œ๋ฅผ ๋ณด๋Š” ์‚ฌ๋žŒ(๋ฏธ๋ž˜์˜ ๋‚˜ ํฌํ•จ!)์ด ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šธ๊นŒ?"๋ฅผ ์ƒ๊ฐํ•˜๋ฉฐ ์ž‘์„ฑํ•˜์ž. ๊น”๋”ํ•œ ์ฝ”๋“œ, ๋ช…ํ™•ํ•œ ์ฃผ์„, ์ผ๊ด€๋œ ์Šคํƒ€์ผ์ด ์ •๋ง ์ค‘์š”ํ•ด! ๐Ÿ“

๐Ÿ’ฌ ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๊ธฐ

ํ˜ผ์ž ๊ณต๋ถ€ํ•˜๋‹ค ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฉด ์ฃผ์ €ํ•˜์ง€ ๋ง๊ณ  ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•ด๋ด. Oracle ๊ณต์‹ ๋ฌธ์„œ, Stack Overflow, ๊ตญ๋‚ด ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ ๋“ฑ ๋„์›€๋ฐ›์„ ๊ณณ์€ ๋งŽ์•„!

๊ทธ๋ฆฌ๊ณ  ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜์„ ์Œ“๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€๋‹ค ๋ณด๋ฉด, ์–ด๋А์ƒˆ ํ”„๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด ์žˆ์„ ๊ฑฐ์•ผ! ๐ŸŒŸ
์ž, ์ด์ œ ์ •๋ง๋กœ ๋์ด์•ผ! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์–ด์ค˜์„œ ๊ณ ๋งˆ์›Œ. ๋„ˆ์˜ ๊ฐœ๋ฐœ์ž ์—ฌ์ •์— ์ด ๊ธ€์ด ์ž‘์€ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด. ํ™”์ดํŒ…! ๐Ÿš€๐Ÿ’ปโœจ

๐ŸŽŠ Oracle PL/SQL ๋งˆ์Šคํ„ฐ๋ฅผ ํ–ฅํ•œ ์—ฌ์ •, ์‹œ์ž‘! ๐ŸŽŠ

Keep Coding, Keep Learning! ๐Ÿ’ช

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.