Oracle中不同函数对数字区间的处理方式不同:
- FOR循环:闭区间[m,n],包含结束值(如1..3包含3)
- SUBSTR :从起始位置开始截取指定长度字符(非结束位置),特殊规则:
- 0视为第1位
- 负数表示从末尾倒数
- INSTR:返回子串出现的位置(从1计数)
- 其他函数:
- ROUND/TRUNC处理小数位
- MOD取余数
- POWER计算幂次
关键区别:
- FOR循环关注起止值包含性
- SUBSTR关注起始位置和长度
- INSTR返回位置索引
日期处理:
- BETWEEN对日期包含结束日00:00:00,不包含时间部分
- 推荐使用>=开始 AND <结束日+1的方式确保包含完整时间段
- TRUNC函数去除时间部分,LAST_DAY获取月末
- 时间段查询应使用不等号确保边界完整
关键区别:数字区间关注起止值包含性,日期处理需特别注意时间部分的边界问题。
📊 Oracle 数字区间问题分类总结
| 功能/函数 | 语法 | 起始位置 | 是否包含结束位置 | 边界规则说明 | 示例 | 结果 |
|---|---|---|---|---|---|---|
| FOR 循环 | FOR i IN m..n LOOP |
从 m 开始 | ✅ 包含 n | 闭区间 [m, n],i 会取到 n |
FOR i IN 1..3 LOOP |
i = 1,2,3 |
| SUBSTR 截取 | SUBSTR(str, m, n) |
从第 m 位开始 | ✅ 包含 | 截取 n 个字符,不是到第 n 位 | SUBSTR('ABCDE', 2, 3) |
'BCD'(位置2,3,4) |
| SUBSTR 简写 | SUBSTR(str, m) |
从第 m 位开始 | ✅ 包含 | 截取到字符串末尾 | SUBSTR('ABCDE', 3) |
'CDE' |
| INSTR 查找 | INSTR(str, sub, m, n) |
从第 m 位开始搜索 | N/A(返回值是位置) | 返回第 n 次出现的位置(从1计数) | INSTR('ABABA', 'B', 1, 2) |
4(第2个B的位置) |
| ROUND 四舍五入 | ROUND(num, m) |
第 m 位小数 | N/A(不是区间问题) | m 为正:小数点后;m 为负:小数点前 | ROUND(123.456, 1) |
123.5 |
| TRUNC 截断 | TRUNC(num, m) |
第 m 位小数 | N/A(不是区间问题) | 直接截断,不四舍五入 | TRUNC(123.456, 1) |
123.4 |
| MOD 取模 | MOD(m, n) |
N/A | N/A | 返回 m 除以 n 的余数 | MOD(10, 3) |
1 |
| POWER 幂运算 | POWER(m, n) |
N/A | N/A | 返回 m 的 n 次方 | POWER(2, 3) |
8 |
🎯 容易混淆的重点对比
1️⃣ FOR 循环 vs SUBSTR 的"结束"概念
| 对比项 | FOR 循环 | SUBSTR |
|---|---|---|
| 区间类型 | 位置区间 [m, n] |
长度区间(从 m 开始取 n 个) |
| 参数含义 | 结束值 | 截取长度 |
| 是否包含 | 包含结束值 | 包含起始位置,取 n 个字符 |
sql
sql
-- FOR 循环:包含 5
FOR i IN 1..5 LOOP -- i = 1,2,3,4,5
-- SUBSTR:第2个参数是长度,不是结束位置
SUBSTR('ABCDE', 2, 3) -- 从位置2开始取3个 → 'BCD'(位置2,3,4)
2️⃣ SUBSTR 的起始位置规则(特殊!)
| 起始位置 | 行为 | 示例(字符串 'ABCDE') |
结果 |
|---|---|---|---|
| 0 | 视为第 1 位 | SUBSTR('ABCDE', 0, 2) |
'AB' |
| 1 | 正常从第 1 位开始 | SUBSTR('ABCDE', 1, 2) |
'AB' |
| 正数 n | 从第 n 位开始 | SUBSTR('ABCDE', 3, 2) |
'CD' |
| 负数 n | 从末尾倒数第 n 位开始 | SUBSTR('ABCDE', -2, 2) |
'DE' |
3️⃣ INSTR 的位置返回值
sql
sql
-- INSTR 返回的是位置编号(从1开始)
INSTR('ABCDE', 'C') -- 返回 3
INSTR('ABABA', 'B', 1, 2) -- 从位置1开始找第2个B → 返回 4
✅ 快速记忆口诀
| 场景 | 口诀 |
|---|---|
| FOR 循环 | 两点之间,两头都算 |
| SUBSTR | 起始位置 + 取几个,不是到哪 |
| INSTR | 找到后告诉你它在第几个位置 |
| 负数索引 | SUBSTR 倒数开始,INSTR 不动 |
示例
sql
--练习:
-- 1,开发函数 ELIM函数 实现功能 ELIM('ABBBBBCD','B') 返回
-- ELIM('ABBBBCD','B')
-- 干掉 第一次出现 第二个入参的内容
-- ELIM('A-B-C','-')返回 'AB-C'
create or replace function ELIM(str varchar2,s varchar2)
return varchar2
is v_res varchar2(100);
v_start number;
begin
if instr(str,s)=0
then
return str;
else
v_start:=instr(str,s,1,1);
--拼接字符串
v_res:=substr(str,1,v_start-1)||substr(str,v_start+1);
dbms_output.put_line(v_res);
return v_res;
end if;
end;
--单独执行 select 不要选中 自定义的函数 一起执行
select ELIM('A-B-C','-')as ELIM from dual;
📊 Oracle 日期边界问题分类总结
一、BETWEEN...AND 的日期边界
| 场景 | 语法示例 | 是否包含结束日期 | 实际等价条件 | 说明 |
|---|---|---|---|---|
| 日期类型(无时间) | WHERE date_col BETWEEN DATE '2024-01-01' AND DATE '2024-01-31' |
✅ 包含 | date_col >= DATE '2024-01-01' AND date_col <= DATE '2024-01-31' |
包含结束日期当天 00:00:00 |
| 日期类型(有时间) | WHERE date_col BETWEEN DATE '2024-01-01' AND DATE '2024-01-31' |
⚠️ 不包含31日的时间部分 | 实际只到 2024-01-31 00:00:00 |
常见陷阱 :2024-01-31 14:30:00 不会被查到 |
| 时间戳类型 | WHERE ts_col BETWEEN TIMESTAMP '2024-01-01 00:00:00' AND TIMESTAMP '2024-01-31 23:59:59' |
✅ 完全包含 | 明确指定了时间边界 | 需要精确到秒或毫秒 |
二、常见的日期边界处理方式
| 需求场景 | 错误写法(陷阱) | 正确写法 | 说明 |
|---|---|---|---|
| 查询某一天的数据 | WHERE date_col = DATE '2024-01-31' |
WHERE date_col >= DATE '2024-01-31' AND date_col < DATE '2024-02-01' |
等于只查当天 00:00:00,漏掉有时间的数据 |
| BETWEEN 查整月 | WHERE date_col BETWEEN DATE '2024-01-01' AND DATE '2024-01-31' |
WHERE date_col >= DATE '2024-01-01' AND date_col < DATE '2024-02-01' |
BETWEEN 不包含1月31日的时间部分 |
| 查询本月至今 | WHERE date_col BETWEEN TRUNC(SYSDATE, 'MM') AND SYSDATE |
WHERE date_col >= TRUNC(SYSDATE, 'MM') AND date_col < SYSDATE + 1 |
BETWEEN 可能漏掉今天的部分时间 |
三、日期函数边界规则
| 函数 | 语法 | 边界规则 | 示例(以 2024-01-31 14:30:45 为例) | 结果 |
|---|---|---|---|---|
| TRUNC | TRUNC(date, 'unit') |
截断到指定单位的开始 | TRUNC(SYSDATE, 'MM') |
2024-01-01 00:00:00 |
| TRUNC | TRUNC(date, 'DD') |
截断到当天的开始 | TRUNC(SYSDATE) |
2024-01-31 00:00:00 |
| LAST_DAY | LAST_DAY(date) |
返回月份最后一天的 00:00:00 | LAST_DAY(DATE '2024-01-31') |
2024-01-31 00:00:00 |
| ADD_MONTHS | ADD_MONTHS(date, n) |
月份边界自动处理 | ADD_MONTHS(DATE '2024-01-31', 1) |
2024-02-29 00:00:00(闰年) |
| NEXT_DAY | NEXT_DAY(date, 'weekday') |
返回下一个指定星期几 | NEXT_DAY(DATE '2024-01-31', 'MONDAY') |
下一个周一 00:00:00 |
四、常见的时间段区间写法(最佳实践)
| 时间段 | 包含边界写法 | 说明 |
|---|---|---|
| 某一天 | date_col >= DATE '2024-01-31' AND date_col < DATE '2024-02-01' |
✅ 推荐:包含当天所有时间 |
| 某一月 | date_col >= DATE '2024-01-01' AND date_col < DATE '2024-02-01' |
✅ 推荐:包含整月 |
| 某一小时 | date_col >= TIMESTAMP '2024-01-31 14:00:00' AND date_col < TIMESTAMP '2024-01-31 15:00:00' |
✅ 包含14:00:00到14:59:59 |
| 本周 | date_col >= TRUNC(SYSDATE, 'IW') AND date_col < TRUNC(SYSDATE, 'IW') + 7 |
周一到周日(ISO标准) |
| 本月 | date_col >= TRUNC(SYSDATE, 'MM') AND date_col < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1) |
整月 |
| 本季度 | date_col >= TRUNC(SYSDATE, 'Q') AND date_col < ADD_MONTHS(TRUNC(SYSDATE, 'Q'), 3) |
整季度 |
| 本年 | date_col >= TRUNC(SYSDATE, 'YY') AND date_col < ADD_MONTHS(TRUNC(SYSDATE, 'YY'), 12) |
整年 |
五、BETWEEN 与日期使用的具体示例
❌ 错误示例(常见陷阱)
sql
sql
-- 查询 2024-01-31 的所有订单
SELECT * FROM orders
WHERE order_date BETWEEN DATE '2024-01-31' AND DATE '2024-01-31';
-- 结果:只查出 order_date = 2024-01-31 00:00:00 的订单
-- 漏掉:2024-01-31 14:30:00 的订单
✅ 正确示例
sql
sql
-- 方法1:使用 >= 和 <(推荐)
SELECT * FROM orders
WHERE order_date >= DATE '2024-01-31'
AND order_date < DATE '2024-02-01';
-- 方法2:使用 TRUNC(但会索引失效)
SELECT * FROM orders
WHERE TRUNC(order_date) = DATE '2024-01-31';
-- 方法3:精确到秒的 BETWEEN
SELECT * FROM orders
WHERE order_date BETWEEN TIMESTAMP '2024-01-31 00:00:00'
AND TIMESTAMP '2024-01-31 23:59:59';
六、日期边界核心要点速查表
| 问题 | 答案 |
|---|---|
| BETWEEN 包含结束日期吗? | ✅ 包含,但只包含结束日期的 00:00:00 |
| 如何包含某一天的所有时间? | 使用 >= 当天 AND < 明天 |
| 如何包含某一整月? | 使用 >= 当月1号 AND < 下月1号 |
| DATE 类型有时间部分吗? | ✅ 有!Oracle DATE 包含时分秒 |
| TRUNC 作用是什么? | 去掉时间部分,归零到 00:00:00 |
| 判断是否同一天? | TRUNC(date1) = TRUNC(date2) |
✅ 记忆口诀
BETWEEN 虽包含,只到零点不算妙
要查整天用不等,大于等于小于明天好
月份区间也同理,上月1号到下月1号
TRUNC 归零去时间,比较日期最可靠
Oracle中的日期运算
Oracle中常用日期运算的总结表格:
| 运算类型 | 示例/公式 | 说明 | 返回值类型 |
|---|---|---|---|
| 日期 ± 数字 | SYSDATE + 1 |
当前日期加1天 | DATE |
| 日期 - 日期 | DATE1 - DATE2 |
两个日期相差的天数(可含小数) | NUMBER |
| 月份加减 | ADD_MONTHS(date, n) ADD_MONTHS(date, -n) |
加/减 n 个月 | DATE |
| 月份差 | MONTHS_BETWEEN(date1, date2) |
date1 - date2 的月数(可为小数) | NUMBER |
| 下一个星期几 | NEXT_DAY(date, '星期一') NEXT_DAY(date, 'MON') |
指定日期后的第一个星期几 | DATE |
| 月末日期 | LAST_DAY(date) |
返回所在月份的最后一天 | DATE |
| 日期截断 | TRUNC(date, 'unit') |
按单位(年/月/日/时/分等)截断 | DATE |
| 日期舍入 | ROUND(date, 'unit') |
按单位舍入(过半进到下一单位) | DATE |
| 提取部分值 | EXTRACT(YEAR FROM date) EXTRACT(MONTH FROM date) EXTRACT(DAY FROM date) EXTRACT(HOUR FROM timestamp) |
提取年/月/日/时/分/秒 | NUMBER |
| 时间差(细粒度) | (date1 - date2) * 24 |
小时数 | NUMBER |
(date1 - date2) * 24 * 60 |
分钟数 | NUMBER | |
(date1 - date2) * 24 * 3600 |
秒数 | NUMBER | |
| 使用时间戳间隔 | (timestamp1 - timestamp2) DAY TO SECOND |
返回天、时、分、秒的间隔 | INTERVAL |
| 数值转日期 | TO_DATE('2024-01-15', 'YYYY-MM-DD') |
字符串转DATE类型 | DATE |
| 日期转字符串 | TO_CHAR(date, 'YYYY-MM-DD HH24:MI:SS') |
DATE转格式化的字符串 | VARCHAR2 |
常用日期单位(用于TRUNC/ROUND)
-
'YEAR'/'YYYY'/'Y'--- 年初(1月1日) -
'MONTH'/'MM'/'MON'--- 月初(1日) -
'DAY'--- 周初(周日,依赖于NLS设置) -
'Q'--- 季初 -
'HH24'/'HH'--- 小时 -
'MI'--- 分钟
示例
sql
sql
-- 加3天
SELECT SYSDATE + 3 FROM DUAL;
-- 加2个月
SELECT ADD_MONTHS(SYSDATE, 2) FROM DUAL;
-- 年龄(月数)
SELECT MONTHS_BETWEEN(SYSDATE, birth_date)/12 FROM employees;
-- 当月最后一天
SELECT LAST_DAY(SYSDATE) FROM DUAL;
-- 本周一(周一作为一周起始)
SELECT TRUNC(SYSDATE, 'IW') FROM DUAL;
-- 两个时间戳的间隔
SELECT (TIMESTAMP '2024-01-15 10:30:00' - TIMESTAMP '2024-01-14 08:20:00') DAY TO SECOND FROM DUAL;
注:
TRUNC和间隔运算在DATE和TIMESTAMP上略有不同,DATE运算结果为NUMBER,TIMESTAMP运算结果为INTERVAL类型。
日期比较大小
在 Oracle 中,比较日期大小有多种方式,取决于你的具体需求。下面整理了几种常见的方法:
1. 基本比较运算符
直接使用 <, >, =, <=, >=, <> 比较 DATE 类型:
sql
sql
-- 查询某天之后的记录
SELECT * FROM orders WHERE order_date > DATE '2024-01-01';
-- 查询某天之前的记录
SELECT * FROM orders WHERE order_date < SYSDATE;
-- 查询某天当天的记录
SELECT * FROM orders WHERE order_date = DATE '2024-03-15';
-- 注意:DATE 包含时间,等于判断需要精确到秒
SELECT * FROM orders
WHERE order_date = TO_DATE('2024-03-15 14:30:00', 'YYYY-MM-DD HH24:MI:SS');
2. 忽略时间部分(只比较日期)
如果字段包含时间,只想比较年月日:
sql
sql
-- 方法1:TRUNC 函数
SELECT * FROM orders WHERE TRUNC(order_date) = DATE '2024-03-15';
-- 方法2:TO_CHAR 格式化
SELECT * FROM orders
WHERE TO_CHAR(order_date, 'YYYY-MM-DD') = '2024-03-15';
-- 方法3:日期范围(性能较好)
SELECT * FROM orders
WHERE order_date >= DATE '2024-03-15'
AND order_date < DATE '2024-03-16';
3. 在查询条件中比较
sql
sql
-- 获取过去7天的数据
SELECT * FROM orders
WHERE order_date >= SYSDATE - 7;
-- 获取本月数据
SELECT * FROM orders
WHERE order_date >= TRUNC(SYSDATE, 'MM')
AND order_date < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1);
-- 获取今年数据
SELECT * FROM orders
WHERE EXTRACT(YEAR FROM order_date) = EXTRACT(YEAR FROM SYSDATE);
4. 在 CASE / DECODE 中比较
sql
sql
SELECT
order_id,
order_date,
CASE
WHEN order_date > SYSDATE THEN 'Future'
WHEN order_date = TRUNC(SYSDATE) THEN 'Today'
ELSE 'Past'
END AS status
FROM orders;
5. 注意事项
| 问题 | 说明 | 建议 |
|---|---|---|
Oracle DATE 包含年、月、日、时、分、秒 |
= 比较必须完全匹配 |
用范围查询或 TRUNC |
| 索引使用 | TRUNC(col) 会使索引失效 |
用 col BETWEEN start AND end |
| 时区 | TIMESTAMP WITH TIME ZONE 需转换 |
用 CAST 或 AT TIME ZONE |
| 空值 | 任何 NULL 比较返回 UNKNOWN |
加上 IS NULL 检查 |
6. 完整示例
sql
sql
-- 创建测试表
CREATE TABLE test_dates (
id NUMBER,
event_date DATE
);
INSERT INTO test_dates VALUES (1, DATE '2024-01-15');
INSERT INTO test_dates VALUES (2, TO_DATE('2024-03-20 08:30:00', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO test_dates VALUES (3, SYSDATE);
-- 查询示例
SELECT * FROM test_dates WHERE event_date > DATE '2024-02-01';
SELECT * FROM test_dates WHERE TRUNC(event_date) = DATE '2024-01-15';
SELECT * FROM test_dates
WHERE event_date BETWEEN DATE '2024-01-01' AND DATE '2024-01-31';