未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合在隔离环境使用虚构数据学习PL/SQL函数、包规范与包体、流水线返回及日志组织,纯计算示例可单独练习;银行业务示例缺少业务表、日志表、序列的DDL、约束和样例数据,不能据此独立复现完整项目。客户分级、评分、AML阈值和冻结规则仅为教学假设;仅有负债时负债率被记为零、缺失汇率返回1及异常哨兵值均不能直接作为有效业务结果。内部提交可能影响调用方事务,批量冻结与并发限额检查不保证原子性或并发安全;含写入、提交或特殊返回类型的接口须遵守SQL调用限制。format_currency的NLS依赖已明确,但format_amount仍声明DETERMINISTIC且异常分支使用依赖会话设置的TO_CHAR;账号脱敏参数也未完整校验,不能作为敏感数据保护保证。本文不应直接用于真实交易、风控或安全保护;本次仅作全文结构审阅,未作外部事实核验或数据库编译运行验证。

Oracle函数与包

Oracle函数概述

Oracle函数是返回一个值的PL/SQL程序单元,可以接受参数;只有满足SQL调用限制、参数与返回类型兼容的函数才能在SQL语句中调用,本文含写表或提交事务的函数不能直接作为查询函数使用。在银行业务中,函数常用于数据计算、格式转换、业务规则实现等场景。

1. 函数的基本语法

CREATE [OR REPLACE] FUNCTION function_name
    [(parameter_name [IN | OUT | IN OUT] datatype [, ...])]
RETURN datatype
[DETERMINISTIC]
[PARALLEL_ENABLE]
AS|IS
    -- 声明部分
BEGIN
    -- 执行部分
    RETURN expression;
EXCEPTION
    -- 异常处理部分
END function_name;

银行业务函数实例

1. 金融计算函数

-- 复利计算函数
CREATE OR REPLACE FUNCTION calculate_compound_interest(
    p_principal IN NUMBER,      -- 本金
    p_annual_rate IN NUMBER,    -- 年利率
    p_years IN NUMBER,          -- 投资年限
    p_compound_frequency IN NUMBER DEFAULT 12  -- 复利频率(月)
) RETURN NUMBER
DETERMINISTIC
AS
    v_result NUMBER;
BEGIN
    -- 复利公式: A = P(1 + r/n)^(nt)
    v_result := p_principal * POWER(
        1 + (p_annual_rate / p_compound_frequency), 
        p_compound_frequency * p_years
    );
    
    -- 四舍五入到分
    RETURN ROUND(v_result, 2);
EXCEPTION
    WHEN OTHERS THEN
        RETURN NULL;
END calculate_compound_interest;
/

-- 贷款月供计算函数
CREATE OR REPLACE FUNCTION calculate_monthly_payment(
    p_loan_amount IN NUMBER,    -- 贷款金额
    p_annual_rate IN NUMBER,    -- 年利率
    p_months IN NUMBER          -- 贷款月数
) RETURN NUMBER
DETERMINISTIC
AS
    v_monthly_rate NUMBER;
    v_payment NUMBER;
BEGIN
    -- 月利率
    v_monthly_rate := p_annual_rate / 12;
    
    -- 等额本息月供公式
    IF v_monthly_rate = 0 THEN
        v_payment := p_loan_amount / p_months;
    ELSE
        v_payment := p_loan_amount * v_monthly_rate * 
                    POWER(1 + v_monthly_rate, p_months) /
                    (POWER(1 + v_monthly_rate, p_months) - 1);
    END IF;
    
    RETURN ROUND(v_payment, 2);
EXCEPTION
    WHEN OTHERS THEN
        RETURN NULL;
END calculate_monthly_payment;
/

-- 年化收益率计算函数
CREATE OR REPLACE FUNCTION calculate_annual_return(
    p_initial_value IN NUMBER,  -- 初始价值
    p_final_value IN NUMBER,    -- 最终价值
    p_days IN NUMBER            -- 投资天数
) RETURN NUMBER
DETERMINISTIC
AS
    v_annual_return NUMBER;
BEGIN
    IF p_initial_value <= 0 OR p_days <= 0 THEN
        RETURN NULL;
    END IF;
    
    -- 年化收益率 = (最终价值/初始价值)^(365/天数) - 1
    v_annual_return := POWER(p_final_value / p_initial_value, 365 / p_days) - 1;
    
    RETURN ROUND(v_annual_return * 100, 4); -- 返回百分比,保留4位小数
EXCEPTION
    WHEN OTHERS THEN
        RETURN NULL;
END calculate_annual_return;
/

-- 使用示例
SELECT 
    calculate_compound_interest(100000, 0.05, 5, 12) as compound_interest,
    calculate_monthly_payment(300000, 0.045, 360) as monthly_payment,
    calculate_annual_return(100000, 110000, 365) as annual_return_pct
FROM dual;

2. 业务规则函数

-- 客户等级评定函数
CREATE OR REPLACE FUNCTION get_customer_grade(
    p_customer_id IN VARCHAR2
) RETURN VARCHAR2
AS
    v_total_assets NUMBER;
    v_product_count NUMBER;
    v_relationship_months NUMBER;
    v_transaction_volume NUMBER;
    v_grade VARCHAR2(20);
BEGIN
    -- 获取客户资产信息
    SELECT 
        NVL(SUM(balance), 0),
        COUNT(DISTINCT product_id)
    INTO v_total_assets, v_product_count
    FROM account_info
    WHERE customer_id = p_customer_id
    AND status = 'ACTIVE';
    
    -- 获取客户关系长度
    SELECT MONTHS_BETWEEN(SYSDATE, MIN(create_time))
    INTO v_relationship_months
    FROM customer_info
    WHERE customer_id = p_customer_id;
    
    -- 获取年交易量
    SELECT NVL(SUM(amount), 0)
    INTO v_transaction_volume
    FROM transaction_log tl
    JOIN account_info ai ON tl.account_id = ai.account_id
    WHERE ai.customer_id = p_customer_id
    AND tl.trans_time >= ADD_MONTHS(SYSDATE, -12);
    
    -- 评定等级
    IF v_total_assets >= 10000000 AND v_product_count >= 5 THEN
        v_grade := 'DIAMOND';
    ELSIF v_total_assets >= 1000000 AND v_product_count >= 3 THEN
        v_grade := 'PLATINUM';
    ELSIF v_total_assets >= 500000 OR v_transaction_volume >= 500000 THEN
        v_grade := 'GOLD';
    ELSIF v_total_assets >= 100000 OR v_relationship_months >= 24 THEN
        v_grade := 'SILVER';
    ELSE
        v_grade := 'BRONZE';
    END IF;
    
    RETURN v_grade;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 'UNKNOWN';
    WHEN OTHERS THEN
        RETURN 'ERROR';
END get_customer_grade;
/

-- 风险评分函数
CREATE OR REPLACE FUNCTION calculate_risk_score(
    p_customer_id IN VARCHAR2
) RETURN NUMBER
AS
    v_age NUMBER;
    v_income NUMBER;
    v_debt_ratio NUMBER;
    v_overdue_count NUMBER;
    v_risk_score NUMBER := 0;
BEGIN
    -- 获取客户基本信息
    SELECT 
        FLOOR(MONTHS_BETWEEN(SYSDATE, birth_date) / 12),
        NVL(annual_income, 0)
    INTO v_age, v_income
    FROM customer_info
    WHERE customer_id = p_customer_id;
    
    -- 计算负债率
    SELECT 
        CASE 
            WHEN SUM(CASE WHEN balance > 0 THEN balance ELSE 0 END) = 0 THEN 0
            ELSE SUM(CASE WHEN balance < 0 THEN ABS(balance) ELSE 0 END) / 
                 SUM(CASE WHEN balance > 0 THEN balance ELSE 0 END)
        END
    INTO v_debt_ratio
    FROM account_info
    WHERE customer_id = p_customer_id;
    
    -- 计算逾期次数
    SELECT COUNT(*)
    INTO v_overdue_count
    FROM loan_overdue_history
    WHERE customer_id = p_customer_id
    AND overdue_date >= ADD_MONTHS(SYSDATE, -24);
    
    -- 风险评分计算
    -- 年龄评分 (20分)
    v_risk_score := v_risk_score + 
        CASE 
            WHEN v_age BETWEEN 25 AND 55 THEN 20
            WHEN v_age BETWEEN 18 AND 65 THEN 15
            ELSE 5
        END;
    
    -- 收入评分 (30分)
    v_risk_score := v_risk_score +
        CASE 
            WHEN v_income >= 500000 THEN 30
            WHEN v_income >= 200000 THEN 25
            WHEN v_income >= 100000 THEN 20
            WHEN v_income >= 50000 THEN 15
            ELSE 5
        END;
    
    -- 负债率评分 (30分)
    v_risk_score := v_risk_score +
        CASE 
            WHEN v_debt_ratio <= 0.3 THEN 30
            WHEN v_debt_ratio <= 0.5 THEN 20
            WHEN v_debt_ratio <= 0.7 THEN 10
            ELSE 0
        END;
    
    -- 信用记录评分 (20分)
    v_risk_score := v_risk_score +
        CASE 
            WHEN v_overdue_count = 0 THEN 20
            WHEN v_overdue_count <= 2 THEN 10
            WHEN v_overdue_count <= 5 THEN 5
            ELSE 0
        END;
    
    RETURN v_risk_score;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 0;
    WHEN OTHERS THEN
        RETURN -1;
END calculate_risk_score;
/

3. 数据格式化函数

-- 金额格式化函数
CREATE OR REPLACE FUNCTION format_amount(
    p_amount IN NUMBER,
    p_currency IN VARCHAR2 DEFAULT 'CNY'
) RETURN VARCHAR2
DETERMINISTIC
AS
    v_formatted VARCHAR2(100);
    v_currency_symbol VARCHAR2(10);
BEGIN
    -- 获取货币符号
    v_currency_symbol := CASE p_currency
        WHEN 'CNY' THEN '¥'
        WHEN 'USD' THEN '$'
        WHEN 'EUR' THEN '€'
        WHEN 'GBP' THEN '£'
        ELSE p_currency || ' '
    END;
    
    -- 格式化金额
    IF ABS(p_amount) >= 100000000 THEN -- 亿
        v_formatted := v_currency_symbol || TO_CHAR(p_amount / 100000000, 'FM999,990.00') || '亿';
    ELSIF ABS(p_amount) >= 10000 THEN -- 万
        v_formatted := v_currency_symbol || TO_CHAR(p_amount / 10000, 'FM999,990.00') || '万';
    ELSE
        v_formatted := v_currency_symbol || TO_CHAR(p_amount, 'FM999,990.00');
    END IF;
    
    RETURN v_formatted;
EXCEPTION
    WHEN OTHERS THEN
        RETURN TO_CHAR(p_amount);
END format_amount;
/

-- 账号脱敏函数
CREATE OR REPLACE FUNCTION mask_account_number(
    p_account_number IN VARCHAR2,
    p_mask_char IN VARCHAR2 DEFAULT '*',
    p_show_first IN NUMBER DEFAULT 4,
    p_show_last IN NUMBER DEFAULT 4
) RETURN VARCHAR2
DETERMINISTIC
AS
    v_masked VARCHAR2(100);
    v_length NUMBER;
BEGIN
    v_length := LENGTH(p_account_number);
    
    IF v_length <= p_show_first + p_show_last THEN
        -- 如果账号太短,只显示部分
        RETURN SUBSTR(p_account_number, 1, 2) || 
               RPAD(p_mask_char, v_length - 2, p_mask_char);
    ELSE
        -- 正常脱敏
        v_masked := SUBSTR(p_account_number, 1, p_show_first) ||
                   RPAD(p_mask_char, v_length - p_show_first - p_show_last, p_mask_char) ||
                   SUBSTR(p_account_number, -p_show_last);
    END IF;
    
    RETURN v_masked;
EXCEPTION
    WHEN OTHERS THEN
        RAISE;
END mask_account_number;
/

-- 身份证脱敏函数
CREATE OR REPLACE FUNCTION mask_id_card(
    p_id_card IN VARCHAR2
) RETURN VARCHAR2
DETERMINISTIC
AS
BEGIN
    IF LENGTH(p_id_card) = 18 THEN
        RETURN SUBSTR(p_id_card, 1, 6) || '********' || SUBSTR(p_id_card, 15);
    ELSIF LENGTH(p_id_card) = 15 THEN
        RETURN SUBSTR(p_id_card, 1, 6) || '******' || SUBSTR(p_id_card, 13);
    ELSE
        RAISE_APPLICATION_ERROR(-20003, '身份证长度不符合本示例要求,拒绝返回原值');
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        RAISE;
END mask_id_card;
/

-- 使用示例
SELECT 
    format_amount(1234567.89, 'CNY') as formatted_cny,
    format_amount(50000, 'USD') as formatted_usd,
    mask_account_number('6225881234567890') as masked_account,
    mask_id_card('110101199001011234') as masked_id
FROM dual;

Oracle包(Package)详解

1. 包的基本概念

Oracle包是相关函数、存储过程、变量、常量、游标、异常的集合,分为包规范(Package Specification)和包体(Package Body)两部分。

2. 银行客户服务包

-- 客户服务包规范
CREATE OR REPLACE PACKAGE pkg_customer_service AS
    
    -- 公共常量
    c_max_daily_withdraw CONSTANT NUMBER := 50000;
    c_max_daily_transfer CONSTANT NUMBER := 200000;
    
    -- 公共类型
    TYPE t_account_list IS TABLE OF VARCHAR2(20);
    
    TYPE t_customer_summary IS RECORD (
        customer_id VARCHAR2(20),
        customer_name VARCHAR2(100),
        total_balance NUMBER(15,2),
        account_count NUMBER(5),
        last_transaction_date DATE
    );
    
    -- 公共异常
    e_customer_not_found EXCEPTION;
    e_account_frozen EXCEPTION;
    e_insufficient_balance EXCEPTION;
    
    -- 公共函数和过程声明
    FUNCTION get_customer_summary(
        p_customer_id IN VARCHAR2
    ) RETURN t_customer_summary;
    
    FUNCTION validate_transaction_limit(
        p_customer_id IN VARCHAR2,
        p_trans_type IN VARCHAR2,
        p_amount IN NUMBER
    ) RETURN BOOLEAN;
    
    PROCEDURE update_customer_info(
        p_customer_id IN VARCHAR2,
        p_phone IN VARCHAR2 DEFAULT NULL,
        p_email IN VARCHAR2 DEFAULT NULL,
        p_address IN VARCHAR2 DEFAULT NULL
    );
    
    PROCEDURE freeze_account(
        p_account_id IN VARCHAR2,
        p_reason IN VARCHAR2
    );
    
    PROCEDURE unfreeze_account(
        p_account_id IN VARCHAR2
    );
    
    FUNCTION get_customer_accounts(
        p_customer_id IN VARCHAR2
    ) RETURN t_account_list PIPELINED;
    
END pkg_customer_service;
/

-- 客户服务包体
CREATE OR REPLACE PACKAGE BODY pkg_customer_service AS
    
    -- 私有变量
    g_package_version CONSTANT VARCHAR2(10) := '1.0.0';
    g_debug_mode BOOLEAN := FALSE;
    
    -- 私有函数
    FUNCTION log_debug(p_message VARCHAR2) RETURN BOOLEAN AS
    BEGIN
        IF g_debug_mode THEN
            DBMS_OUTPUT.PUT_LINE('[DEBUG] ' || TO_CHAR(SYSDATE, 'HH24:MI:SS') || ' - ' || p_message);
        END IF;
        RETURN TRUE;
    END log_debug;
    
    -- 公共函数实现
    FUNCTION get_customer_summary(
        p_customer_id IN VARCHAR2
    ) RETURN t_customer_summary AS
        v_summary t_customer_summary;
    BEGIN
        -- 获取客户基本信息
        SELECT customer_id, customer_name
        INTO v_summary.customer_id, v_summary.customer_name
        FROM customer_info
        WHERE customer_id = p_customer_id;
        
        -- 获取账户汇总信息
        SELECT 
            NVL(SUM(balance), 0),
            COUNT(*),
            MAX(last_trans_date)
        INTO 
            v_summary.total_balance,
            v_summary.account_count,
            v_summary.last_transaction_date
        FROM account_info
        WHERE customer_id = p_customer_id
        AND status = 'ACTIVE';
        
        RETURN v_summary;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE e_customer_not_found;
        WHEN OTHERS THEN
            RAISE;
    END get_customer_summary;
    
    FUNCTION validate_transaction_limit(
        p_customer_id IN VARCHAR2,
        p_trans_type IN VARCHAR2,
        p_amount IN NUMBER
    ) RETURN BOOLEAN AS
        v_daily_amount NUMBER;
        v_limit NUMBER;
        v_result BOOLEAN := TRUE;
    BEGIN
        -- 获取当日累计交易金额
        SELECT NVL(SUM(amount), 0)
        INTO v_daily_amount
        FROM transaction_log tl
        JOIN account_info ai ON tl.account_id = ai.account_id
        WHERE ai.customer_id = p_customer_id
        AND tl.trans_type = p_trans_type
        AND TRUNC(tl.trans_time) = TRUNC(SYSDATE)
        AND tl.status = 'SUCCESS';
        
        -- 获取限额
        v_limit := CASE p_trans_type
            WHEN 'WITHDRAW' THEN c_max_daily_withdraw
            WHEN 'TRANSFER' THEN c_max_daily_transfer
            ELSE 999999999
        END;
        
        -- 检查是否超限
        IF v_daily_amount + p_amount > v_limit THEN
            v_result := FALSE;
        END IF;
        
        -- 记录日志
        INSERT INTO transaction_limit_log (
            customer_id, trans_type, amount, daily_total, 
            limit_amount, is_approved, check_time
        ) VALUES (
            p_customer_id, p_trans_type, p_amount, v_daily_amount,
            v_limit, CASE WHEN v_result THEN 'Y' ELSE 'N' END, SYSDATE
        );
        
        COMMIT;
        RETURN v_result;
    EXCEPTION
        WHEN OTHERS THEN
            RETURN FALSE;
    END validate_transaction_limit;
    
    PROCEDURE update_customer_info(
        p_customer_id IN VARCHAR2,
        p_phone IN VARCHAR2 DEFAULT NULL,
        p_email IN VARCHAR2 DEFAULT NULL,
        p_address IN VARCHAR2 DEFAULT NULL
    ) AS
        v_old_phone VARCHAR2(11);
        v_old_email VARCHAR2(100);
        v_old_address VARCHAR2(500);
    BEGIN
        -- 获取原始信息
        SELECT phone, email, address
        INTO v_old_phone, v_old_email, v_old_address
        FROM customer_info
        WHERE customer_id = p_customer_id;
        
        -- 更新客户信息
        UPDATE customer_info SET
            phone = NVL(p_phone, phone),
            email = NVL(p_email, email),
            address = NVL(p_address, address),
            update_time = SYSDATE
        WHERE customer_id = p_customer_id;
        
        IF SQL%ROWCOUNT = 0 THEN
            RAISE e_customer_not_found;
        END IF;
        
        -- 记录变更日志
        INSERT INTO customer_change_log (
            customer_id, change_type, old_value, new_value, 
            change_time, operator
        ) VALUES (
            p_customer_id, 'CONTACT_UPDATE',
            'phone:' || v_old_phone || '|email:' || v_old_email || '|address:' || v_old_address,
            'phone:' || NVL(p_phone, v_old_phone) || '|email:' || NVL(p_email, v_old_email) || '|address:' || NVL(p_address, v_old_address),
            SYSDATE, USER
        );
        
        COMMIT;
    EXCEPTION
        WHEN e_customer_not_found THEN
            RAISE_APPLICATION_ERROR(-20001, '客户不存在');
        WHEN OTHERS THEN
            ROLLBACK;
            RAISE;
    END update_customer_info;
    
    PROCEDURE freeze_account(
        p_account_id IN VARCHAR2,
        p_reason IN VARCHAR2
    ) AS
    BEGIN
        UPDATE account_info SET
            status = 'FROZEN',
            freeze_reason = p_reason,
            freeze_time = SYSDATE
        WHERE account_id = p_account_id
        AND status = 'ACTIVE';
        
        IF SQL%ROWCOUNT = 0 THEN
            RAISE_APPLICATION_ERROR(-20002, '账户不存在或已被冻结');
        END IF;
        
        -- 记录冻结日志
        INSERT INTO account_status_log (
            account_id, status_change, reason, change_time, operator
        ) VALUES (
            p_account_id, 'ACTIVE->FROZEN', p_reason, SYSDATE, USER
        );
        
        COMMIT;
    END freeze_account;
    
    PROCEDURE unfreeze_account(
        p_account_id IN VARCHAR2
    ) AS
    BEGIN
        UPDATE account_info SET
            status = 'ACTIVE',
            freeze_reason = NULL,
            unfreeze_time = SYSDATE
        WHERE account_id = p_account_id
        AND status = 'FROZEN';
        
        IF SQL%ROWCOUNT = 0 THEN
            RAISE_APPLICATION_ERROR(-20003, '账户不存在或未被冻结');
        END IF;
        
        -- 记录解冻日志
        INSERT INTO account_status_log (
            account_id, status_change, reason, change_time, operator
        ) VALUES (
            p_account_id, 'FROZEN->ACTIVE', '解冻', SYSDATE, USER
        );
        
        COMMIT;
    END unfreeze_account;
    
    FUNCTION get_customer_accounts(
        p_customer_id IN VARCHAR2
    ) RETURN t_account_list PIPELINED AS
    BEGIN
        FOR account_rec IN (
            SELECT account_id
            FROM account_info
            WHERE customer_id = p_customer_id
            ORDER BY open_date
        ) LOOP
            PIPE ROW(account_rec.account_id);
        END LOOP;
        RETURN;
    END get_customer_accounts;
    
END pkg_customer_service;
/

3. 风险管理包

-- 风险管理包规范
CREATE OR REPLACE PACKAGE pkg_risk_management AS
    
    -- 风险等级常量
    c_risk_low CONSTANT VARCHAR2(10) := 'LOW';
    c_risk_medium CONSTANT VARCHAR2(10) := 'MEDIUM';
    c_risk_high CONSTANT VARCHAR2(10) := 'HIGH';
    c_risk_critical CONSTANT VARCHAR2(10) := 'CRITICAL';
    
    -- 风险评估记录类型
    TYPE t_risk_assessment IS RECORD (
        customer_id VARCHAR2(20),
        risk_score NUMBER(5,2),
        risk_level VARCHAR2(10),
        assessment_date DATE,
        factors VARCHAR2(1000)
    );
    
    -- 反洗钱检查函数
    FUNCTION check_aml_risk(
        p_customer_id IN VARCHAR2,
        p_transaction_amount IN NUMBER,
        p_transaction_type IN VARCHAR2
    ) RETURN VARCHAR2;
    
    -- 信用风险评估
    FUNCTION assess_credit_risk(
        p_customer_id IN VARCHAR2
    ) RETURN t_risk_assessment;
    
    -- 欺诈检测
    FUNCTION detect_fraud_pattern(
        p_customer_id IN VARCHAR2,
        p_transaction_data IN VARCHAR2
    ) RETURN BOOLEAN;
    
    -- 风险预警处理
    PROCEDURE process_risk_alert(
        p_customer_id IN VARCHAR2,
        p_alert_type IN VARCHAR2,
        p_alert_level IN VARCHAR2,
        p_description IN VARCHAR2
    );
    
END pkg_risk_management;
/

-- 风险管理包体
CREATE OR REPLACE PACKAGE BODY pkg_risk_management AS
    
    FUNCTION check_aml_risk(
        p_customer_id IN VARCHAR2,
        p_transaction_amount IN NUMBER,
        p_transaction_type IN VARCHAR2
    ) RETURN VARCHAR2 AS
        v_daily_cash_amount NUMBER;
        v_monthly_transfer_count NUMBER;
        v_risk_level VARCHAR2(10) := c_risk_low;
        v_customer_grade VARCHAR2(20);
    BEGIN
        -- 获取客户等级
        v_customer_grade := get_customer_grade(p_customer_id);
        
        -- 检查当日现金交易累计金额
        SELECT NVL(SUM(amount), 0)
        INTO v_daily_cash_amount
        FROM transaction_log tl
        JOIN account_info ai ON tl.account_id = ai.account_id
        WHERE ai.customer_id = p_customer_id
        AND tl.trans_type IN ('CASH_DEPOSIT', 'CASH_WITHDRAW')
        AND TRUNC(tl.trans_time) = TRUNC(SYSDATE);
        
        -- 检查当月转账频率
        SELECT COUNT(*)
        INTO v_monthly_transfer_count
        FROM transaction_log tl
        JOIN account_info ai ON tl.account_id = ai.account_id
        WHERE ai.customer_id = p_customer_id
        AND tl.trans_type = 'TRANSFER'
        AND tl.trans_time >= TRUNC(SYSDATE, 'MM');
        
        -- 风险等级判断
        IF p_transaction_amount >= 200000 THEN
            v_risk_level := c_risk_high;
        ELSIF v_daily_cash_amount + p_transaction_amount >= 50000 THEN
            v_risk_level := c_risk_medium;
        ELSIF v_monthly_transfer_count >= 100 THEN
            v_risk_level := c_risk_medium;
        END IF;
        
        -- 记录AML检查日志
        INSERT INTO aml_check_log (
            customer_id, transaction_amount, transaction_type,
            daily_cash_amount, monthly_transfer_count, risk_level,
            check_time
        ) VALUES (
            p_customer_id, p_transaction_amount, p_transaction_type,
            v_daily_cash_amount, v_monthly_transfer_count, v_risk_level,
            SYSDATE
        );
        
        RETURN v_risk_level;
    END check_aml_risk;
    
    FUNCTION assess_credit_risk(
        p_customer_id IN VARCHAR2
    ) RETURN t_risk_assessment AS
        v_assessment t_risk_assessment;
        v_income NUMBER;
        v_debt_ratio NUMBER;
        v_overdue_count NUMBER;
        v_credit_history_months NUMBER;
        v_factors VARCHAR2(1000);
    BEGIN
        v_assessment.customer_id := p_customer_id;
        v_assessment.assessment_date := SYSDATE;
        
        -- 获取客户收入信息
        SELECT NVL(annual_income, 0)
        INTO v_income
        FROM customer_info
        WHERE customer_id = p_customer_id;
        
        -- 计算负债率
        SELECT 
            CASE 
                WHEN SUM(CASE WHEN balance > 0 THEN balance ELSE 0 END) = 0 THEN 0
                ELSE SUM(CASE WHEN balance < 0 THEN ABS(balance) ELSE 0 END) / 
                     SUM(CASE WHEN balance > 0 THEN balance ELSE 0 END)
            END
        INTO v_debt_ratio
        FROM account_info
        WHERE customer_id = p_customer_id;
        
        -- 统计逾期次数
        SELECT COUNT(*)
        INTO v_overdue_count
        FROM loan_overdue_history
        WHERE customer_id = p_customer_id
        AND overdue_date >= ADD_MONTHS(SYSDATE, -24);
        
        -- 计算信用历史长度
        SELECT MONTHS_BETWEEN(SYSDATE, MIN(open_date))
        INTO v_credit_history_months
        FROM account_info
        WHERE customer_id = p_customer_id;
        
        -- 风险评分计算
        v_assessment.risk_score := 
            CASE WHEN v_income >= 200000 THEN 25 ELSE v_income / 8000 END + -- 收入评分(25分)
            CASE WHEN v_debt_ratio <= 0.3 THEN 25 WHEN v_debt_ratio <= 0.5 THEN 15 ELSE 5 END + -- 负债率评分(25分)
            CASE WHEN v_overdue_count = 0 THEN 25 WHEN v_overdue_count <= 2 THEN 15 ELSE 5 END + -- 信用记录评分(25分)
            CASE WHEN v_credit_history_months >= 36 THEN 25 WHEN v_credit_history_months >= 12 THEN 15 ELSE 5 END; -- 信用历史评分(25分)
        
        -- 风险等级判定
        v_assessment.risk_level := 
            CASE 
                WHEN v_assessment.risk_score >= 80 THEN c_risk_low
                WHEN v_assessment.risk_score >= 60 THEN c_risk_medium
                WHEN v_assessment.risk_score >= 40 THEN c_risk_high
                ELSE c_risk_critical
            END;
        
        -- 风险因子描述
        v_factors := '收入:' || v_income || '|负债率:' || ROUND(v_debt_ratio * 100, 2) || 
                    '%|逾期次数:' || v_overdue_count || '|信用历史:' || v_credit_history_months || '月';
        v_assessment.factors := v_factors;
        
        -- 保存评估结果
        INSERT INTO credit_risk_assessment (
            customer_id, risk_score, risk_level, assessment_date, factors
        ) VALUES (
            v_assessment.customer_id, v_assessment.risk_score, 
            v_assessment.risk_level, v_assessment.assessment_date, v_assessment.factors
        );
        
        RETURN v_assessment;
    END assess_credit_risk;
    
    FUNCTION detect_fraud_pattern(
        p_customer_id IN VARCHAR2,
        p_transaction_data IN VARCHAR2
    ) RETURN BOOLEAN AS
        v_is_fraud BOOLEAN := FALSE;
        v_unusual_time_count NUMBER;
        v_unusual_location_count NUMBER;
        v_large_amount_count NUMBER;
    BEGIN
        -- 检查异常时间交易(深夜交易)
        SELECT COUNT(*)
        INTO v_unusual_time_count
        FROM transaction_log tl
        JOIN account_info ai ON tl.account_id = ai.account_id
        WHERE ai.customer_id = p_customer_id
        AND (EXTRACT(HOUR FROM CAST(tl.trans_time AS TIMESTAMP)) >= 23
             OR EXTRACT(HOUR FROM CAST(tl.trans_time AS TIMESTAMP)) <= 5)
        AND tl.trans_time >= SYSDATE - 7;
        
        -- 检查异地交易
        SELECT COUNT(*)
        INTO v_unusual_location_count
        FROM transaction_log tl
        JOIN account_info ai ON tl.account_id = ai.account_id
        WHERE ai.customer_id = p_customer_id
        AND tl.location_code NOT IN (
            SELECT DISTINCT location_code 
            FROM customer_usual_locations 
            WHERE customer_id = p_customer_id
        )
        AND tl.trans_time >= SYSDATE - 1;
        
        -- 检查大额交易
        SELECT COUNT(*)
        INTO v_large_amount_count
        FROM transaction_log tl
        JOIN account_info ai ON tl.account_id = ai.account_id
        WHERE ai.customer_id = p_customer_id
        AND tl.amount > 50000
        AND tl.trans_time >= SYSDATE - 1;
        
        -- 欺诈模式判断
        IF v_unusual_time_count >= 3 OR 
           v_unusual_location_count >= 2 OR 
           v_large_amount_count >= 3 THEN
            v_is_fraud := TRUE;
        END IF;
        
        -- 记录检测结果
        INSERT INTO fraud_detection_log (
            customer_id, unusual_time_count, unusual_location_count,
            large_amount_count, is_fraud_detected, detection_time
        ) VALUES (
            p_customer_id, v_unusual_time_count, v_unusual_location_count,
            v_large_amount_count, CASE WHEN v_is_fraud THEN 'Y' ELSE 'N' END, SYSDATE
        );
        
        RETURN v_is_fraud;
    END detect_fraud_pattern;
    
    PROCEDURE process_risk_alert(
        p_customer_id IN VARCHAR2,
        p_alert_type IN VARCHAR2,
        p_alert_level IN VARCHAR2,
        p_description IN VARCHAR2
    ) AS
        v_alert_id NUMBER;
    BEGIN
        -- 生成告警ID
        SELECT risk_alert_seq.NEXTVAL INTO v_alert_id FROM dual;
        
        -- 插入风险告警记录
        INSERT INTO risk_alert (
            alert_id, customer_id, alert_type, alert_level,
            description, alert_time, status
        ) VALUES (
            v_alert_id, p_customer_id, p_alert_type, p_alert_level,
            p_description, SYSDATE, 'OPEN'
        );
        
        -- 根据告警级别采取相应措施
        IF p_alert_level = c_risk_critical THEN
            -- 高风险:冻结所有账户
            FOR account_rec IN (
                SELECT account_id FROM account_info 
                WHERE customer_id = p_customer_id AND status = 'ACTIVE'
            ) LOOP
                pkg_customer_service.freeze_account(
                    account_rec.account_id, 
                    '高风险告警自动冻结'
                );
            END LOOP;
            
        ELSIF p_alert_level = c_risk_high THEN
            -- 中高风险:限制大额交易
            UPDATE customer_info SET
                transaction_limit = 10000
            WHERE customer_id = p_customer_id;
        END IF;
        
        COMMIT;
    END process_risk_alert;
    
END pkg_risk_management;
/

函数和包的最佳实践

1. 性能优化

-- 年龄计算依赖当前日期,不能声明为DETERMINISTIC
CREATE OR REPLACE FUNCTION calculate_age(p_birth_date DATE) 
RETURN NUMBER
AS
BEGIN
    RETURN FLOOR(MONTHS_BETWEEN(SYSDATE, p_birth_date) / 12);
END;
/

-- 格式化结果依赖会话NLS设置,不声明DETERMINISTIC;PARALLEL_ENABLE不保证查询实际并行执行
CREATE OR REPLACE FUNCTION format_currency(p_amount NUMBER)
RETURN VARCHAR2
PARALLEL_ENABLE
AS
BEGIN
    RETURN TO_CHAR(p_amount, 'L999,999,990.00');
END;
/

-- 查询当日汇率:函数内部依赖当前日期,不使用RESULT_CACHE
CREATE OR REPLACE FUNCTION get_exchange_rate(
    p_from_currency VARCHAR2,
    p_to_currency VARCHAR2
) RETURN NUMBER
AS
    v_rate NUMBER;
BEGIN
    SELECT rate INTO v_rate
    FROM exchange_rates
    WHERE from_currency = p_from_currency
    AND to_currency = p_to_currency
    AND rate_date = TRUNC(SYSDATE);
    
    RETURN v_rate;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 1;
END;
/

2. 异常处理和调试

-- 包级别异常处理
CREATE OR REPLACE PACKAGE pkg_utilities AS
    
    -- 自定义异常
    e_invalid_parameter EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_invalid_parameter, -20100);
    
    -- 调试日志函数
    PROCEDURE debug_log(p_message VARCHAR2);
    
    -- 通用验证函数
    FUNCTION validate_amount(p_amount NUMBER) RETURN BOOLEAN;
    
END pkg_utilities;
/

CREATE OR REPLACE PACKAGE BODY pkg_utilities AS
    
    g_debug_enabled BOOLEAN := TRUE;
    
    PROCEDURE debug_log(p_message VARCHAR2) AS
    BEGIN
        IF g_debug_enabled THEN
            INSERT INTO debug_log (log_time, message)
            VALUES (SYSTIMESTAMP, p_message);
        END IF;
    END debug_log;
    
    FUNCTION validate_amount(p_amount NUMBER) RETURN BOOLEAN AS
    BEGIN
        IF p_amount IS NULL OR p_amount < 0 THEN
            RAISE e_invalid_parameter;
        END IF;
        
        RETURN TRUE;
    EXCEPTION
        WHEN e_invalid_parameter THEN
            debug_log('无效金额: ' || TO_CHAR(p_amount));
            RAISE_APPLICATION_ERROR(-20100, '金额不能为空且必须大于或等于0');
    END validate_amount;
    
END pkg_utilities;
/

Oracle函数和包可用于组织银行业务逻辑。合理的设计和优化有助于提高代码复用性、维护性和执行效率,但不能单独保证银行系统稳定运行;本文的业务阈值和处理流程仅作为教学假设,实际应用还需满足业务规则、事务一致性、并发控制及安全要求。