在 Oracle 中,处理日期是一个核心且常见的任务,存储过程中的日期参数主要有以下几个方面需要注意:

- 如何定义日期参数
- 如何向日期参数传入值
- 如何在存储过程内部处理日期(计算、格式化等)
- 如何返回日期值
- 常见陷阱和最佳实践
定义日期参数
在存储过程的声明部分,直接使用 DATE 数据类型来定义参数即可。
CREATE OR REPLACE PROCEDURE proc_with_date_param (
p_start_date IN DATE,
p_end_date IN DATE
) AS
-- 可以在这里声明一个 DATE 类型的局部变量
v_current_date DATE;
BEGIN
-- 存储过程逻辑
DBMS_OUTPUT.PUT_LINE('开始日期: ' || TO_CHAR(p_start_date, 'YYYY-MM-DD'));
DBMS_OUTPUT.PUT_LINE('结束日期: ' || TO_CHAR(p_end_date, 'YYYY-MM-DD'));
-- 赋值给局部变量
v_current_date := SYSDATE;
DBMS_OUTPUT.PUT_LINE('当前日期: ' || TO_CHAR(v_current_date, 'YYYY-MM-DD HH24:MI:SS'));
END proc_with_date_param;
/
向日期参数传入值
这是最容易出错的地方,Oracle 期望 DATE 类型的值,但应用程序或 SQL*Plus 等工具通常以字符串形式传递日期。必须在调用时进行显式转换。
假设我们要调用上面的 proc_with_date_param。
使用 TO_DATE 函数(推荐)
这是最标准、最安全的方法,使用 TO_DATE 函数,并强烈建议指定日期格式。

-- 正确的调用方式(推荐)
-- 格式 'YYYY-MM-DD' 是最不容易混淆的格式之一
BEGIN
proc_with_date_param(
p_start_date => TO_DATE('2025-10-26', 'YYYY-MM-DD'),
p_end_date => TO_DATE('2025-10-31', 'YYYY-MM-DD')
);
END;
/
输出:
开始日期: 2025-10-26
结束日期: 2025-10-31
当前日期: 2025-10-27 10:30:45 -- 这里的日期和时间会根据你运行时的时间变化
为什么必须指定格式?
如果不指定格式,Oracle 会使用会话的 NLS_DATE_FORMAT 参数,这个参数在不同环境(开发、测试、生产)中可能不同,导致你的代码在 A 环境正常,在 B 环境就出错。'10/11/2025' 在美国格式是 10月11日,而在欧洲格式可能是 11月10日。
使用 ANSI 日期字面量(Oracle 9i 及以上版本)
Oracle 支持一种更直观的日期字面量格式:DATE 'YYYY-MM-DD',这种方式不需要 TO_DATE 函数,并且不依赖于 NLS_DATE_FORMAT,是更优的选择。
-- 正确的调用方式(更优)
BEGIN
proc_with_date_param(
p_start_date => DATE '2025-10-26',
p_end_date => DATE '2025-10-31'
);
END;
/
注意:这种格式要求年月日的顺序必须是 YYYY-MM-DD,非常严格。

错误的调用方式
❌ 错误示例:直接传入字符串
-- 错误!PLS-00306: wrong number or types of arguments in call to 'PROC_WITH_DATE_PARAM'
BEGIN
proc_with_date_param('2025-10-26', '2025-10-31'); -- 错误,传递的是 VARCHAR2
END;
/
在存储过程内部处理日期
一旦参数进入存储过程,它就是一个标准的 DATE 类型,你可以使用 Oracle 内置的日期函数进行各种操作。
1 日期格式化输出
使用 TO_CHAR 函数将日期转换为字符串以便显示。
CREATE OR REPLACE PROCEDURE print_formatted_date (p_date IN DATE) AS
BEGIN
DBMS_OUTPUT.PUT_LINE('默认格式: ' || p_date); -- 依赖于 NLS_DATE_FORMAT
-- 强烈推荐:总是使用明确的格式模型
DBMS_OUTPUT.PUT_LINE('标准格式: ' || TO_CHAR(p_date, 'YYYY-MM-DD'));
DBMS_OUTPUT.PUT_LINE('详细格式: ' || TO_CHAR(p_date, 'YYYY年MM月DD日 HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('美国格式: ' || TO_CHAR(p_date, 'MM/DD/YYYY'));
END;
/
2 日期计算
Oracle 的 DATE 类型可以直接加减数字,数字代表天数。
CREATE OR REPLACE PROCEDURE date_calculations (p_date IN DATE) AS
v_next_day DATE;
v_prev_day DATE;
v_one_week_later DATE;
BEGIN
-- 加一天
v_next_day := p_date + 1;
DBMS_OUTPUT.PUT_LINE('下一天: ' || TO_CHAR(v_next_day, 'YYYY-MM-DD'));
-- 减一天
v_prev_day := p_date - 1;
DBMS_OUTPUT.PUT_LINE('前一天: ' || TO_CHAR(v_prev_day, 'YYYY-MM-DD'));
-- 加7天
v_one_week_later := p_date + 7;
DBMS_OUTPUT.PUT_LINE('一周后: ' || TO_CHAR(v_one_week_later, 'YYYY-MM-DD'));
-- 获取某个月的最后一天
-- 方法:下个月的第一天减去1天
v_last_day_of_month := LAST_DAY(p_date);
DBMS_OUTPUT.PUT_LINE('本月最后一天: ' || TO_CHAR(v_last_day_of_month, 'YYYY-MM-DD'));
END;
/
3 使用 ADD_MONTHS 等函数
CREATE OR REPLACE PROCEDURE add_months_example (p_date IN DATE) AS
v_three_months_later DATE;
v_one_year_ago DATE;
BEGIN
-- 增加月份
v_three_months_later := ADD_MONTHS(p_date, 3);
DBMS_OUTPUT.PUT_LINE('三个月后: ' || TO_CHAR(v_three_months_later, 'YYYY-MM-DD'));
-- 减少月份
v_one_year_ago := ADD_MONTHS(p_date, -12);
DBMS_OUTPUT.PUT_LINE('一年前: ' || TO_CHAR(v_one_year_ago, 'YYYY-MM-DD'));
END;
/
返回日期值
存储过程不能直接 RETURN 一个值(除了状态码),要返回日期,通常使用 OUT 或 IN OUT 参数。
1 使用 OUT 参数
OUT 参数只用于从过程向调用者返回值,调用时不能为其传入值。
CREATE OR REPLACE PROCEDURE get_last_day_of_month (
p_input_date IN DATE,
p_last_day OUT DATE
) AS
BEGIN
-- 计算输入日期所在月份的最后一天
p_last_day := LAST_DAY(p_input_date);
DBMS_OUTPUT.PUT_LINE('在过程中计算出的最后一天是: ' || TO_CHAR(p_last_day, 'YYYY-MM-DD'));
END;
/
调用方式:
需要先声明一个变量来接收 OUT 参数的值。
DECLARE
v_start_date DATE := DATE '2025-02-15';
v_end_date DATE;
BEGIN
-- 调用存储过程,v_end_date 会被赋值
get_last_day_of_month(p_input_date => v_start_date, p_last_day => v_end_date);
-- 在调用后打印结果
DBMS_OUTPUT.PUT_LINE('调用后,v_end_date 的值是: ' || TO_CHAR(v_end_date, 'YYYY-MM-DD')); -- 应该是 2025-02-28
END;
/
2 使用 IN OUT 参数
IN OUT 参数既可以传入值,也可以被修改并返回新值。
CREATE OR REPLACE PROCEDURE adjust_date (
p_date IN OUT DATE
) AS
BEGIN
-- 将传入的日期增加30天,然后返回
p_date := p_date + 30;
DBMS_OUTPUT.PUT_LINE('在过程中调整后的日期是: ' || TO_CHAR(p_date, 'YYYY-MM-DD'));
END;
/
调用方式:
DECLARE
v_my_date DATE := DATE '2025-01-01';
BEGIN
DBMS_OUTPUT.PUT_LINE('调用前的日期是: ' || TO_CHAR(v_my_date, 'YYYY-MM-DD'));
-- 调用,传入 v_my_date
adjust_date(p_date => v_my_date);
-- v_my_date 的值已经被修改
DBMS_OUTPUT.PUT_LINE('调用后的日期是: ' || TO_CHAR(v_my_date, 'YYYY-MM-DD')); -- 应该是 2025-01-31
END;
/
最佳实践与常见陷阱
✅ 最佳实践
-
始终使用明确的日期格式:在调用存储过程时,使用
TO_DATE('...', 'YYYY-MM-DD')或DATE 'YYYY-MM-DD',在内部格式化时,使用TO_CHAR(..., '...')并指定格式。 -
优先使用
DATE 'YYYY-MM-DD':如果可能,这是最不容易出错的方式。 -
处理时间部分:
DATE类型包含日期和时间,如果你只关心日期,在比较或计算时要注意清除时间部分。-- 错误:p_some_date 的时间是 '2025-10-26 14:30:00',这个比较会失败 IF p_some_date > DATE '2025-10-26' THEN ... -- 正确:使用 TRUNC 清除时间部分 IF TRUNC(p_some_date) > DATE '2025-10-26' THEN ...
-
考虑时区:如果你的应用需要处理不同时区的日期,请使用
TIMESTAMP WITH TIME ZONE数据类型,而不是DATE。DATE类型没有时区信息。 -
使用
IN OUT还是OUT:- 如果只需要返回一个新值,与输入无关,使用
OUT。 - 如果需要对传入的值进行修改并返回修改后的结果,使用
IN OUT。
- 如果只需要返回一个新值,与输入无关,使用
❌ 常见陷阱
- 依赖
NLS_DATE_FORMAT:如前所述,这是导致代码在不同环境行为不一致的主要原因。 - 忽略时间部分:在比较日期时,忘记
DATE类型包含时间,导致逻辑错误。 - 使用
TO_DATE而不指定格式:TO_DATE('01/02/2025')的含义是模糊的,应极力避免。 - 将日期作为字符串传递:在应用程序代码(如 Java, Python)中,必须确保在调用 Oracle 存储过程前,将日期字符串转换为数据库期望的
DATE类型,大多数数据库驱动(如 JDBC 的PreparedStatement.setTimestamp()或setDate())会自动处理这种转换。
| 场景 | 推荐做法 | 示例 |
|---|---|---|
| 定义参数 | 使用 DATE 类型 |
p_my_date IN DATE |
| 传入日期值 | 使用 DATE 'YYYY-MM-DD' 或 TO_DATE(...) |
proc_call(DATE '2025-10-26') |
| 格式化输出 | 使用 TO_CHAR(..., '格式模型') |
TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI') |
| 日期计算 | 使用 , , ADD_MONTHS, LAST_DAY 等 |
my_date + 7 或 ADD_MONTHS(my_date, 1) |
| 返回日期 | 使用 OUT 或 IN OUT 参数 |
p_result OUT DATE |
| 比较日期 | 使用 TRUNC() 忽略时间部分 |
IF TRUNC(date1) > TRUNC(date2) THEN ... |
遵循这些原则,可以让你在 Oracle 存储过程中更安全、更可靠地处理日期参数。
