# DDL

DDL(Data Definition Language) 数据定义语言,用于定义和管理 SQL 数据库中的所有对象的语言,包括 CREATE, ALTER, DROP, TRUNCATE, COMMENT, GRANT, REVOKE

# DB Objects

view functional
user_objects 当前用户通过 DDL 建立的所有对象
user_tables 当前用户的所有 Table
user_views 当前用户的所有 View
user_sequences 当前用户的所有 Sequence
user_source 当前用户的 Package, Trigger Source
user_constraints 当前用户的所有 Constraint
user_indexes 当前用户的所有 Index
user_errors 当前用户的所有 Error
user_dependencies 当前用户的所有 Dependencie

使用:判断表是否存在,如果存在就删除

1
2
3
4
5
6
7
8
9
10
11
declare
num number;
begin
select count(1) into num
from user_tables
where table_name = upper('test');

if num > 0 then
execute immediate 'drop table test';
end if;
end;

user_tables 只能查询当前用户的表,如果查询其他 owner 的表需要查询 dba_tables 或者 all_tables

  • dba_tables: describes all relational tables in the database.
  • all_tables: describes the relational tables accessible to the current user.
  • user_tables: describes the relational tables owned by the current user.
1
2
3
4
select *
from dba_tables
where owner = upper('ysc')
and table_name = upper('som_edi_temp_h');

使用:查找文本所在 Source

在做 Transact Move Orders 时弹出错误:累计拣货数量超过报关数量。

  • 检查 form 的 trigger 没有发现 累计拣货数量超过报关数量 相关代码和描述。
  • 在 PL/SQL 中使用 select * from all_source s where s.text like '%累计拣货数量超过报关数量%'; 可以在 DB trigger 中找到相关文字,查看 trigger 代码可以看到实际原因是报关资料抄写没有抄写成功。

uer_errors

1
2
3
4
5
6
7
8
9
create or replace function edi_div(p1 number, p2 number) 
return number
is
l_temp numberx;
begin
return p1 / p2;
end;

select * from user_errors where name = 'EDI_DIV';
NAME TYPE SEQUENCE LINE POSITION TEXT ATTRIBUTE MESSAGE_NUMBER
EDI_DIV FUNCTION 1 4 10 PLS-00201: identifier ‘NUMBERX’ must be declared ERROR 201
EDI_DIV FUNCTION 2 4 10 PL/SQL: Item ignored ERROR 0

# Table Columns

The ALL_TABLE_COLUMNS view filters out system-generated hidden columns and invisible columns.
The ALL_TAB_COLS view does not filter out hidden columns and invisible columns.

1
2
3
4
5
6
7
8
9
select *
from all_tab_cols t
where t.table_name = upper('table_name')
and t.owner = 'APPS'

select listagg(t.column_name, ',') within group(order by t.segment_column_id)
from all_tab_cols t
where t.table_name = upper('table_name')
and t.owner = 'APPS';

# GET_DDL

在 Oracle 中,使用 DBMS_METADATA 包中的 GET_DDL 函数来获得对应对象的定义语句。

GET_DDL 函数的定义和参数说明如下:

1
2
3
4
5
6
7
8
DBMS_METADATA.GET_DDL (
object_type IN VARCHAR2, --对象类型
name IN VARCHAR2, --对象名称
schema IN VARCHAR2 DEFAULT NULL, --对象所在的Schema,默认为当前用户所在所Schema
version IN VARCHAR2 DEFAULT 'COMPATIBLE', --对象原数据的版本
model IN VARCHAR2 DEFAULT 'ORACLE', --原数据的类型默认为ORACLE
transform IN VARCHAR2 DEFAULT 'DDL')
RETURN CLOB; --对象的原数据默认以CLOB类型返回

GET_DDL 函数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select dbms_metadata.get_ddl('TABLE', 'ASO_BI_QOT_APRB_MV', 'APPS') from dual;
select dbms_metadata.get_ddl('SEQUENCE', 'RA_TRX_NUMBER_1000_S', 'APPS') from dual;
-- 获取所有sequence的定义
select dbms_metadata.get_ddl('SEQUENCE', u.object_name)
from user_objects u
where object_type='SEQUENCE';

--The output not include terminators.
BEGIN
DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'SQLTERMINATOR', true);
DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'PRETTY', true);
END;
/
select DBMS_METADATA.get_ddl('TABLE', 'EDI_T') as output from dual;
1
2
3
select DBMS_METADATA.get_dependent_ddl('INDEX','SOM_EDI_856_HEADER','YSC') as output from dual;

select DBMS_METADATA.get_xml('TABLE', 'EDI_T') as output from dual;

# DB Dynamic Message

v$ 开头的表一般是 oracle 的系统视图 ,具体存放数据库相关动态信息。

# v$session

v$session 是 APPS 用户下面对于 sys.v_$session 视图的同义词。在这个视图中,每一个连接到数据库实例中的 session 都拥有一条记录。包括用户 session 及后台进程如 DBWR,LGWR,arcchiver 等等。v$session 是基础信息视图,用于找寻用户 SID 或 SADDR

# v$sql

含有 SQL_TEXT, USERS_OPENING (执行语句的用户数等)

# v$database

select name from v$database; (返回当前数据库名,例如 TEST)

# v$version

返回数据库版本

# Create Primary Key and Index

Designate primay key when create table.

1
2
3
4
5
6
create table som_edi_861_header (
header_id number not null primary key,
document_number varchar2(30),
document_date varchar2(8),
received_date varchar2(8),
);

Designate primary key after create table.

1
2
alter table som_edi_861_header
add constraint som_edi_861_header_pk primary key (header_id);

Create index after create table.

1
create index som_edi_861_header_n1 on som_edi_861_header(document_number);

# Rename Table Name

1
RENAME TABLE SAMP.EMP_ACT TO EMPLOYEE_ACT

# Alter Table Columns

操作
更新字段名 alter table table_name rename column column_old to column_new
删除字段 alter table table_name drop column column_name
添加字段并附值 alter table table_name add column_name number(1) default 1
修改字段数据类型 alter table table_name modify column_name varchar2(20);
修改字段值 update table_name set column_name=value where column_name=value;

# Recover the Dropped Table

1
flashback table hr.regions_hist to before drop;

# Read Only Table

# Read Only Keyword

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create table edi_emp(
eno number,
ename varchar2(1000),
sal number
);

insert into edi_emp values (1, 'Babb', 2000);
insert into edi_emp values (2, 'Babb Chen', 9000);

alter table edi_emp read only;
insert into edi_emp values (3, 'Babbb Chen', 9000); --ORA-12081: update operation not allowed on table "APPS"."EDI_EMP"

alter table edi_emp read write;
insert into edi_emp values (3, 'Babbb Chen', 9000);

# Trigger Implements

1
2
3
4
5
6
7
create or replace trigger edi_block_dml_on_emp before
insert or update or delete on edi_emp
begin
raise_application_error(-20001, 'DML Operation not allowed on EMP');
end;

insert into edi_emp values (4, 'Babbbb Chen', 9000); -- ORA-20001: DML Operation not allowed on EMP

# Grant Select Only

1
2
3
4
5
6
7
8
select * from edi_emp; 

grant select on edi_emp to ysc;

--use ysc login
select * from apps.edi_emp;

insert into apps.edi_emp values (5, 'Babbbbb Chen', 9000); --ORA-01031: insufficient privileges

# Record Exception When Add Constraint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create table edi_t(
c number
);

insert into edi_t values (1);
insert into edi_t values (2);
insert into edi_t values (3);
insert into edi_t values (4);
insert into edi_t values (2);
insert into edi_t values (4);

alter table edi_t add constraint pk1 primary key (c); --ORA-02437: cannot validate (APPS.PK1) - primary key violated


create table edi_exceptions (
row_id rowid,
user_name varchar2(30),
table_name varchar2(30),
cons_name varchar2(30)
);

alter table edi_t add constraint pk1 primary key (c) exceptions into edi_exceptions; --ORA-02437: cannot validate (APPS.PK1) - primary key violated

1
select * from edi_exceptions;
ROW_ID USER_NAME TABLE_NAME CONS_NAME
AAgRpkAFIAACf+WAAE APPS EDI_T PK1
AAgRpkAFIAACf+WAAB APPS EDI_T PK1
AAgRpkAFIAACf+WAAF APPS EDI_T PK1
AAgRpkAFIAACf+WAAD APPS EDI_T PK1
1
2
3
4
5
select * 
from edi_t
where rowid in (
select row_id from edi_exceptions
);
C
2
4
2
4

# Error Logging Table

1
2
3
4
5
6
7
8
9
10
11
12
13
create table edi_emp1(
empno number primary key,
ename varchar2(30),
doj date,
sal number
);

insert into edi_emp1 values(1, 'Babb', sysdate, 1000);
insert into edi_emp1 values(2, 'Babb', sysdate, null);
insert into edi_emp1 values(3, 'Babb', null, 2000);
insert into edi_emp1 values(4, 'Babbbbbbbbbb Chen', sysdate, 4000);

select * from edi_emp1;
EMPNO ENAME DOJ SAL
1 Babb 2024-01-06 03:18:55 PM 1000
2 Babb 2024-01-06 03:18:55 PM
3 Babb 2000
4 Babbbbbbbbbb Chen 2024-01-06 03:18:56 PM 4000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create table edi_emp2(
empno number primary key,
ename varchar2(7), --edi_emp1 is varchar2(30)
doj date not null, --edi_emp1 is nullable
sal number not null --edi_emp1 is nullable
);

insert into edi_emp2 select * from edi_emp1; --ORA-01400: cannot insert NULL into ("APPS"."EDI_EMP2"."SAL")
select * from edi_emp2; --0 rows selected

begin
dbms_errlog.create_error_log(dml_table_name => 'edi_emp2'); --dml_table_name same as target table name
end;

select * from err$_edi_emp2; --0 rows selected

insert into edi_emp2 select * from edi_emp1
log errors into err$_edi_emp2 reject limit unlimited;

select * from edi_emp2;
EMPNO ENAME DOJ SAL
1 Babb 2024-01-06 03:18:55 PM 1000
1
select * from err$_edi_emp2;
ORA_ERR_NUMBER$ ORA_ERR_MESG$ ORA_ERR_ROWID$ ORA_ERR_OPTYP$ ORA_ERR_TAG$ EMPNO ENAME DOJ SAL
1400 ORA-01400: cannot insert NULL into (“APPS”.“EDI_EMP2”.“SAL”) I 2 Babb 06-JAN-24
1400 ORA-01400: cannot insert NULL into (“APPS”.“EDI_EMP2”.“DOJ”) I 3 Babb 2000
12899 ORA-12899: value too large for column “APPS”.“EDI_EMP2”.“ENAME” (actual: 17, maximum: 7) I 4 Babbbbbbbbbb Chen 06-JAN-24 4000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
truncate table err$_edi_emp2;
truncate table edi_emp2;

--------------------------------------------------------
insert into edi_emp2 select * from edi_emp1
log errors into err$_edi_emp2('TEST INSERT') reject limit unlimited; -- TEST INSERT is ORA_ERR_TAG$

select * from err$_edi_emp2;
select * from edi_emp2;
--------------------------------------------------------
update edi_emp2 set sal = null
log errors into err$_edi_emp2('TEST UPDATE') reject limit unlimited;

select * from err$_edi_emp2;
select * from edi_emp2;
---------------------------------------------------------
create or replace trigger edi_row_before_del
before delete on edi_emp2 for each row
declare
lv_number number;
begin
lv_number := 1 / 0;
end;

delete from edi_emp2
log errors into err$_edi_emp2('TEST DELETE') reject limit unlimited;

select * from err$_edi_emp2;
select * from edi_emp2;
ORA_ERR_NUMBER$ ORA_ERR_MESG$ ORA_ERR_ROWID$ ORA_ERR_OPTYP$ ORA_ERR_TAG$ EMPNO ENAME DOJ SAL
1400 ORA-01400: cannot insert NULL into (“APPS”.“EDI_EMP2”.“SAL”) I TEST INSERT 2 Babb 31-MAR-25
1400 ORA-01400: cannot insert NULL into (“APPS”.“EDI_EMP2”.“DOJ”) I TEST INSERT 3 Babb 2000
12899 ORA-12899: value too large for column “APPS”.“EDI_EMP2”.“ENAME” (actual: 17, maximum: 7) I TEST INSERT 4 Babbbbbbbbbb Chen 31-MAR-25 4000
1407 ORA-01407: cannot update (“APPS”.“EDI_EMP2”.“SAL”) to NULL AAnjPPAGSAABjXDAAA U TEST UPDATE 1 Babb 31-MAR-25
1476 ORA-01476: divisor is equal to zero ORA-06512: at “APPS.EDI_ROW_BEFORE_DEL”, line 4 ORA-04088: error during execution of trigger ‘APPS.EDI_ROW_BEFORE_DEL’ AAnjPPAGSAABjXDAAA D TEST DELETE 1 Babb 31-MAR-25 1000
1
2
3
4
drop table edi_emp1;
drop table edi_emp2;
drop table err$_edi_emp2;
drop trigger edi_row_before_del;

# Unusesd Coulumn

If you are concerned about the length of time it could take to drop column data from all of the rows in a large table, you can use the ALTER TABLE…SET UNUSED statement. This statement marks one or more columns as unused, but does not actually remove the target column data or restore the disk space occupied by these columns. However, a column that is marked as unused is not displayed in queries or data dictionary views, and its name is removed so that a new column can reuse that name. All constraints, indexes, and statistics defined on the column are also removed.

The ALTER TABLE…DROP UNUSED COLUMNS statement is the only action allowed on unused columns. It physically removes unused columns from the table and reclaims disk space.

1
2
3
4
5
6
7
8
9
10
11
12
13
create table edi_test(
a number,
b number,
c number
);

insert into edi_test(a, b, c) values (1, 2, 3);

alter table edi_test set unused(c);

select * from edi_test;

alter table edi_test drop unused columns;

# Virtual Column

1
2
3
4
5
6
7
8
create table edi_test(
a number,
b number,
c as (a + b)
);

insert into edi_test(a, b) values (1, 2);
select * from edi_test;

# Dynamic SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create table edi_t (
id number,
name varchar2(100)
);

create or replace procedure alter_table_column(table_name varchar2,
column_name varchar2,
column_type varchar2) is
begin
execute immediate 'alter table ' || table_name || ' modify ' ||
column_name || ' ' || column_type;
end;

begin
alter_table_column('edi_t', 'name', 'varchar2(30)');
end;

drop procedure alter_table_column;
1
2
3
4
5
6
7
8
9
10
declare
l_tab_name varchar2(30) := 'edi_t';
l_id number := 1;
l_name varchar2(30) := 'Babb';
l_sql_stmt varchar2(100);
begin
l_sql_stmt := 'insert into ' || l_tab_name || ' values (:id, :name)';
execute immediate l_sql_stmt
using l_id, l_name;
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
declare
l_tab_name varchar2(30) := 'edi_t';
l_id number := 1;
l_name varchar2(30) := 'Babb Chen';
l_sql_stmt varchar2(100);
l_updated_name varchar2(30);
begin
l_sql_stmt := 'update ' || l_tab_name ||
' set name = :1 where id = :2 returning name into :3';
execute immediate l_sql_stmt
using l_name, l_id
returning into l_updated_name;
dbms_output.put_line(l_updated_name);
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
create or replace procedure edi_update_sal(p_empno number,
p_sal varchar2,
p_name out varchar2,
p_job out varchar2) is
l_sql_stmt varchar2(100);
begin
l_sql_stmt := 'update edi_t set sal = :1 where empno = :2 returning ename, job into :3, :4';
execute immediate l_sql_stmt
using p_sal, p_empno
returning into p_name, p_job;
end;

declare
l_sql_block varchar2(100);
l_name varchar2(30);
l_job varchar2(30);
begin
l_sql_block := 'begin edi_update_sal(:empno,:sal,:name,:job); end;';
execute immediate l_sql_block
using in 7369, 9999, out l_name, out l_job;
dbms_output.put_line(l_name || ':' || l_job);
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare
type ref_cursor is ref cursor;
emp_rec_cur ref_cursor;
emp_rec edi_t%rowtype;
l_sql_stmt varchar2(100);
begin
l_sql_stmt := 'select * from edi_t where deptno = :1';
open emp_rec_cur for l_sql_stmt using 10;
loop
fetch emp_rec_cur into emp_rec;
exit when emp_rec_cur%notfound;
dbms_output.put_line(emp_rec.empno || '-' || emp_rec.ename);
end loop;
close emp_rec_cur;
end;
1
2
3
4
5
6
7
8
9
10
11
declare
begin
execute immediate 'update edi_t set mgr = :1 where empno = :2'
using null, 7369; --PLS-00457: expressions have to be of SQL types
end;

declare
begin
execute immediate 'update edi_t set mgr = :1 where empno = :2'
using '', 7369;
end;

# Foreign key refer to primary key in same table

1
2
3
4
5
6
7
8
9
10
11
create table edi_emp(
emp_id number primary key,
emp_name varchar2(100),
mgr_id number references edi_emp(emp_id)
);

insert into edi_emp values (1, 'Babb', null);
insert into edi_emp values (2, 'Babbb', 1);
insert into edi_emp values (3, 'Babbc', 1);

insert into edi_emp values (4, 'Babb Chen', 5); --ORA-02291: integrity constraint (APPS.SYS_C001459559) violated - parent key not found

# The number of rows affected

1
2
3
4
5
6
7
8
9
10
create table edi_t(
n number
);

insert into edi_t
select level
from dual
connect by level <= 10;

select * from edi_t;
N
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
begin
update edi_t set n = n + 10;

dbms_output.put_line('Rows Effected: ' || sql%rowcount);
rollback;
end;
--Rows Effected: 10

begin
insert into edi_t values (100);

dbms_output.put_line('Rows Effected: ' || sql%rowcount);
rollback;
end;
--Rows Effected: 1

declare
type l_number_list_type is table of number;
l_number_list l_number_list_type := l_number_list_type();
begin
select n
bulk collect into l_number_list
from edi_t;

dbms_output.put_line(sql%rowcount);
dbms_output.put_line(l_number_list.count);
end;
--10
--10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
declare 
lv_count number;
begin
update edi_t set n = n + 10
returning count(1) into lv_count;

dbms_output.put_line('Rows Effected: ' || lv_count);
rollback;
--Rows Effected: 10
end;

declare
lv_number number;
begin
insert into edi_t values (100)
returning count(1) into lv_number; --PL/SQL: ORA-00934: group function is not allowed here

dbms_output.put_line(lv_number);

rollback;
end;

declare
lv_number number;
begin
insert into edi_t values (100)
returning n into lv_number;

dbms_output.put_line(lv_number);
rollback;
end;
--100

# Trigger on Delete and Truncate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create table edi_t(name varchar2(30));

insert into edi_t values('scott');
insert into edi_t values('babb');

create or replace trigger edi_del_trg before
delete on edi_t
for each row
begin
raise_application_error(-20001, 'Deletion not allowed');
end;

delete from edi_t; --ORA-20001: Deletion not allowed
-- Can truncate
truncate table edi_t;

drop table edi_t;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create table edi_t(name varchar2(30));

insert into edi_t values('scott');
insert into edi_t values('babb');

create or replace trigger edi_truncate_trg before
truncate on schema
begin
if ora_dict_obj_name = 'EDI_T' and ora_dict_obj_type = 'TABLE' then
raise_application_error(-20001, 'Truncate not allowed');
end if;
end;

truncate table edi_t; --ORA-20001: Truncate not allowed
--Can delete
delete from edi_t;


--drop table edi_t;
--drop trigger edi_truncate_trg;

# Instead of trigger

1
2
3
4
5
6
7
8
9
10
11
12
create table edi_emp as select * from scott.emp;
create table edi_dept as select * from scott.dept;

create view edi_emp_dept_v
as
select e.empno, e.ename, e.job, e.mgr,
e.hiredate, e.sal, e.deptno, d.dname
from edi_emp e, edi_dept d
where e.deptno = d.deptno;

insert into edi_emp_dept_v(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO, DNAME)
values (1001, 'BABB', 'IT', 'JBN', sysdate, 1000, 60, 'IA'); --ORA-01779: cannot modify a column which maps to a non key-preserved table
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
create or replace trigger edi_emp_dept_v_trg 
instead of insert on edi_emp_dept_v
declare
l_check_exists number;
begin
select count(*)
into l_check_exists
from edi_dept
where deptno = :new.deptno;

if l_check_exists = 0 then
insert into edi_dept(deptno, dname) values (:new.deptno, :new.dname);
end if;

select count(*)
into l_check_exists
from edi_emp
where empno = :new.empno;

if l_check_exists = 0 then
insert into edi_emp(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
values (:new.EMPNO, :new.ENAME, :new.JOB, :new.MGR, :new.HIREDATE, :new.SAL, :new.DEPTNO);
end if;
end;

insert into edi_emp_dept_v(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO, DNAME)
values (1001, 'BABB', 'IT', 7839, sysdate, 1000, 60, 'IA');
1
select * from edi_dept;
DEPTNO DNAME LOC
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
60 IA
1
select * from edi_emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
7369 SMITH CLERK 7902 1980-12-17 800.00 20
7499 ALLEN SALESMAN 7698 1981-02-20 1600.00 300.00 30
7521 WARD SALESMAN 7698 1981-02-22 1250.00 500.00 30
7566 JONES MANAGER 7839 1981-04-02 2975.00 20
7654 MARTIN SALESMAN 7698 1981-09-28 1250.00 1400.00 30
7698 BLAKE MANAGER 7839 1981-05-01 2850.00 30
7782 CLARK MANAGER 7839 1981-06-09 2450.00 10
7788 SCOTT ANALYST 7566 1982-12-09 3000.00 20
7839 KING PRESIDENT 1981-11-17 5000.00 10
7844 TURNER SALESMAN 7698 1981-09-08 1500.00 0.00 30
7876 ADAMS CLERK 7788 1983-01-12 1100.00 20
7900 JAMES CLERK 7698 1981-12-03 950.00 30
7902 FORD ANALYST 7566 1981-12-03 3000.00 20
7934 MILLER CLERK 7782 1982-01-23 1300.00 10
1001 BABB IT 7839 2024-01-08 07:59:46 PM 1000.00 60

# Constraint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
create table edi_t(
id number,
name varchar2(30),
course_id number,
course_completed varchar2(1),
course_complete_date date
);

alter table edi_t
add constraint c1
check (
(nvl(course_completed, 'N') = 'N' and course_complete_date is null)
or
(nvl(course_completed, 'N') = 'Y' and course_complete_date is not null)
);

insert into edi_t values (1, 'babb', 1001, 'Y', null); --ORA-02290: check constraint (APPS.C1) violated
insert into edi_t values (1, 'babb', 1001, null, sysdate); --ORA-02290: check constraint (APPS.C1) violated
insert into edi_t values (1, 'babb', 1001, null, null);
insert into edi_t values (2, 'aabb', 1002, 'Y', sysdate);

--drop table edi_t;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create table edi_t (
id number,
name varchar2(30),
job varchar2(30)
);

insert into edi_t values (1, 'babb', 'IT');
insert into edi_t values (2, 'aabb', 'IT');
insert into edi_t values (3, 'babb chen', 'CEO');

create unique index i1 on
edi_t (
case when job = 'CEO' then job
else null end
);

insert into edi_t values (4, 'BC', 'CEO'); --ORA-00001: unique constraint (APPS.I1) violated
insert into edi_t values (4, 'BC', 'IT');

--drop table edi_t;

# Use DDL and DML inside function

1
2
3
4
5
6
7
8
9
10
create table edi_t(c1 number);

create or replace function edi_t_fn return number
as
begin
update edi_t set c1 = c1;
return 1;
end;

select edi_t_fn from dual; --ORA-14551: cannot perform a DML operation inside a query
1
2
3
4
5
6
7
8
9
10
create or replace function edi_t_fn return number
as
pragma autonomous_transaction;
begin
update edi_t set c1 = c1;
commit;
return 1;
end;

select edi_t_fn from dual;
EDI_T_FN
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create or replace function edi_t_fn return number
as
pragma autonomous_transaction;
begin
execute immediate 'create table edi_t1(id number)';
execute immediate 'insert into edi_t1 values (1)';
--execute immediate 'drop table edi_t1';

commit;
return 1;
end;
/
select edi_t_fn from dual;
select * from edi_t1;

--drop table edi_t;
--drop function edi_t_fn;
ID
1

# Deferrable novalidate

Deferrable novalidate can make “primary key” creates NONUNIQUE index.

1
2
3
4
5
6
7
8
9
10
11
12
13
create table edi_t( 
no number,
name varchar2(30)
);
/

insert into edi_t
select level, chr(ascii('A') - 1 + level)
from dual connect by level <= 5;

insert into edi_t values (1, 'AA');

select * from edi_t;
NO NAME
1 A
2 B
3 C
4 D
5 E
1 AA
1
2
--The table has two rows with same NO 1
alter table edi_t add constraint no_pk primary key(no); --ORA-02437: cannot validate (APPS.NO_PK) - primary key violated
1
2
3
4
5
6
--No error for deferrable novalidate
alter table edi_t add constraint no_pk primary key(no) deferrable novalidate;

--Insert or update to the 'no' already exists in table will cause unique constraint error
insert into edi_t values(1, 'AAA'); --ORA-00001: unique constraint (APPS.NO_PK) violated
update edi_t set no = 2 where name = 'C'; --ORA-00001: unique constraint (APPS.NO_PK) violated
1
select u.uniqueness, u.* from user_indexes u where u.table_name = 'EDI_T';
UNIQUENESS INDEX_NAME INDEX_TYPE TABLE_OWNER TABLE_NAME TABLE_TYPE UNIQUENESS COMPRESSION PREFIX_LENGTH TABLESPACE_NAME INI_TRANS MAX_TRANS INITIAL_EXTENT NEXT_EXTENT MIN_EXTENTS MAX_EXTENTS PCT_INCREASE PCT_THRESHOLD INCLUDE_COLUMN FREELISTS FREELIST_GROUPS PCT_FREE LOGGING BLEVEL LEAF_BLOCKS DISTINCT_KEYS AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY CLUSTERING_FACTOR STATUS NUM_ROWS SAMPLE_SIZE LAST_ANALYZED DEGREE INSTANCES PARTITIONED TEMPORARY GENERATED SECONDARY BUFFER_POOL FLASH_CACHE CELL_FLASH_CACHE USER_STATS DURATION PCT_DIRECT_ACCESS ITYP_OWNER ITYP_NAME PARAMETERS GLOBAL_STATS DOMIDX_STATUS DOMIDX_OPSTATUS FUNCIDX_STATUS JOIN_INDEX IOT_REDUNDANT_PKEY_ELIM DROPPED VISIBILITY DOMIDX_MANAGEMENT SEGMENT_CREATED
NONUNIQUE NO_PK NORMAL APPS EDI_T TABLE NONUNIQUE DISABLED APPS_TS_TX_DATA 2 255 131072 131072 1 2147483645 0 10 YES 0 1 5 1 1 1 VALID 6 6 2024-01-09 11:15:27 AM 1 1 NO N N N DEFAULT DEFAULT DEFAULT NO NO NO NO NO VISIBLE YES

# The Difference between varchar2(10), varchar2(10 byte) and varchar2(10 char)

  • VARCHAR2(10 byte) will support up to 10 bytes of data, which could be as few as two characters in a multi-byte character sets.
  • VARCHAR2(10 char) could support as much as 40 bytes of information and will support to up 10 characters of data.
  • VARCHAR2(10) uses the current value of NLS_LENGTH_SEMANTICS to determine the limit for the string. incase of byte, then it’s 10 bytes. incase of char, then it’s 10 characters.
1
2
3
SELECT * 
FROM NLS_DATABASE_PARAMETERS
WHERE parameter='NLS_LENGTH_SEMANTICS';
PARAMETER VALUE
NLS_LENGTH_SEMANTICS BYTE

# DML

DML(Data Manipulation Language) 数据操纵语言,SQL 中处理数据等操作统称为数据操纵语言,包括 SELECT, INSERT, UPDATE, DELETE, CALL, EXPLAIN PLAN, LOCK TABLE

# Select 1

select 1 from table 用于判断是否存在满足条件的数据,如果有都返回 1,类似于 select anyCol from table 一般用于判断结果集是否存在。

1
2
3
4
5
6
7
update som_edi_so_import_temp esit
set esit.ret_code = 'E'
where esit.ret_code is null
and not exists (
select 1
from hr_operating_units hou
where hou.name = esit.ou_name);

# Select Into

当在 PL_SQL 中执行 SELECT ... INTO ... 语句时,如果返回结果集为空,则回触发 NO_DATA_FOUND 错误;
但是当 SELECT 中有字段用到 max, min, sum, avg... 等聚合函数时,即使结果集为空,也不会触发 NO_DATA_FOUND 错误, 因为聚合函数 max, sum 在处理的时候如果不存在结果集会返回 null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
declare 
l_return varchar2(1);
begin
select *
into l_return
from dual
where 1 <> 1;
end; --ORA-01403: no data found


declare
l_return varchar2(1);
begin
select *
into l_return
from dual
where 1 <> 1;
exception
when no_data_found then
dbms_output.put_line('No Data Found');
end; --No Data Found


declare
l_return varchar2(1);
begin
select count(*)
into l_return
from dual
where 1 <> 1;
end;

注意,当 SELECT ... INTO ... 触发 ORA-01422: exact fetch returns more than requested number of rows 异常时,会将第一个结果插入,如下虽然产生了异常,后续 l_number 的值为 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare
l_number number;
begin
begin
select *
into l_number
from (select 1 from dual union select 2 from dual);
exception
when others then
dbms_output.put_line(sqlerrm);
end;
dbms_output.put_line(l_number);
end;
--ORA-01422: exact fetch returns more than requested number of rows
--1

# Scalar Subqueries

1
2
3
INSERT INTO my_table VALUES ((SELECT 1 FROM dual), NULL);

SELECT substr((SELECT 'ABC' FROM dual), 1, 1) FROM dual;

# Explicit Defaults

1
2
3
INSERT INTO my_table VALUES (1, 'OWNER', DEFAULT, NULL);

UPDATE my_table SET column1 = DEFAULT;

# Insert Into

select 语句如果查询不到数据,insert into 不会产生异常。

1
insert into babb_temp(si) select 1 from dual where 1 <> 1;

# Insert Mutiple

1
2
3
4
5
insert into babb_test_over(name,class,score)
select 'a','3',45 from dual union all
select 'b','3',55 from dual union all
select 'c','2',74 from dual union all
select 'd','3',99 from dual

# Multitable Inserts Insert All and Insert First

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
create table edi_t(
c varchar2(10)
);

create table edi_t1 as select * from edi_t;
create table edi_t2 as select * from edi_t;
create table edi_t3 as select * from edi_t;

insert into edi_t values('a');
insert into edi_t values('b');
insert into edi_t values('c');

select * from edi_t;

insert all
into edi_t1 values(c)
into edi_t2 values(c)
into edi_t3 values(c)
select *
from edi_t;

select * from edi_t1;
select * from edi_t2;
select * from edi_t3;

drop table edi_t;
drop table edi_t1;
drop table edi_t2;
drop table edi_t3;
----------------------------------------------

create table edi_e(
ename varchar2(20),
edeptno number
);

create table edi_e10 as select * from edi_e where 1 <> 1;
create table edi_e20 as select * from edi_e where 1 <> 1;

insert into edi_e values ('Babb', 10);
insert into edi_e values ('Julian', 10);
insert into edi_e values ('Rice', 10);
insert into edi_e values ('Yoyo', 20);
insert into edi_e values ('Owen', 20);

insert all
when edeptno = 10 then
into edi_e10 values(ename, edeptno)
when edeptno = 20 then
into edi_e20 values(ename, edeptno)
select *
from edi_e;

select * from edi_e10;
select * from edi_e20;

drop table edi_e;
drop table edi_e10;
drop table edi_e20;
------------------------------------------------

create table edi_e(
ename varchar2(20),
salary number
);

create table edi_e_low_sal as select * from edi_e where 1 <> 1;
create table edi_e_mid_sal as select * from edi_e where 1 <> 1;
create table edi_e_high_sal as select * from edi_e where 1 <> 1;


insert into edi_e values ('Babb', 1000);
insert into edi_e values ('Yoyo', 2000);
insert into edi_e values ('Owen', 3000);
insert into edi_e values ('Julian', 4000);
insert into edi_e values ('Rice', 5000);

/*insert all
when salary <= 2000 then
into edi_e_low_sal values (ename, salary)
when salary > 2000 and salary <= 4000 then
into edi_e_mid_sal values (ename, salary)
else
into edi_e_high_sal values (ename, salary)
select *
from edi_e;*/


insert first --找到符合条件的就不再继续往下走
when salary <= 2000 then
into edi_e_low_sal values (ename, salary)
when salary <= 4000 then
into edi_e_mid_sal values (ename, salary)
else
into edi_e_high_sal values (ename, salary)
select *
from edi_e;


select * from edi_e_low_sal;
select * from edi_e_mid_sal;
select * from edi_e_high_sal;

drop table edi_e;
drop table edi_e_low_sal;
drop table edi_e_mid_sal;
drop table edi_e_high_sal;

-------------------------------------------------------

create table edi_region_sales(
prod_id varchar2(10),
apac number,
canada number,
euro number,
us number
);

insert into edi_region_sales values ('PROD1', 100, 200, 300, 400);
insert into edi_region_sales values ('PROD2', 500 , 600, 700, 800);

select * from edi_region_sales;

create table edi_region_sales_row_wise(
prod_id varchar2(10),
region_name varchar2(100),
sales_numbers number
);

insert into edi_region_sales_row_wise
select *
from edi_region_sales
unpivot (column_value for column_name in (apac, canada, euro, us));


select * from edi_region_sales_row_wise;

insert all
into edi_region_sales_row_wise values (prod_id, 'APAC', apac)
into edi_region_sales_row_wise values (prod_id, 'CANADA', canada)
into edi_region_sales_row_wise values (prod_id, 'EURO', euro)
into edi_region_sales_row_wise values (prod_id, 'US', us)
select *
from edi_region_sales;

drop table edi_region_sales;
drop table edi_region_sales_row_wise;

# Insert String Date

ALTER SESSION SET nls_date_format = 'YYYY-MM-DD'

ALTER SESSION SET nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- 创建日历表calendar
CREATE TABLE calendar(
id INTEGER NOT NULL PRIMARY KEY, -- 日历编号
calendar_date DATE NOT NULL UNIQUE, -- 日历日期
calendar_year INTEGER NOT NULL, -- 日历年
calendar_month INTEGER NOT NULL, -- 日历月
calendar_day INTEGER NOT NULL, -- 日历日
is_work_day VARCHAR(1) DEFAULT 'Y' NOT NULL -- 是否工作日
);

-- 创建考勤记录表attendance
CREATE TABLE attendance(
id INTEGER NOT NULL PRIMARY KEY, -- 考勤记录编号
check_date DATE NOT NULL, -- 考勤日期
emp_id INTEGER NOT NULL, -- 员工编号
clock_in TIMESTAMP, -- 上班打卡时间
clock_out TIMESTAMP, -- 下班打卡时间
CONSTRAINT uk_attendance UNIQUE (check_date, emp_id)
);

-- ALTER SESSION SET nls_date_format = 'YYYY-MM-DD';
-- ALTER SESSION SET nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF';
INSERT INTO calendar VALUES (1,'2021-01-01',2021,1,1,'N');
INSERT INTO calendar VALUES (2,'2021-01-02',2021,1,2,'N');
INSERT INTO calendar VALUES (3,'2021-01-03',2021,1,3,'N');
INSERT INTO calendar VALUES (4,'2021-01-04',2021,1,4,'Y');
INSERT INTO calendar VALUES (5,'2021-01-05',2021,1,5,'Y');
INSERT INTO calendar VALUES (6,'2021-01-06',2021,1,6,'Y');

INSERT INTO attendance VALUES (1,'2021-01-04',1,'2021-01-04 08:34:02.374','2021-01-04 18:33:11.842');
INSERT INTO attendance VALUES (2,'2021-01-04',2,'2021-01-04 08:10:31.367','2021-01-04 19:11:59.19');
INSERT INTO attendance VALUES (3,'2021-01-04',3,'2021-01-04 08:54:08.807','2021-01-04 18:27:21.348');
INSERT INTO attendance VALUES (4,'2021-01-04',4,'2021-01-04 08:27:55.39','2021-01-04 18:10:16.862');
INSERT INTO attendance VALUES (5,'2021-01-04',5,'2021-01-04 08:39:34.557','2021-01-04 18:36:10.973');
INSERT INTO attendance VALUES (6,'2021-01-04',6,'2021-01-04 08:32:34.859','2021-01-04 19:29:04.401');

# Case When

类型 结尾 碰到没有处理的分支而又缺少 ELSE 子句时
case 语句 end case; 报错 ORA-06592: CASE not found while executing CASE statement
case 表达式 end 返回 NULL

Sample Case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CASE SELECTOR
WHEN EXPRESSION 1 THEN STATEMENT 1;
WHEN EXPRESSION 2 THEN STATEMENT 2;
...
WHEN EXPRESSION N THEN STATEMENT N;
ELSE STATEMENT N+1;
END CASE;

if som_edi_temp_d_rec.customer_id = 'XXX' then
case l_edi_shipping_method
when 'S' then l_freight_terms_code := '10FCA';
when 'A' then l_freight_terms_code := '10FCA';
end case;
end if;

Search Case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CASE
WHEN SEARCH CONDITION 1 THEN STATEMENT 1;
WHEN SEARCH CONDITION 2 THEN STATEMENT 2;
...
WHEN SEARCH CONDITION N THEN STATEMENT N;
ELSE STATEMENT N+1;
END CASE;

select name,score,
case
when score >= 90 then
'优秀'
when score between 70 and 89 then
'良好'
when score between 60 and 69 then
'及格'
when score < 60 then
'不及格'
end 等级
from test;

Case When and Decode

1
2
3
select decode(1, 1, 'One', '2', 'Two', 'None'),
decode(2, 1, 'One', '2', 'Two', 'None')
from dual;
D1 D2
One Two
1
2
3
4
5
6
7
8
9
select case 1
when 1 then
'One'
when '2' then --ORA-00932: inconsistent datatypes: expected NUMBER got CHAR
'Two'
else
'None'
end
from dual;

# Trigger

1
2
create table edi_t as select * from scott.emp;
select * from edi_t;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
7369 SMITH CLERK 7902 1980-12-17 800.00 20
7499 ALLEN SALESMAN 7698 1981-02-20 1600.00 300.00 30
7521 WARD SALESMAN 7698 1981-02-22 1250.00 500.00 30
7566 JONES MANAGER 7839 1981-04-02 2975.00 20
7654 MARTIN SALESMAN 7698 1981-09-28 1250.00 1400.00 30
7698 BLAKE MANAGER 7839 1981-05-01 2850.00 30
7782 CLARK MANAGER 7839 1981-06-09 2450.00 10
7788 SCOTT ANALYST 7566 1982-12-09 3000.00 20
7839 KING PRESIDENT 1981-11-17 5000.00 10
7844 TURNER SALESMAN 7698 1981-09-08 1500.00 0.00 30
7876 ADAMS CLERK 7788 1983-01-12 1100.00 20
7900 JAMES CLERK 7698 1981-12-03 950.00 30
7902 FORD ANALYST 7566 1981-12-03 3000.00 20
7934 MILLER CLERK 7782 1982-01-23 1300.00 10

Compound trigger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
create or replace trigger edi_comp_trigger
for insert or update or delete on edi_t compound trigger

g_var varchar2(50) := 'Demo for Compound Trigger';

before statement is
l_var varchar2(50) := 'Before Statement';
begin
dbms_output.put_line(l_var);
if inserting then
dbms_output.put_line('---Insert');
elsif updating then
dbms_output.put_line('---Update');
elsif deleting then
dbms_output.put_line('---Delete');
end if;
end before statement;

before each row is
l_var varchar2(50) := 'Before Each Row';
begin
dbms_output.put_line(l_var);
end before each row;

after each row is
l_var varchar2(50) := 'After Each Row';
begin
dbms_output.put_line(l_var);
end after each row;

after statement is
l_var varchar2(50) := 'After Statement';
begin
dbms_output.put_line(l_var);
end after statement;
end;
/

update edi_t set sal = sal * 2 where deptno = 10;

/*
Before Statement
---Update
Before Each Row
After Each Row
Before Each Row
After Each Row
Before Each Row
After Each Row
After Statement
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
create table edi_t_log(
empno number,
modified_date date,
desc_of_change varchar2(100)
);

create or replace trigger edi_comp_trigger
for update on edi_t compound trigger

type l_log_tab_type is table of edi_t_log%rowtype;
l_log_list l_log_tab_type := l_log_tab_type();

before each row is
l_log_row edi_t_log%rowtype;
begin
l_log_row.empno := :old.empno;
l_log_row.modified_date := sysdate;
l_log_row.desc_of_change := 'OLD SAL = ' || :old.sal || ', NEW SAL = ' || :new.sal;

l_log_list.extend();
l_log_list(l_log_list.last()) := l_log_row;
end before each row;

after statement is
begin
forall i in 1..l_log_list.count
insert into edi_t_log values l_log_list(i);
end after statement;
end;

update edi_t set sal = sal * 2 where deptno = 10;
select * from edi_t_log;
EMPNO MODIFIED_DATE DESC_OF_CHANGE
7782 2024-01-09 10:49:51 AM OLD SAL = 2450, NEW SAL = 4900
7839 2024-01-09 10:49:51 AM OLD SAL = 5000, NEW SAL = 10000
7934 2024-01-09 10:49:51 AM OLD SAL = 1300, NEW SAL = 2600

Mutating trigger

Mutating error occurs whenever a ‘row level’ trigger tries to modify or select data from table that is already undergoing change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
create or replace trigger edi_validate_trg
before update of sal on edi_t
for each row
declare
l_ceo_sal number;
begin
select sal --select
into l_ceo_sal
from edi_t
where job = 'PRESIDENT';

if (:new.sal < l_ceo_sal and :old.job <> 'PRESIDENT') or (:old.job = 'PRESIDENT') then
insert into edi_t_log values (:new.empno, sysdate, 'OLD SAL = ' || :old.sal || ', NEW SAL = ' || :new.sal);
else
insert into edi_t_log values (:new.empno, sysdate, 'Employee salary cannot be more than ' || l_ceo_sal);
end if;
end;

update edi_t set sal = sal * 2 where empno = 7369; --updating
/*
ORA-04091: table APPS.EDI_T is mutating, trigger/function may not see it
ORA-06512: at "APPS.EDI_VALIDATE_TRG", line 4
ORA-04088: error during execution of trigger 'APPS.EDI_VALIDATE_TRG'
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
create or replace trigger edi_validate_trg
for update on edi_t compound trigger
l_ceo_sal number;

before statement is
begin
select sal
into l_ceo_sal
from edi_t
where job = 'PRESIDENT';
end before statement;

before each row is
begin
if (:new.sal < l_ceo_sal and :old.job <> 'PRESIDENT') or (:old.job = 'PRESIDENT') then
insert into edi_t_log values (:new.empno, sysdate, 'OLD SAL = ' || :old.sal || ', NEW SAL = ' || :new.sal);
else
:new.sal := :old.sal;
insert into edi_t_log values (:new.empno, sysdate, 'Employee salary cannot be more than ' || l_ceo_sal);
end if;
end before each row;
end;

update edi_t set sal = sal * 10 where empno = 7369; --updating

Trigger order of same type on same table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
create table edi_t(
id number
);
create sequence edi_t_seq;

create or replace trigger edi_t_trg_a
before insert on edi_t for each row
begin
dbms_output.put_line('A-' || edi_t_seq.nextval);
end;
/

create or replace trigger edi_t_trg_b
before insert on edi_t for each row
begin
dbms_output.put_line('B-' || edi_t_seq.nextval);
end;
/

create or replace trigger edi_t_trg_c
before insert on edi_t for each row
begin
dbms_output.put_line('C-' || edi_t_seq.nextval);
end;
/

insert into edi_t values (1);

/*
C-1
B-2
A-3
*/


create or replace trigger edi_t_trg_a
before insert on edi_t for each row
begin
dbms_output.put_line('A-' || edi_t_seq.nextval);
end;
/

create or replace trigger edi_t_trg_b
before insert on edi_t for each row follows edi_t_trg_a
begin
dbms_output.put_line('B-' || edi_t_seq.nextval);
end;
/

create or replace trigger edi_t_trg_c
before insert on edi_t for each row follows edi_t_trg_b
begin
dbms_output.put_line('C-' || edi_t_seq.nextval);
end;
/

insert into edi_t values (1);

/*
A-4
B-5
C-6
*/

drop trigger edi_t_trg_c;
drop trigger edi_t_trg_b;
drop trigger edi_t_trg_a;

drop table edi_t;
drop sequence edi_t_seq;

# Cursor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
declare
cursor emp_name_list is
select ename from scott.emp;

l_name varchar2(100);
begin
open emp_name_list;
loop
fetch emp_name_list
into l_name;
exit when emp_name_list%notfound;
dbms_output.put_line(l_name);
end loop;
close emp_name_list;
end;

/*
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
declare
cursor emp_name_list is
select ename from scott.emp;
l_name varchar2(100);
begin

if not emp_name_list%isopen then
open emp_name_list;
end if;

loop
fetch emp_name_list into l_name;
exit when emp_name_list%notfound;
dbms_output.put_line(l_name || '-' || emp_name_list%rowcount);
end loop;

if emp_name_list%isopen then
close emp_name_list;
end if;
end;

/*
SMITH-1
ALLEN-2
WARD-3
JONES-4
MARTIN-5
BLAKE-6
CLARK-7
SCOTT-8
KING-9
TURNER-10
ADAMS-11
JAMES-12
FORD-13
MILLER-14
*/

Implicit cursor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
begin
delete from edi_t;

dbms_output.put_line(SQL%ROWCOUNT);

if SQL%ISOPEN then
dbms_output.put_line('Implicit Cursor is Opened');
else
dbms_output.put_line('Implicit Cursor is Closed');
end if;
end;

--0
--Implicit Cursor is Closed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
declare
l_deptno number;

cursor emp_name_list(p_deptno number) is
select ename from scott.emp where deptno = p_deptno;

l_name varchar2(100);
begin
dbms_output.put_line('------- EMP from deptno 10 ------');
open emp_name_list(10);
loop
fetch emp_name_list into l_name;
exit when emp_name_list%notfound;
dbms_output.put_line(l_name);
end loop;
close emp_name_list;


dbms_output.put_line('------- EMP from deptno 20 ------');
open emp_name_list(20);
loop
fetch emp_name_list into l_name;
exit when emp_name_list%notfound;
dbms_output.put_line(l_name);
end loop;
close emp_name_list;
end;

/*
------- EMP from deptno 10 ------
CLARK
KING
MILLER
------- EMP from deptno 20 ------
SMITH
JONES
SCOTT
ADAMS
FORD
*/

Ref Cursor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
declare
type ref_cur_type is ref cursor;

rc_emp_list_cur ref_cur_type;

l_name varchar2(30);
l_dept_row scott.dept%rowtype;
begin
open rc_emp_list_cur for select ename from scott.emp;
loop
fetch rc_emp_list_cur into l_name;
exit when rc_emp_list_cur%notfound;
dbms_output.put_line(l_name);
end loop;
close rc_emp_list_cur;

open rc_emp_list_cur for select * from scott.dept;
loop
fetch rc_emp_list_cur into l_dept_row;
exit when rc_emp_list_cur%notfound;
dbms_output.put_line(l_dept_row.deptno || '-' || l_dept_row.dname);
end loop;
close rc_emp_list_cur;
end;

/*
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
10-ACCOUNTING
20-RESEARCH
30-SALES
40-OPERATIONS
*/

Ref cursor strongly typed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
declare
type ref_cur_type is ref cursor return scott.emp%rowtype;

rc_emp_list_cur ref_cur_type;

l_emp_row scott.emp%rowtype;
begin
open rc_emp_list_cur for select * from scott.emp;
loop
fetch rc_emp_list_cur into l_emp_row;
exit when rc_emp_list_cur%notfound;
dbms_output.put_line(l_emp_row.ename);
end loop;
close rc_emp_list_cur;
end;

/*
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
*/

sys_cursor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
declare
--type ref_cur_type is ref cursor;

rc_emp_list_cur sys_refcursor;

l_name varchar2(30);
l_dept_row scott.dept%rowtype;
begin
open rc_emp_list_cur for select ename from scott.emp;
loop
fetch rc_emp_list_cur into l_name;
exit when rc_emp_list_cur%notfound;
dbms_output.put_line(l_name);
end loop;
close rc_emp_list_cur;

open rc_emp_list_cur for select * from scott.dept;
loop
fetch rc_emp_list_cur into l_dept_row;
exit when rc_emp_list_cur%notfound;
dbms_output.put_line(l_dept_row.deptno || '-' || l_dept_row.dname);
end loop;
close rc_emp_list_cur;
end;

/*
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
10-ACCOUNTING
20-RESEARCH
30-SALES
40-OPERATIONS
*/

Return cursor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
create or replace function edi_t_fn(p_dept_no number)
return sys_refcursor
is
l_emp_list sys_refcursor;
begin
open l_emp_list for select ename from scott.emp where deptno = p_dept_no;

return l_emp_list;
end;
/

declare
l_ref_cursor sys_refcursor;
l_name varchar2(30);
begin
l_ref_cursor := edi_t_fn(10);
--open l_ref_cursor;
loop
fetch l_ref_cursor into l_name;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_name);
end loop;

close l_ref_cursor;
end;

/*
CLARK
KING
MILLER
*/

Current of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
declare 
cursor emp_list is
select empno from edi_emp where deptno = 10 for update;

l_empno number;
begin
open emp_list;
loop
fetch emp_list into l_empno;
exit when emp_list%notfound;

dbms_output.put_line('Updating ' || l_empno);

update edi_emp set sal = sal + 9999
where current of emp_list;

dbms_output.put_line('Updating count ' || emp_list%rowcount);

end loop;
close emp_list;
end;

/*
Updating 7782
Updating count 1
Updating 7839
Updating count 2
Updating 7934
Updating count 3
*/

Cursor vs Collection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
create or replace type edi_varchar_list is table of varchar2(30);

create or replace procedure edi_get_emps_use_collection(p_deptno in number,
p_out_name_list out edi_varchar_list) as
l_varchar_list edi_varchar_list := edi_varchar_list();
begin
select ename
bulk collect into l_varchar_list
from scott.emp
where deptno = p_deptno;

p_out_name_list := l_varchar_list;
end;

create or replace procedure edi_get_emps_use_ref_cursor(p_deptno in number,
p_out_name_list out sys_refcursor) as
begin
open p_out_name_list for
select ename from scott.emp where deptno = p_deptno;
end;


create or replace procedure edi_use_ref_cursor(p_in varchar2,
p_out_cur out sys_refcursor) as
begin
if p_in = 'ENAME' then
open p_out_cur for
select ename from scott.emp;
elsif p_in = 'DNAME' then
open p_out_cur for
select dname from scott.dept;
else
open p_out_cur for
select 'Invalid Input' from dual;
end if;
end;

drop type edi_varchar_list;
drop procedure edi_get_emps_use_collection;
drop procedure edi_get_emps_use_ref_cursor;
drop procedure edi_use_ref_cursor;

Cursor in package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
create or replace package edi_t as 
cursor normal_cur is select * from scott.emp;
end;

declare
l_emp scott.emp%rowtype;
begin
open edi_t.normal_cur;
loop
fetch edi_t.normal_cur into l_emp;
exit when edi_t.normal_cur%notfound;
dbms_output.put_line(l_emp.ename);
end loop;
close edi_t.normal_cur;
end;

--Error: PLS-00994: Cursor Variables cannot be declared as part of a package
create or replace package edi_t as
l_ref_cursor sys_refcursor;
end;

--Error: PLS-00994: Cursor Variables cannot be declared as part of a package
create or replace package edi_t as
type l_ref_cursor_type is ref cursor;
l_ref_cursor l_ref_cursor_type;
end;

drop package edi_t;

# Ref Cursor

ref cursor 可以用于定义游标变量,定义游标时不需要指定相应的 select 语句,但是当使用游标时(open)需要指定 select 语句。

使用 pl/sql 编写一个块,可以输入部门号,并显示该部门所有员工姓名和他的工资。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
declare
--定义游标类型
type sp_emp_copy_cursor is ref cursor;
--定义一个游标变量
test_cursor sp_emp_copy_cursor;
v_ename emp_copy.ename%type;
v_sal emp_copy.sal%type;

begin
--把test_cursor和一个select结合
open test_cursor for select ename,sal from emp_copy where deptno=&no;
--循环取出(fethch)
loop
fetch test_cursor into v_ename,v_sal;
--判断test_cursor是否为空
exit when test_cursor%notfound;
dbms_output.put_line('名字:'||v_ename||'工资:'||v_sal);
end loop;
--关闭游标
close test_cursor;
end;

ref cursor 也称为动态游标,直到运行时才知道这条查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
declare
-- dynamic cursor
type rc is ref cursor;
-- static cursor
cursor c is select * from dual;
l_cursor rc;
begin
if (to_char(sysdate,'dd') = 30) then
-- ref cursor with dynamic sql
open l_cursor for 'select * from emp';
elsif (to_char(sysdate,'dd') = 29) then
-- ref cursor with static sql
open l_cursor for select * from dept;
else
-- ref cursor with static sql
open l_cursor for select * from dual;
end if;
-- the "normal" static cursor
open c;
end;

# Rowid Rownum

rownum 使用 SELECT 语句返回的结果集,若希望按特定条件查询前 N 条记录,可以使用伪列 rownum 。rownum 是对结果集加的一个伪列,即先查到结果集之后再加上去的一个列 (强调:先要有结果集)。简单的说 rownum 是符合条件结果的序列号。它总是从 1 开始排起的。使用 rownum 时,只能使用 <、<=、!= 符号。

rowid 是数据的详细地址,通过 rowid,oracle 可以快速的定位某行具体的数据的位置。rowid 可以分为物理 rowid 和逻辑 rowid 两种。普通的表中的 rowid 是物理 rowid,索引组织表 (IOT) 的 rowid 是逻辑 rowid。当表中有大量重复数据时,可以使用 rowid 快速删除重复的记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
--1. rownum 使用
--(1) 找到第二行以后的记录
select * from (select rownum no,id,name from student) where no>2;

--(2) rownum 和排序
--oracle中的rownum是在取数据的时候产生的序号,所以想对指定排序的数据指定rowmun行数据就必须注意了。可以看出,rownum并不是按照name列来生成的序号。系统是按照记录插入时的顺序给记录排的号,rowid也是顺序分配的。
select rownum,id,name from student order by name;
rownum id name
---------- ------ ---------------------------------------------------
3 200003 李三
2 200002 王二
1 200001 张一
4 200004 赵四

--为了解决这个问题,需要使用子查询
select rownum,id,name from (select * from student order by name);
rownum ID NAME
---------- ------ ---------------------------------------------------
1 200003 李三
2 200002 王二
3 200001 张一
4 200004 赵四

--(3) 更新表的某一列为 rownum

UPDATE T a
SET a.neworder =
(SELECT b.newOrder
FROM (SELECT rownum over(ORDER BY amount DESC) newOrder,
ROWID browid
FROM T) b
WHERE b.browid = a.rowid)

--(4) 更新表不相关的两列
将 test_b mac 列覆盖 test_a mac 列

create table test_a (
mac varchar2(100)
);

create table test_b (
mac varchar2(100)
);

insert into test_a values('aaa');
insert into test_a values('bbb');

insert into test_b values('ccc');
insert into test_b values('ddd');

select * from test_a;
select * from test_b;

update test_a t
set t.mac =
(select b.mac
from (select rownum rn, mac from test_a) a,
(select rownum rn, mac from test_b) b
where a.rn = b.rn
and t.mac = a.mac);

-- 2.rowid 的使用

--快速删除重复的记录

delete from stu a
where rowid not in (select max(b.rowid)
from stu b
where a.no = b.no
and a.name = b.name
and a.sex = b.sex); --这里 max 使用 min 也可以

delete from stu a
where rowid < (select max(b.rowid)
from stu b
where a.no = b.no
and a.name = b.name
and a.sex = b.sex); --这里如果把 max 换成 min 的话,前面的 where 子句中需要把"<"改为">"

delete from stu
where rowid not in
(select max(rowid)
from stu t
group by t.no, t.name, t.sex); --使用了 group by,减少了显性的比较条件,提高效率

delete from stu a
where rowid not in (select max(b.rowid)
from stu b
where a.no < b.no
and a.name = b.name); --取 name 重复学号大的数据

# Set Operation

操作 描述 重复行 排序
union 并集 不包括 默认规则的排序
union all 并集 包括 不进行排序
intersect 交集 不包括 默认规则的排序
minus 差集 不包括 默认规则的排序

union 和 union all 的区别是,union 会自动压缩多个结果集合中的重复结果,并进行默认排序。而 union all 则将所有的结果全部显示出来,不进行排序。

# Column Alias

如果字段别名中有空格等特殊字符,使用双引号引起来

1
select fnd_profile.value('ORG_ID')  "org id"  from dual;

# Implicit Conversion

操作 转换
比较 / 算术运算 字符型转换为数值型
连接时 (||) 数值型转换为字符型
赋值、调用函数时 定义的变量类型为准
1
2
3
4
5
6
7
select '1' || 2  + 2 from dual;
-- 14

select 1 from dual where '12' > 2 ; --12 > 2 but '12' < '2'
-- 1
select '1' + 1 from dual;
-- 2

# Null

一般对于可空字段需要做特殊处理,因为 null 与 任何其它字段比较都会返回 undefined ,例如 null = null & null != null & null = 'XXX' & null <> 'XXX' 都不成立。

1
2
3
4
5
6
7
8
9
cursor c_som_edi_temp_d is
select *
from som_edi_temp_d
where edi_id = p_edi_id
and nvl(transfer, 'Y') <> 'D';

select 1 from dual null = null
select 1 from dual null != null
select 1 from dual null <> 'D'

# Null & ‘’

1
2
3
select length(''), length(null) from dual; --'' ''
select nvl('', 1), nvl(null, 1) from dual; --1 1
select * from dual where '' is null; --X

# Nulls First

nulls first 将 null 查询结果集排在最前面; nulls last 将 null 查询结果集排在最后面。

1
2
3
4
5
6
7
select *
from som_edi_810_header seh
order by seh.invoice_date nulls last;

select *
from som_edi_810_header seh
order by seh.invoice_date nulls first;

# Order by varchar as number

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT column_name
FROM table_name
ORDER BY
CASE
--WHEN REGEXP_LIKE(column_name, '^[0-9]+$') THEN TO_NUMBER(column_name)
WHEN REGEXP_LIKE(column_name, '^\d+$') THEN TO_NUMBER(column_name)
ELSE NULL
END ASC, -- Sorts numeric values
CASE
--WHEN NOT REGEXP_LIKE(column_name, '^[0-9]+$') THEN column_name
WHEN NOT REGEXP_LIKE(column_name, '^\d+$') THEN column_name
ELSE NULL
END ASC; -- Sorts non-numeric values
1
2
3
SELECT column_name
FROM table_name
ORDER BY LPAD(column_name, 20, '0');

# For Update

for updatefor update nowait 是对操作的数据行进行加锁,在事务提交前防止其他操作对数据的修改。 for updatefor update nowait 主要区别在于是否等待,如果不加 nowait ,在另一个 session 执行 update 时就会等待,直至锁被释放。如果添加了 nowait 在另一个 session 执行 update 时就会报错 ORA-00054: resource busy

1
2
select * from som_edi_temp_h seh where seh.edi_id = '15194' for update
select * from som_edi_temp_h seh where seh.edi_id = '15194' for update nowait

# Escape Character

oracle 中从第二个 ' 开始表示转义符,使用 escape 可以设置其它字符为转义字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select ''''||sd.ordered_quantity||chr(39) from som_edi_temp_d sd;
-- 输出 '30', 第三个 ' 被第二个 ' 转义为一个 ', 也可以使用 chr(39) 直接表示 '
-- _表示确切的未知字符
select * from t11 where name like '%_%';
ID NAME
---------- --------------------
1 baaab
2 ba_ab
3 ba%ab
-- escape 设置 \ 为转义符
select * from t11 where name like '%\_%' escape '\';
ID NAME
---------- --------------------
2 ba_ab

# Quote

plsql 使用单引号作为转义符

最外层的两个单引号作为引号操作符,第二个作为转义符号,第三个单引号是真实的数据输出
select '''' from dual; 输出 '
q’’ 内的内容会原样输出
select q'[Route du Nant-d'Avril, 150]' from dual;
替换字符串中的单引号为双引号
replace(str, '''', '''''')

1
2
3
4
5
6
select '''SQL'' is structured query language' as q1,
q'@'SQL' is structured query language@' as q2,
Q'@'SQL' is structured query language@' as q3,
q'['SQL' is structured query language]' as q4,
Q'['SQL' is structured query language]' as q5
from dual;
Q1 Q2 Q3 Q4 Q5
‘SQL’ is structured query language ‘SQL’ is structured query language ‘SQL’ is structured query language ‘SQL’ is structured query language ‘SQL’ is structured query language

# Flash Back

1
2
3
4
5
6
7
8
9
10
11
delete from babb_emp;
-- 打印 babb_emp 表一分钟前的数据(表名不能用简写形式)
select * from babb_emp as of timestamp (sysdate - 1/1440);
-- 添加条件
select * from babb_emp as of timestamp (sysdate - 1/1440) where babb_emp.id = '123';

-- Retrieve package was drop or replace
select *
from all_source as of timestamp to_timestamp('20200902 16:00:00', 'YYYYMMDD HH24:MI:SS')
where name = upper('som_edi_custom_utils_pkg')
order by type, line

# Query Clob

1
2
3
select dbms_lob.substr(dbms_metadata.get_ddl('TABLE', 'EMP', 'SCOTT')),
cast(dbms_metadata.get_ddl('TABLE', 'EMP', 'SCOTT') as varchar2(4000))
from dual;

# Update Use Subquery

select 1 from dual where 1 <> 1 查询不到数据,但是会显示更新了 xx 条数据,结果 customer_id 都会更新为 null。
因为没有为 som_edi_temp_d_temp 添加条件会更新这张表的所有数据, select 1 from dual where 1 <> 1 没有数据,customer_id 都会变成 null, 正确的更新做法是为 som_edi_temp_d_temp 添加条件限制要更新这张表的哪些数据。

1
2
3
4
5
6
7
8
9
10
11
update som_edi_temp_d_temp sed
set sed.customer_id = (
select 1 from dual where 1 <> 1
);


update som_edi_temp_d_temp sed
set sed.customer_id = (
select 1 from dual where 1 <> 1
)
where sed.customer_id is null;

# Trigger Implements ID Autoincrement

注意这里使用的是 BEFORE, 如果使用 AFTER,记录已经写入表中,需要使用 Upadate 更新。

1
2
3
4
5
CREATE OR REPLACE TRIGGER YSC.som_hub_dn_upload_header_bi1 BEFORE
INSERT ON som_hub_dn_upload_header FOR EACH ROW WHEN (NEW.header_id is null)
BEGIN
SELECT som_hub_dn_upload_header_s.NEXTVAL INTO :NEW.header_id FROM DUAL;
END;

Header 插入多条记录时,不使用 Cursor, 使 Line 关联到 Header 的 Unique Header Id.

  1. 在 Header 上创建 Trigger,当没有提供 Id 时,自动根据索引生成 Id. 或者 直接使用 som_edi_asn_h_s.nextval。
  2. Insert Line 时连接 Header, 条件是可能导致 Header 拆分成多条的 Column.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
CREATE OR REPLACE TRIGGER som_edi_asn_header_ai before
INSERT ON som_edi_asn_header FOR EACH ROW WHEN (NEW.header_id is null)
BEGIN
SELECT som_edi_asn_h_s.nextval INTO :NEW.header_id FROM DUAL;
END;

insert into som_edi_asn_header
( --HEADER_ID, --use trigger
--HEADER_ID, --or use som_edi_asn_h_s.nextval directly
DELIVERY_ID,
CUSTOMER_PO,
ITEM_NO)
select --l_header_id,
--som_edi_asn_h_s.nextval
sh.delivery_id,
sh.customer_po,
sl.item_no
from som_hub_dn_upload_header sh,
som_hub_dn_upload_line sl
where sh.header_id = sl.header_id
and sh.delivery_id = p_delivery_id
group by sh.delivery_id, sh.customer_po, sl.item_no;

insert into som_edi_asn_line
(HEADER_ID,
PALLET_NO,
CARTON_NO,
DEVICE_SERIAL_NUMBER)
select --l_header_id,
seh.header_id,
sl.pallet_id,
sl.carton_id,
sl.prod_sn,
from som_hub_dn_upload_header sh,
som_hub_dn_upload_line sl,
som_edi_asn_header seh
where sh.header_id = sl.header_id
and sh.delivery_id = seh.delivery_id
and sh.customer_po = seh.customer_po
and sl.item_no = seh.item_no
and sh.delivery_id = p_delivery_id
and nvl(sh.status, 'N') = 'N';

# Materialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
begin
with t as
(select /*+ MATERIALIZE */
som_edi_pkg.get_hub_invoice(sel.so_line_id) hub_invoice,
som_edi_pkg.get_qty_per_pallet(sel.invoice_line_id) qty_per_pallet
from som_commercial_invoice_headers seh,
som_commercial_invoice_lines sel
where seh.invoice_header_id = sel.invoice_header_id
and seh.invoice_no = p_invoice_no)

select count(*)
into l_count
from t
where t.hub_invoice is null
or t.qty_per_pallet is null;

if l_count <> 0 then
debug('Get HUB Invoice No or Calculate Qty Per Pallet error.');

retcode := '2';
return;
end if;
end;

# count(*) over()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function get_estimated_invoice_date(p_invoice_no  in varchar2,
p_invoice_seq in varchar2) return date
is
l_return date;
begin
begin
select max(case
when acceptance_id_count = 0 and acceptance_date_count = 0 then ship_date
when acceptance_id_count = total_line_count and acceptance_id_count = acceptance_date_count then acceptance_date
else null
end) invoice_date
into l_return
from (select asn.ship_date,
orde.revrec_signature_date as acceptance_date,
count(orde.contingency_id) over() as acceptance_id_count,
count(orde.revrec_signature_date) over() as acceptance_date_count,
count(*) over() as total_line_count
from (select distinct h.invoice_no,
h.invoice_seq,
h.ship_date,
l.so_line_id
from som_edi_856_header h, som_edi_856_line l
where h.edi856_id = l.edi856_id) asn,
oe_order_lines_all orde
where asn.invoice_no = p_invoice_no
and asn.invoice_seq = p_invoice_seq
and asn.so_line_id = orde.line_id(+));
exception
when others then
l_return := null;
end;

return l_return;
end get_estimated_invoice_date;

# Bulk Connect Into

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
declare 
type varchar_list_type is table of varchar2(100);
l_list1 varchar_list_type := varchar_list_type();
l_list2 varchar_list_type := varchar_list_type();
l_start_time number;
l_end_time number;
begin

l_start_time := dbms_utility.get_time();
for i in (select * from som_edi_temp_h) loop
l_list1.extend();
l_list1(l_list1.last()) := i.cust_po_number;
end loop;
l_end_time := dbms_utility.get_time();
dbms_output.put_line(l_end_time - l_start_time);

l_start_time := dbms_utility.get_time();
select cust_po_number
bulk collect into l_list2
from som_edi_temp_h;
l_end_time := dbms_utility.get_time();
dbms_output.put_line(l_end_time - l_start_time);
end;

--58
--2

# Forall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
select * from som_edi_temp_h;
create table edi_t1 as select * from som_edi_temp_h where 1 <> 1;
create table edi_t2 as select * from som_edi_temp_h where 1 <> 1;

declare
type list_type is table of som_edi_temp_h%rowtype;
l_list2 list_type := list_type();
l_start_time number;
l_end_time number;
begin
select *
bulk collect into l_list2
from som_edi_temp_h;

l_start_time := dbms_utility.get_time();
for i in l_list2.first..l_list2.last loop
insert into edi_t1 values l_list2(i);
end loop;
l_end_time := dbms_utility.get_time();
dbms_output.put_line(l_end_time - l_start_time);

l_start_time := dbms_utility.get_time();
forall i in l_list2.first..l_list2.last
insert into edi_t2 values l_list2(i);
l_end_time := dbms_utility.get_time();
dbms_output.put_line(l_end_time - l_start_time);
end;
--37
--2

drop table edi_t1;
drop table edi_t2;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
create table edi_t (name varchar2(5) not null);

declare
type name_list_type is table of varchar2(100);
l_name_list name_list_type := name_list_type();
begin
l_name_list.extend(5);
l_name_list(1) := null;
l_name_list(2) := 'A';
l_name_list(3) := 'AA';
l_name_list(4) := 'AAA';
l_name_list(5) := 'AAAAAAA';

forall i in l_name_list.first..l_name_list.last save exceptions
insert into edi_t values (l_name_list(i));
exception
when others then
dbms_output.put_line('Error count = ' || sql%bulk_exceptions.count);
for i in 1..sql%bulk_exceptions.count loop
dbms_output.put_line('Error index position = ' || sql%bulk_exceptions(i).error_index);
dbms_output.put_line('Error code = ' || sql%bulk_exceptions(i).error_code);
dbms_output.put_line('Error message = ' || sqlerrm(-sql%bulk_exceptions(i).error_code));
end loop;
end;

--Error count = 2
--Error index position = 1
--Error code = 1400
--Error message = ORA-01400: cannot insert NULL into ()
--Error index position = 5
--Error code = 12899
--Error message = ORA-12899: value too large for column (actual: , maximum: )

select * from edi_t;
NAME
A
AA
AAA

# View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
create or replace view som_edi_mes_header_v as
select "dbname" as dbname,
"head_id" as head_id,
"invoice_no" as invoice_no,
"exc_seq" as exc_seq,
"work_order" as work_order,
"part_no" as part_no,
"customer" as customer,
"qty" as qty,
"ctns" as ctns,
"pallets" as pallets,
"po_no" as po_no,
"delivery_no" as delivery_no,
"create_time" as create_time,
"update_time" as update_time,
"country_of_origin" as country_of_origin,
"transfer_flag" as transfer_flag,
"transfer_time" as transfer_time,
"update_by" as update_by,
"model_name" as model_name,
"ver_no" as ver_no,
"tran_create_time" as tran_create_time
from (
select 'SN' as "dbname", sn.* from "dbo"."ssc_t_edi_head"@mes2edi.sn sn
union all
select 'SC' as "dbname", sc.* from "dbo"."ssc_t_edi_head"@mes2edi.sc sc
);

--dblink 是 Oracle 连接到 SQLServer DB
--现在不用在 delivery_no 上加双引号,sev."delivery_no" = '102234'
select * from som_edi_mes_header_v sev where sev.delivery_no = '102234'

# Force View

1
2
3
drop table edi_t;
create or replace view edi_t_v as
select * from edi_t; --ORA-00942: table or view does not exist
1
2
3
4
5
create or replace force view edi_t_v as
select * from edi_t;

select * from user_views where view_name = 'EDI_T_V';
select u.status, u.* from user_objects u where u.object_name = 'EDI_T_V';
STATUS OBJECT_NAME SUBOBJECT_NAME OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE CREATED LAST_DDL_TIME TIMESTAMP STATUS TEMPORARY GENERATED SECONDARY NAMESPACE EDITION_NAME
INVALID EDI_T_V 8462071 VIEW 2024-01-09 11:46:24 AM 2024-01-09 11:46:24 AM 2024-01-09:11:46:24 INVALID N N N 1
1
2
3
4
select * from edi_t_v; --ORA-04063: view "APPS.EDI_T_V" has errors

create table edi_t as select * from scott.emp where deptno = 10;
select * from edi_t_v;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
7782 CLARK MANAGER 7839 1981-06-09 2450.00 10
7839 KING PRESIDENT 1981-11-17 5000.00 10
7934 MILLER CLERK 7782 1982-01-23 1300.00 10

# Text of force view

1
2
3
4
5
6
7
8
9
10
11
12
create or replace force view edi_t_v as
select * from edi_t;

select text from user_views where view_name = 'EDI_T_V'; --select * from edi_t

create table edi_t as select * from scott.emp;
select text from user_views where view_name = 'EDI_T_V'; --select * from edi_t
select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --INVALID

select * from edi_t_v; --Auto Recompile
select text from user_views where view_name = 'EDI_T_V'; --select "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" from edi_t
select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --VALID

# View WITH READ ONLY constraint

1
2
3
4
5
6
7
create table edi_t(c number);

create or replace view edi_t_v as
select * from edi_t;

insert into edi_t_v values (1);
select * from edi_t;
C
1
1
2
3
4
5
6
7
8
create or replace view edi_t_v as
select * from edi_t
with read only;

insert into edi_t_v values (1); --ORA-42399: cannot perform a DML operation on a read-only view

--drop table edi_t;
--drop view edi_t_v;

# View WITH CHECK OPTION constraint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create table edi_t(
id number,
name varchar2(30),
dept varchar2(30)
);
/

insert into edi_t values (1, 'A', 'XX');
insert into edi_t values (2, 'B', 'XX');
insert into edi_t values (3, 'C', 'XX');
insert into edi_t values (4, 'D', 'YY');
insert into edi_t values (5, 'E', 'YY');
insert into edi_t values (6, 'F', 'YY');

create or replace view edi_t_v as
select *
from edi_t
where dept = 'XX'
with check option;

insert into edi_t_v values (7, 'G', 'XX');
insert into edi_t_v values (8, 'H', 'YY'); --ORA-01402: view WITH CHECK OPTION where-clause violation
update edi_t_v set dept = 'YY' where id = 1; --ORA-01402: view WITH CHECK OPTION where-clause violation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create or replace view edi_t_v as
select *
from edi_t
where dept = 'XX'
with check option constraint check_option_constraint;

create or replace view edi_t_v2 as
select *
from edi_t
where dept = 'YY'
with read only constraint read_only_constraint;

select * from user_views where view_name in ('EDI_T_V', 'EDI_T_V2');
select * from user_constraints where table_name in ('EDI_T_V', 'EDI_T_V2');
OWNER CONSTRAINT_NAME CONSTRAINT_TYPE TABLE_NAME SEARCH_CONDITION R_OWNER R_CONSTRAINT_NAME DELETE_RULE STATUS DEFERRABLE DEFERRED VALIDATED GENERATED BAD RELY LAST_CHANGE INDEX_OWNER INDEX_NAME INVALID VIEW_RELATED
APPS CHECK_OPTION_CONSTRAINT V EDI_T_V ENABLED NOT DEFERRABLE IMMEDIATE NOT VALIDATED USER NAME 2024-01-09 02:41:11 PM
APPS READ_ONLY_CONSTRAINT O EDI_T_V2 ENABLED NOT DEFERRABLE IMMEDIATE NOT VALIDATED USER NAME 2024-01-09 02:41:11 PM

# Check the list of objects used in view

1
2
3
4
5
6
create or replace view edi_t_v as
select e.*, d.dname
from scott.emp e, scott.dept d
where e.deptno = d.deptno;

select * from user_dependencies u where u.name = 'EDI_T_V';
NAME TYPE REFERENCED_OWNER REFERENCED_NAME REFERENCED_TYPE REFERENCED_LINK_NAME SCHEMAID DEPENDENCY_TYPE
EDI_T_V VIEW SCOTT EMP TABLE 173 HARD
EDI_T_V VIEW SCOTT DEPT TABLE 173 HARD

# Compile and Recompile View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create table edi_t as select * from scott.emp;

create or replace view edi_t_v as
select * from edi_t;

select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --VALID

alter table edi_t drop column sal;
select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --INVALID

alter table edi_t add sal number;
select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --INVALID

alter view edi_t_v compile; --Compile
select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --VALID

select * from edi_t_v; --Auto Recompile
select u.status, u.* from user_objects u where u.OBJECT_NAME = 'EDI_T_V'; --VALID

# Inline View

1
2
3
4
select (select d.dname from scott.dept d where d.deptno = e.deptno) as dname,
round(avg(sal))
from scott.emp e
group by deptno;
DNAME ROUND(AVG(SAL))
SALES 1567
RESEARCH 2175
ACCOUNTING 2917
1
2
3
4
5
select e.*, d.avg_sal
from scott.emp e,
(select deptno, avg(sal) avg_sal from scott.emp group by deptno) d
where e.deptno = d.deptno
and e.sal > d.avg_sal;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO AVG_SAL
7698 BLAKE MANAGER 7839 1981-05-01 2850.00 30 1566.666667
7499 ALLEN SALESMAN 7698 1981-02-20 1600.00 300.00 30 1566.666667
7902 FORD ANALYST 7566 1981-12-03 3000.00 20 2175
7788 SCOTT ANALYST 7566 1982-12-09 3000.00 20 2175
7566 JONES MANAGER 7839 1981-04-02 2975.00 20 2175
7839 KING PRESIDENT 1981-11-17 5000.00 10 2916.666667

# Any and In

1
2
3
4
5
6
7
8
9
10
-- Any and In
with t as (
select level l
from dual
connect by level <= 10
)
--select * from t where l in (1, 3, 5)
--select * from t where l = any(1, 3, 5)
--select * from t where l > any(1, 3, 5)
select * from t where l < any(1, 3, 5);
L
1
2
3
4

# RANK & DENSE_RANK as Aggreate function and Analytical function

Analytical function

1
2
3
4
5
6
select empno,
ename,
sal,
rank() over(order by sal desc) rank,
dense_rank() over(order by sal desc) dense_rank
from scott.emp;
EMPNO ENAME SAL RANK DENSE_RANK
7839 KING 5000.00 1 1
7902 FORD 3000.00 2 2
7788 SCOTT 3000.00 2 2
7566 JONES 2975.00 4 3
7698 BLAKE 2850.00 5 4
7782 CLARK 2450.00 6 5
7499 ALLEN 1600.00 7 6
7844 TURNER 1500.00 8 7
7934 MILLER 1300.00 9 8
7521 WARD 1250.00 10 9
7654 MARTIN 1250.00 10 9
7876 ADAMS 1100.00 12 10
7900 JAMES 950.00 13 11
7369 SMITH 800.00 14 12

Aggregate function

1
2
3
select rank(1600) within group(order by sal desc) rank_of_1600,
dense_rank(1600) within group(order by sal desc) dense_rank_of_1600
from scott.emp;
RANK_OF_1600 DENSE_RANK_OF_1600
7 6

# Exclude duplicate records while insertion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
reate table edi_t_target(
id number primary key,
name varchar2(10)
);

insert into edi_t_target values(1, 'A');
insert into edi_t_target values(2, 'B');
insert into edi_t_target values(3, 'C');
insert into edi_t_target values(4, 'D');

create table edi_t_source as select * from edi_t_target where id <= 2;
insert into edi_t_source values (5, 'E');
insert into edi_t_source values (6, 'F');

select * from edi_t_target;
select * from edi_t_source;
ID NAME
1 A
2 B
3 C
4 D
ID NAME
---- ----
1 A
2 B
3 C
4 D
1
insert into edi_t_target select * from edi_t_source; --ORA-00001: unique constraint (APPS.SYS_C001472303) violated

Error log tables

1
2
3
4
5
6
7
8
9
10
11
begin
dbms_errlog.create_error_log(dml_table_name => 'edi_t_target');
end;
/

insert into edi_t_target select * from edi_t_source
log errors into err$_edi_t_target reject limit unlimited;

select * from edi_t_target;
select * from err$_edi_t_target;
rollback;
ID NAME
1 A
2 B
3 C
4 D
5 E
6 F
ORA_ERR_NUMBER$ ORA_ERR_MESG$ ORA_ERR_ROWID$ ORA_ERR_OPTYP$ ORA_ERR_TAG$ ID NAME
--------------- ----------------------------------------------------------- -------------- -------------- ------------ ---- ----
1 ORA-00001: unique constraint (APPS.SYS_C001459597) violated I 1 A
1 ORA-00001: unique constraint (APPS.SYS_C001459597) violated I 2 B

Hint IGNORE_ROW_ON_DUPKEY_INDEX

1
2
3
4
5
insert /*+ IGNORE_ROW_ON_DUPKEY_INDEX(edi_t_target(id)) */
into edi_t_target select * from edi_t_source;

select * from edi_t_target;
rollback;
ID NAME
1 A
2 B
3 C
4 D
5 E
6 F

# Exclude duplicate records while insertion not has primary key

Merge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
create table edi_t_target(
id number,
name varchar2(10)
);
/

insert into edi_t_target values(1, 'A');
insert into edi_t_target values(2, 'B');
insert into edi_t_target values(3, 'C');
insert into edi_t_target values(4, 'D');

create table edi_t_source as select * from edi_t_target where id <= 2;
insert into edi_t_source values (5, 'E');
insert into edi_t_source values (6, 'F');

merge into edi_t_target t
using edi_t_source s on (t.id = s.id)
when not matched then
insert (id, name) values (s.id, s.name);

select * from edi_t_target;
rollback;
ID NAME
1 A
2 B
3 C
4 D
5 E
6 F

Subquery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
insert into edi_t_target
select * from edi_t_source
minus
select * from edi_t_target;

select * from edi_t_target;
rollback;


insert into edi_t_target
select *
from edi_t_source
where id not in (select id from edi_t_target);

select * from edi_t_target;
rollback;

--drop table edi_t_target;
--drop table edi_t_source;

# Merge with error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
create table edi_t_target(
id number,
name varchar2(5)
);
/

create table edi_t_source(
id number,
name varchar2(30),
resigned varchar2(1)
);
/

insert into edi_t_target values(1, 'A');
insert into edi_t_target values(2, 'B');
insert into edi_t_target values(3, 'C');
insert into edi_t_target values(4, 'D');

insert into edi_t_source values(1, 'AA', null);
insert into edi_t_source values(2, 'BB', null);
insert into edi_t_source values(3, 'C', 'Y');
insert into edi_t_source values (5, 'E', null);
insert into edi_t_source values (6, 'FFFFFFFFFF', null);

begin
dbms_errlog.create_error_log(dml_table_name => 'edi_t_target');
end;
/

merge into edi_t_target t
using edi_t_source s
on (t.id = s.id)
when matched then
update set name = s.name
delete where s.resigned = 'Y'
when not matched then
insert values (s.id, s.name)
log errors into err$_edi_t_target reject limit unlimited;

select * from edi_t_target;
select * from err$_edi_t_target;
ID NAME
1 AA
2 BB
4 D
5 E
ORA_ERR_NUMBER$ ORA_ERR_MESG$ ORA_ERR_ROWID$ ORA_ERR_OPTYP$ ORA_ERR_TAG$ ID NAME
--------------- ------------------------------------------------------------ -------------- -------------- ------------ ---- ----------
12899 ORA-12899: value too large for column “APPS”.“EDI_T_TARGET”.“NAME” (actual: 10, maximum: 5) I 6 FFFFFFFFFF

# DCL

DCL(Data Control Language) 数据控制语言,用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,对数据库实行监视等,包括 COMMI, SAVEPOINT, ROLLBACK, SET TRANSACTION

# Savepoint Rollback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE SEQUENCE "SUCRE"."DEPTNO_SEQ"
MINVALUE 1
MAXVALUE 1.00000000000000E+27
INCREMENT BY 10
START WITH 201
CACHE 20
NOORDER NOCYCLE;

DECLARE
v_number number;
BEGIN
v_number := 1;
insert into DEPT values(deptno_seq.nextval,v_number,'');
savepoint A;
insert into DEPT values(deptno_seq.nextval,v_number+1,'');
savepoint B;
insert into DEPT values(deptno_seq.nextval,v_number+2,'');
savepoint C;
--退回到 A
rollback to A;
--提交,只会提交 point A前面的
commit;
END;

# Autonomous Transaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create table edi_t(n number);
insert into edi_t values (1); --Main Transaction

declare
pragma autonomous_transaction; --Autonomous Child Transaction
begin
insert into edi_t values (2);
commit;
end;
/

insert into edi_t values (3); --Main Transaction

rollback; --rollback Main Transaction

select * from edi_t;
--2

Autonomous transaction usage

Log error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
create table edi_log (
error_no number,
error_msg varchar2(100)
);
/

create or replace procedure edi_log_error(p_error_no number, p_error_msg varchar2)
is
pragma autonomous_transaction;
begin
insert into edi_log values (p_error_no, p_error_msg);
commit;
end;
/


declare
l_err_no number;
l_err_msg varchar2(100);
begin
insert into edi_t values(1);
insert into edi_t values('2');
insert into edi_t values('3$');
insert into edi_t values(4.3);
exception
when others then
l_err_no := sqlcode;
l_err_msg := sqlerrm;
edi_log_error(l_err_no, l_err_msg);
rollback;
end;
/

select * from edi_log;
ERROR_NO ERROR_MSG
-1722 ORA-01722: invalid number

Commit inside trigger

1
2
3
4
5
6
7
8
9
create or replace trigger edi_t_trg
before insert on edi_t
for each row
begin
commit;
end;
/

insert into edi_t values (1); --ORA-04092: cannot COMMIT in a trigger
1
2
3
4
5
6
7
8
9
10
11
12
create or replace trigger edi_t_trg
before insert on edi_t
for each row
declare
pragma autonomous_transaction;
begin
commit;
end;
/

insert into edi_t values (1);
select * from edi_t;

Calling function having DML statement in SELECT statement

1
2
3
4
5
6
7
8
create or replace function edi_t_with_dml return number as
begin
insert into edi_t values (3);
return 1;
end;
/

select edi_t_with_dml from dual; --ORA-14551: cannot perform a DML operation inside a query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create or replace function edi_t_with_dml return number as
pragma autonomous_transaction;
begin
insert into edi_t values (3);
commit;
return 1;
end;
/

select edi_t_with_dml from dual;
select * from edi_t;

--drop table edi_t;
--drop function edi_t_with_dml;
EDI_T_WITH_DML
1
ID
----
3

# ERP Request Commit

ERP Request Error 可以设置 retcode = ‘2’ 会自动 rollback, 没有 Error 会自动 commit, 如果在 Error 的情况下需要更新表中的状态,可以手动 rollback 之前的操作,然后更新错误状态,然后手动 commit。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
procedure insert_so(errbuf   out varchar2,
retcode out varchar2,
p_edi_id in number) is
begin
begin
exception
when others then
retcode := 2; --1
l_error_msg := l_error_msg || dbms_utility.format_error_backtrace || dbms_utility.format_error_stack;
debug('error occured in insert_so: ' || l_error_msg);

rollback; --2

update som_edi_temp_h seh
set seh.transfer = 'E',
seh.transfer_date = sysdate,
seh.attribute1 = l_error_msg
where seh.edi_id = p_edi_id; --3

commit; --4
end;
end insert_so;

# Func Procedure

# DBMS_OUTPUT and FND_FILE

code meaning
dbms_output.put(’’) Output content to buffer
dbms_output.put_line(’’) Output content to console and newline
fnd_file.put(fnd_file.output, ‘’) Writes text to a file, without appending any new line characters
fnd_file.new_line(fnd_file.output, 1) Writes line terminators to a file (new line character)
fnd_file.put_line(fnd_file.output, ‘’) Writes text to a file followed by a new line character

注意: dbms_output.put 只会输出内容到缓存; dbms_output.new_line 输出缓存内容并换行.

# Package Create

第一次创建 oracle packge 时可能提示编译错误,但 plsql 并不会指出具体的错误,这时需要右键 Edit Spec & Body 重新打开 package 编译就可以看见具体的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- package head
create or replace package apps.sms_po_requisitions_log_pkg is

-- Author : 11435
-- Created : 2019/2/1 9:45:48
-- Purpose : ASCP REQUISITIONS LOG

procedure put_csv(p_value in varchar2, p_separator in varchar2 default ',');

end sms_po_requisitions_log_pkg;


-- package body
create or replace package body apps.sms_po_requisitions_log_pkg is

procedure put_csv(p_value in varchar2, p_separator in varchar2 default ',') is
begin
/**
Test In PL/SQL
dbms_output.put : output content to buffer
dbms_output.new_line : output buffer to console and newline
*/
dbms_output.put('"' || p_value || '"' || p_separator);
exception
when others then
raise;
end put_csv;
end sms_po_requisitions_log_pkg;

# Package Compile Error

PLS-004010: duplicate fields in RECORD, TABLE or argument list are not permitted
原因:在 procedure 参数和定义部分同时定义了 p_seq_id

1
2
3
4
procedure proc_855(errbuf         out varchar2,
retcode out varchar2,
p_seq_id in number) is
p_seq_id number;

# Procedure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--创建存储过程
create or replace procedure p_babb_test(num out number)
is
begin
--select 语句中必须使用 into
select count(*) into num from scott.emp;
end;
--调用存储过程
declare
a number;
begin
p_babb_test(a);
dbms_output.put_line(a);
end;
--删除存储过程
drop procedure p_babb_test;

# Synonym

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- CREATE [OR REPLACE] [PUBLIC] SYNONYM [schema.] synonym_name
-- FOR [schema.] object_name [@dblink_name];

--before
select * from app.suppliers;

--create synonym
CREATE PUBLIC SYNONYM suppliers
FOR app.suppliers;

--now
select * from suppliers;

--drop
drop public SYNONYM suppliers;

--没有 public 创建的 synonym 仅对当前 owner 有效
CREATE SYNONYM suppliers
FOR app.suppliers;

-- 查询所有 synonym
select * from all_synonyms sn where sn.SYNONYM_NAME like 'SOM_EDI%';

--Synonym 可以用在 dblink 对应的 object。
create synonym edi_mes_head for "dbo"."ssc_t_edi_head"@mes2edi;

select * from edi_mes_head where "delivery_no" in ('102234');

# Default Param

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CREATE OR REPLACE PROCEDURE HelloWorld3 (
p_user_name VARCHAR2,
p_val1 VARCHAR2 DEFAULT ' Good Moning,',
p_val2 VARCHAR2 DEFAULT ' Nice to Meet you'
) AS
BEGIN
dbms_output.put_line('Hello ' || p_user_name || p_val1 || p_val2 || '!');
END HelloWorld3;
/
Procedure created.

--指定参数名称调用
--当最后2个参数是有默认的时候,跳過中間的那個。
BEGIN
HelloWorld3('Edward');
HelloWorld3('Edward', p_val1 => ' Good Night,');
HelloWorld3('Edward', p_val1 => ' Good Night,', p_val2 => 'Bye');
HelloWorld3('Edward', p_val2 => ' HeiHei ');
END;
/
/*
Hello Edward Good Moning, Nice to Meet you!
Hello Edward Good Night, Nice to Meet you!
Hello Edward Good Night,Bye!
Hello Edward Good Moning, HeiHei !
*/

# Sum Count

group/no group sum() count()
no group 捞不到数据 null 0
group 捞不到数据 no data found no data found
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--没有数据返回0
select count(*) from test where 1 <> 1;

--有 group by 会先 group by, 此时什么都不会返回
select count(*) from test where 1 <> 1 group by test.name;


--没有数据返回 null
select sum(test.qty)from test where 1 <> 1;

--没有数据什么都不会返回
select sum(test.qty) from test where 1 <> 1 group by test.name;

--没有数据什么都不会返回
select sum(test.qty) from test where 1 <> 1 group by test.qty;

--返回 null
select null + 2 from dual;


--如果查不到数据min()返回空,此时into不会出错
select min(setd.po_line) into l_po_line from som_edi_temp_d setd;
--如果查不到数据会产生错误
select setd.po_line into l_po_line from som_edi_temp_d setd;

# Count Distinct

查找一个 pallet 下有几个不同的 carton

1
2
3
4
5
select count(distinct sel.carton_id_value)
from som_edi_856_line sel
where sel.edi856_id = p_edi856_id
and sel.pallet_id_value = p_pallet_id
group by sel.pallet_id_value;

# Instr Substr

instr(sourceString,destString,start,appearPosition)

instr 在一个字符串中查找指定的字符,返回被查找到的指定的字符的位置。可以从右向左查找即开始位置为负。查找不到返回 0。

substr(cExpression,nStartPosition [,nCharactersReturned])

substr 是从给定的字符表达式或字段中返回一个子字符串。

example: 返回不包含目录的文件名

1
2
3
4
5
select substr('c:\test\test.xlsx',
instr('c:\test\test.xlsx', '\', -1, 1) + 1,
length('c:\test\test.xlsx') - instr('c:\test\test.xlsx', '\', -1, 1))
from dual;
--test.xlsx

# Option Filter

edi_id 是一个栏位并不是一个变量,如果没有传入 p_edi_id 则 edi_id 栏位不为空的情况下 edi_id = edi_id 恒成立 (null=null 不成立)

1
2
3
4
5
6
7
8
select edi_id
from som_edi_temp_h
where transfer in ('N', 'Q')
and edi_id = nvl(p_edi_id, edi_id) --
and customer_id = nvl(p_customer_id, customer_id)
and trim(cust_po_number) = nvl(trim(p_customer_po), cust_po_number)
and edi_type = nvl(p_edi_type, edi_type)
order by edi_id;

# TO_CHAR

1
2
3
4
5
6
7
8
9
10
11
12
--获取系统当前的分钟数mi,获取月份是mm
select to_char(sysdate,'mi') as nowMinute from dual

--数字转换为字符串,并保留小数位数和前导 0
select to_char(3.145, '9999990.00'), length(to_char(3.145, '9999990.00')) from dual;-- 3.15 11
select trim(to_char(3.145, '9999990.00')), length(trim(to_char(3.145, '9999990.00'))) from dual; --3.15 4


select trim(to_char(0.145, '9999990.00')),
trim(to_char(0.145, '9999999.00'))
from dual; --0.15 .15

number to char and keep two decimals

round and trunc

1
2
3
4
5
6
7
8
9
10
11
select round(1234567891234.6789, 2),
round(12345678912345.6789, 2),
round(123456789123456789.6789, 2)
from dual;
--1234567891234.68 12345678912345.7 1.23456789123457E17

select trunc(1234567891234.6789, 2),
trunc(12345678912345.6789, 2),
trunc(123456789123456789.6789, 2)
from dual;
--1234567891234.67 12345678912345.7 1.23456789123457E17

to_char

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
select '(' || to_char(1234567891234.6789, '999999999999999990.00') || ')',
'(' || to_char(12345678912345.6789, '999999999999999990.00') || ')',
'(' || to_char(123456789123456789.6789, '999999999999999990.00') || ')'
from dual;
--( 1234567891234.68) ( 12345678912345.68) ( 123456789123456789.68)


select '(' || trim(to_char(1234567891234.6789, '999999999999999990.00')) || ')',
'(' || trim(to_char(12345678912345.6789, '999999999999999990.00')) || ')',
'(' || trim(to_char(123456789123456789.6789, '999999999999999990.00')) || ')'
from dual;
--(1234567891234.68) (12345678912345.68) (123456789123456789.68)


select '(' || to_char(1234567891234.6789, 'FM999999999999999990.00') || ')',
'(' || to_char(12345678912345.6789, 'FM999999999999999990.00') || ')',
'(' || to_char(123456789123456789.6789, 'FM999999999999999990.00') || ')'
from dual;
--(1234567891234.68) (12345678912345.68) (123456789123456789.68)

select '(' || to_char(trunc(1234567891234.6789, 2), 'FM999999999999999990.00') || ')',
'(' || to_char(trunc(12345678912345.6789, 2), 'FM999999999999999990.00') || ')',
'(' || to_char(trunc(123456789123456789.6789, 2), 'FM999999999999999990.00') || ')'
from dual;
--(1234567891234.67) (12345678912345.67) (123456789123456789.67)


select to_char(0.6789, 'FM999999999999999990.00'),
to_char(0.6789, 'FM999999999999999999.00')
from dual;
--0.68 .68

# CHR ASCII

在 oracle 中 chr () 函数和 ascii () 是一对反函数。chr () 函数将 ASCII 码转换为字符:字符 –> ASCII 码;ascii () 函数将字符转换为 ASCII 码:ASCII 码 –> 字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
select chr(65) from dual;
CHR(65)
-------
A

select ascii('A') from dual;
ASCII('A')
----------
65

--chr(0)表示的是字符串的结束符
create table t_text (a varchar2(100));
insert into t_text values('1234');
select length(a), '-'||a||'-' from t_text;
LENGTH(A) '-'||A||'-'
---------- --------------------------
4 -1234-

--这时候没有问题,现在加一个chr(0)
update t_text set a = a||chr(0);
select length(a),'-'||a||'-' from t_text;

LENGTH(A) '-'||A||'-'
---------- -------------------------
5 -1234

--chr(0)后面的东西没法显示出来
update t_text set a = a||'5678';
select length(a),'-'||a||'-' from t_text;
LENGTH(A) '-'||A||'-'
---------- --------------------------
9 -1234

--可以使用substr
select substr(a,6,4) from t_text;
SUBSTR(A,6,4)
-------------
5678

# Trunc Extract Date

trunc 用于截取日期,默认截取日期到日,截取之后的类型仍然为 date 类型

extract 可以从日期和时间戳提取年月日等,返回 number 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT TRUNC(SYSDATE) FROM DUAL; --2012-12-19
SELECT TRUNC(SYSDATE,'MM') FROM DUAL; --2012-12-1
SELECT TRUNC(SYSDATE,'YYYY') FROM DUAL; --2012-1-1
SELECT TRUNC(SYSDATE,'DD') FROM DUAL; --2012-12-19
SELECT TRUNC(SYSDATE,'HH') 或 HH24 FROM DUAL; --2012-12-19 14:00:00
SELECT TRUNC(SYSDATE,'MI') FROM DUAL; --2012-12-19 14:30:00
SELECT TRUNC(SYSDATE,'SS') FROM DUAL; -- 会出现 bad precison specifier,直接用SYSDATE不用TRUNC

select extract(year from systimestamp) year
,extract(month from systimestamp) month
,extract(day from systimestamp) day
from dual
union
select extract(year from sysdate) year
,extract(month from sysdate) month
,extract(day from sysdate) day
from dual

# Decode

decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值)

1
2
3
4
5
6
7
8
select decode(sign(变量1 - 变量2), -1, 变量1, 变量2) from dual; --取较小值

--注意: select 1 from dual where null = null; 中 null = null 不成立,但是在 decode 中是满足的,应该是使用 is null。
select decode(ship_loc.city, NULL, NULL, ship_loc.city || ', '); --变量处理

select *
from table_subject
order by decode(subject_name, '语文', 1, '数学', 2, '外语', 3); --根据文字排序

decode 注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--decode 隐式转换以第一个返回值(如果是 null 当作 varchar)为基准

--number 不能转换为 sysdate
select decode('a', null, sysdate, 1) from dual; --ORA-00932: inconsistent datatypes: expected DATE got NUMBER

--varchar 可以转换为日期,但必须是指定格式
select decode('a', null, sysdate, 'a') from dual; --ORA-01858: a non-numeric character was found where a numeric was expected

--尝试将字符串 '1' 转换为日期
select decode('a', null, sysdate, '1') from dual; --ORA-01840: input value not long enough for date format

--'15-JAN-20' 是 Server 指定 Format 格式可以转换为日期
select decode('a', null, sysdate, '15-JAN-20') from dual; --2020/1/15

--'a' 是 varchar,sysdate 总是可以转换为 varchar, 返回 Server 指定 Format 格式
select decode(sysdate, null, 'a', sysdate) from dual; --14-JAN-20

-- null 当作 varchar,sysdate 总是可以转换为 varchar, 返回 Server 指定 Format 格式
select decode(sysdate, null, null, sysdate) from dual; --14-JAN-20

--null 当作 varchar, 转换第二个返回值为 varchar
select decode(1, 2, null, 2) from dual --返回字符 2

decode and union 注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--ORA-01790: expression must have same datatype as corresponding expression
select decode(1, 2, null, '2') from dual -- 字符 2
union
select 2 from dual; --数字 2

--use to_number or case statment solve ORA-01790 error
select decode(1, 2, to_number(null), 2) from dual
union
select 2 from dual;
--2

select case when 1 = 2 then null else 2 end from dual
union
select 2 from dual;
--2

decode(null, null, null, null) union

use sql only

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select null from dual
union
select 2 from dual;
--2
--

select decode(null, null, null, null) from dual --no error
union
select 2 from dual;
--2
--

select to_char(null) from dual --ORA-01790: expression must have same datatype as corresponding expression
union
select 2 from dual;

use plsql

1
2
3
4
5
6
7
8
9
10
11
12
13
declare
l_number number;
begin
begin
select *
into l_number
from (select decode(null, null, null, null) from dual union select 2 from dual); --ORA-01790: expression must have same datatype as corresponding expression
exception
when others then
dbms_output.put_line(sqlerrm);
end;
dbms_output.put_line(l_number);
end;

# Get data type using DUMP()

Type=1 means VARCHAR2/NVARCHAR2
Type=2 means NUMBER/FLOAT
Type=12 means DATE, etc.

1
2
3
select decode(1, 2, null, 2) from dual --返回字符 2

select dump(decode(1, 2, null, 2)) from dual; --Typ=1 Len=1: 50

# Listagg

同组字符串拼接函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
with temp as(
select 'China' nation ,'Guangzhou' city from dual union all
select 'China' nation ,'Shanghai' city from dual union all
select 'China' nation ,'Beijing' city from dual union all
select 'USA' nation ,'New York' city from dual union all
select 'USA' nation ,'Bostom' city from dual union all
select 'Japan' nation ,'Tokyo' city from dual
)
select nation, listagg(city,',') within group(order by city desc), count(1)
from temp
group by nation;

nation citylistagg count(1)
------ -------------------------- --------
China Shanghai,Guangzhou,Beijing 3
Japan Tokyo 1
USA New York,Bostom 2


-- null, '' 不会包含在 listagg 连接的字符串中, 需要进行 nvl 处理
with temp as(
select 'China' nation ,'Guangzhou' city from dual union all
select 'China' nation , null city from dual union all
select 'China' nation ,'' city from dual union all
select 'USA' nation ,'New York' city from dual union all
select 'USA' nation ,'Bostom' city from dual union all
select 'Japan' nation ,'Tokyo' city from dual
)
select nation, listagg(city,',') within group(order by city desc), count(1)
from temp
group by nation;

nation citylistagg count(1)
------ -------------------------- --------
China Guangzhou 3
Japan Tokyo 1
USA New York,Bostom 2

--partitiobn by 不会影响记录条数
with temp as(
select 'China' nation ,'Guangzhou' city from dual union all
select 'China' nation ,'Shanghai' city from dual union all
select 'China' nation ,'Beijing' city from dual union all
select 'USA' nation ,'New York' city from dual union all
select 'USA' nation ,'Bostom' city from dual union all
select 'Japan' nation ,'Tokyo' city from dual
)
select nation,
listagg(city, ',') within group(order by city desc) over (partition by nation) as citylistagg,
count(1) over(partition by nation) as g_total,
--count(1) over(partition by 1) as total
count(1) over() as total
from temp;

NATION CITYLISTAGG G_TOTAL TOTAL
------ -------------------------- -------- ------
China Shanghai,Guangzhou,Beijing 3 6
China Shanghai,Guangzhou,Beijing 3 6
China Shanghai,Guangzhou,Beijing 3 6
Japan Tokyo 1 6
USA New York,Bostom 2 6
USA New York,Bostom 2 6

# Length

'' 和 null 会返回空,不会返回 0

1
2
select length('') from dual;
select length(null) from dual;

# Decimal

1
2
3
4
select 1 from dual where 1 = 1.0;
-- 1
select 1.0 from dual;
-- 1

# Least and Greatest

LEAST( expr1 [, expr2, ... expr_n] )

  • Returns
    The LEAST function returns a value that is the same datatype as expr1.
    Having a NULL value in one of the expressions will return NULL as the least value.

  • Note

    If the datatypes of the expressions are different, all expressions will be converted to whatever datatype expr1 is.

    If the comparison is based on a character comparison, one character is considered smaller than another if it has a lower character set value.

1
2
3
4
5
select * from dual where greatest('a', 2, 3, null, sysdate) is null; -- X

select * from dual where least('a', 2, 3, null, sysdate) is null; --X

select greatest('a', 'A', 2, 3), least('a', 'A', 2, 3) from dual; --a 2

# Str Clean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
--Remove all CR/LF, trim the left and right spaces, tabs
function str_clean(p_str in varchar2) return varchar2
is
l_return varchar2(1000);
l_tab_space varchar2(10) := chr(9) || ' ';
begin
/**
* Standard ASCII Characters
* Decimal Description
* 9 Horizontal tab (HT)
* 10 Line feed (LF)
* 13 Carriage return (CR)
*/
begin
--translate(p_str, 'a' || chr(10) || chr(13), 'a'): a translate to a, chr(10) & chr(13) replace to empty
--ltrim(str, l_tab_space): trim left tab and space behavior like translate, such as 'chr(9) chr(9) a' will trim to 'a'
--if use trim twice select trim(trim(chr(9) from chr(9) || ' ' || chr(9) || 'a')) from dual; will got 'chr(9) a'
select rtrim(ltrim(translate(p_str, 'a' || chr(10) || chr(13), 'a'), l_tab_space), l_tab_space)
into l_return
from dual;
end;

return l_return;

end str_clean;

# Lpad Str

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
select substr('abc', -2) from dual; --bc

select lpad('abc', 4, 0) from dual; --0abc
select lpad('abcde', 4, 0) from dual; --abcd

select lpad('', 4, 0) from dual; --

select lpad('0', 4, 0) from dual; --0000
select lpad('0', 4) from dual; -- 0


function lpad_str(p_str in varchar2,
p_length in number,
p_pad_str in varchar2 default ' ') return varchar2
is
l_return varchar2(4000);
begin
begin
select case
when length(p_str) >= p_length then
substr(p_str, -1 * p_length)
else
lpad(nvl(p_str, p_pad_str), p_length, p_pad_str)
end
into l_return
from dual;
exception
when others then
l_return := null;
end;

return l_return;
end lpad_str;

select lpad_str('abc', 4, 0) from dual; --0abc
select lpad_str('', 4, 0) from dual; --0000

# NOCOPY

NOCOPY is passed by reference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
CREATE OR REPLACE PROCEDURE proc_p1(param_value IN OUT VARCHAR2) AS
BEGIN
param_value := 'ASSIGNED INSIDE PROCEDURE';
END;
/

DECLARE
lv_var VARCHAR2(100);
BEGIN
lv_var := 'ASSIGNED IN ANONYMOUS BLOCK';
proc_p1(lv_var);
dbms_output.put_line(lv_var);
END;
/
--ASSIGNED INSIDE PROCEDURE



CREATE OR REPLACE PROCEDURE proc_copy(param_value IN OUT VARCHAR2) AS
x NUMBER;
BEGIN
param_value := 'ASSIGNED INSIDE PROCEDURE';
x := 1 / 0;
END;
/

DECLARE
lv_var VARCHAR2(100);
BEGIN
-- To check the proc_copy
lv_var := 'ASSIGNED IN ANONYMOUS BLOCK';
BEGIN
proc_copy(lv_var);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
DBMS_OUTPUT.PUT_LINE(lv_var);
END;
/
--ORA-01476: divisor is equal to zero
--ASSIGNED IN ANONYMOUS BLOCK



CREATE OR REPLACE PROCEDURE proc_nocopy(param_value IN OUT NOCOPY VARCHAR2) AS
x NUMBER;
BEGIN
param_value := 'ASSIGNED INSIDE PROCEDURE';
x := 1 / 0;
END;
/

DECLARE
lv_var VARCHAR2(100);
BEGIN
lv_var := 'ASSIGNED IN ANONYMOUS BLOCK';
BEGIN
proc_nocopy(lv_var);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
DBMS_OUTPUT.PUT_LINE(lv_var);
END;
/
--ORA-01476: divisor is equal to zero
--ASSIGNED INSIDE PROCEDURE


drop procedure proc_copy;
drop procedure proc_nocopy;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
create or replace package edi_demo_package as 
type nest_tab_type is table of varchar2(4000);
lv_nest_tab_var nest_tab_type := nest_tab_type();

procedure p_copy(param_value in out nest_tab_type);

procedure p_no_copy(param_value in out nocopy nest_tab_type);
end;
/

create or replace package body edi_demo_package as
procedure p_copy(param_value in out nest_tab_type) as
begin
null;
end;

procedure p_no_copy(param_value in out nocopy nest_tab_type) as
begin
null;
end;
end;
/


declare
lv_start_time number;
lv_end_time number;
lv_last_index number;
begin
for i in 1..50000 loop
edi_demo_package.lv_nest_tab_var.extend;
lv_last_index := edi_demo_package.lv_nest_tab_var.last();
edi_demo_package.lv_nest_tab_var(lv_last_index) := lpad('A', 4000, 'A');
end loop;


lv_start_time := dbms_utility.get_time;
edi_demo_package.p_copy(edi_demo_package.lv_nest_tab_var);
lv_end_time := dbms_utility.get_time;
dbms_output.put_line('COPY = ' || (lv_end_time - lv_start_time)); --COPY = 31

lv_start_time := dbms_utility.get_time;
edi_demo_package.p_no_copy(edi_demo_package.lv_nest_tab_var);
lv_end_time := dbms_utility.get_time;
dbms_output.put_line('NOCOPY = ' || (lv_end_time - lv_start_time)); --NOCOPY = 0
end;

drop package edi_demo_package;

# PIPLINED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
create or replace type edi_t_varchar_list is table of varchar2(30);

create or replace function edi_t_get_emp_list return edi_t_varchar_list as
l_name_list edi_t_varchar_list := edi_t_varchar_list();
begin
for i in (select * from scott.emp) loop
dbms_lock.sleep(1);
l_name_list.extend();
l_name_list(l_name_list.last()) := i.ename;
end loop;

return l_name_list;
end;

select *
from table(edi_t_get_emp_list())
where rownum <= 3; --Cost 14s



create or replace function edi_t_get_emp_list_p return edi_t_varchar_list pipelined as
l_name_list edi_t_varchar_list := edi_t_varchar_list();
begin
for i in (select * from scott.emp) loop
dbms_lock.sleep(1);
pipe row(i.ename);
end loop;

return;
end;

select *
from table(edi_t_get_emp_list_p())
where rownum <= 3; --Cost 3s

# add_months and interval ‘’ month

1
2
3
4
select add_months(to_date('20240229', 'yyyymmdd'), 12) from dual; --2025-02-28

--2025 不是闰年,2 月份只有 28 天没有 29 天
select to_date('20240229', 'yyyymmdd') + interval '12' month from dual; --ORA-01839: date not valid for month specified

# Date Literal and Timestamp Literal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select date '2007-06-15', --date literal
date '2007-06-15' + 14.5 / 24,
cast (timestamp '2007-06-15 14:30:00' as date),
to_date('2007-06-15 14:30', 'YYYY-MM-DD HH24:MI'),
to_date('2007-06-15 02:30 PM', 'YYYY-MM-DD HH12:MI AM')
from dual;

select * from V$TIMEZONE_NAMES;

select
TIMESTAMP '2007-06-15 14:30:00 +00:00',
TIMESTAMP '2007-06-15 09:30:00 -05:00',
TIMESTAMP '2007-06-15 10:30:00 Canada/Eastern',
TIMESTAMP '2007-06-15 10:30:00 Canada/Eastern EDT'
from dual;

# Timestamp Timezone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
with t as (
select TIMESTAMP '2007-06-15 14:30:00 +00:00' as d from dual union all
select TIMESTAMP '2007-06-15 09:30:00 -05:00' from dual union all
select TIMESTAMP '2007-06-15 10:30:00 Canada/Eastern' from dual union all
select TIMESTAMP '2007-06-15 10:30:00 Canada/Eastern EDT' from dual
)
select d as test_time_value,
d AT LOCAL as session_time_zone_time,
d AT TIME ZONE SESSIONTIMEZONE as session_time_zone_time2,
d AT TIME ZONE DBTIMEZONE as db_time_zone_time,
d AT TIME ZONE '+01:00' as utc_plus_1_time,
d AT TIME ZONE '-03:30' as utc_minus_3_30_time,
d AT TIME ZONE 'Australia/Sydney' as sydney_time,
d AT TIME ZONE 'UTC' AS utc_zone_time,
sys_extract_utc(d) as utc_time
from t;


select to_timestamp_tz('December 12, 2023 4:30 AM 05:00', 'Month dd, yyyy hh:mi AM TZH:TZM'),
to_timestamp_tz('December 12, 2023 4:30 AM 05:00', 'Month dd, yyyy hh:mi AM TZH:TZM') AT LOCAL
from dual;

# Date and Times Exact Formatting with the FX Modifier

1
2
3
4
5
select
to_date( '123', 'yyyy' ) as a, -- three digit year, not four
to_timestamp( '2007 12', 'yyyy mm' ) as b, -- extra spaces
to_timestamp_tz( '2007/12', 'yyyy-mm' ) as c -- "/" instead of "-"
from dual ;
A B C
0123-01-01 01-DEC-07 12.00.00.000000000 AM 01-DEC-07 12.00.00.000000000 AM +08:00
1
2
3
4
5
select to_date( '123', 'FXyyyy' ) as a from dual ; --ORA-01862: the numeric value does not match the length of the format item

select to_timestamp( '2007 12', 'FXyyyy mm' ) as b from dual; --ORA-01858: a non-numeric character was found where a numeric was expected

select to_timestamp_tz( '2007/12', 'FXyyyy-mm' ) as c from dual; --ORA-01861: literal does not match format string

# Dates and Times Fill Mode Formatting with the FM Modifier

The Fill Mode modifier, FM, toggles the suppression of blank padding in character format elements like “MONTH” and leading zeros in numeric format elements like “YYYY”.

1
2
3
4
5
select
sysdate as input,
'"' || to_char( sysdate, 'MONTH' ) || '"' as without_fm ,
'"' || to_char( sysdate, 'FMMONTH' ) || '"' as with_fm
from dual;
INPUT WITHOUT_FM WITH_FM
2024-01-06 02:52:09 PM "JANUARY " “JANUARY”
1
2
3
4
5
select
sysdate as input,
'"' || to_char( sysdate, 'MM' ) || '"' as without_fm ,
'"' || to_char( sysdate, 'FMMM' ) || '"' as with_fm
from dual;
INPUT WITHOUT_FM WITH_FM
2024-01-06 03:02:59 PM “01” “1”

# Date and Times Format Suffixes

1
2
3
4
5
TH   - converts numbers to ordinal numbers

SP - converts numbers to words

SPTH - converts numbers to ordinal words
1
2
3
4
5
6
select
to_char( timestamp '2007-04-03 02:01:01', 'yyyyTH, mmTH, ddTH, hhTH, miTH, ssTH' ) output,
to_char( timestamp '2007-04-03 02:01:01', 'FMyyyyTH, mmTH, ddTH, hhTH, miTH, ssTH' ) as output1,
to_char( timestamp '2000-04-03 02:01:00', 'mmSP, ddSP, hh24SP, miSP, ssSP' ) as output2,
to_char( timestamp '2000-04-03 02:01:00', 'mmSPTH, ddTHSP hh24SPTH, miTHSP, ssSPTH' ) as output3
from dual ;
OUTPUT OUTPUT1 OUTPUT2 OUTPUT3
2007th, 04th, 03rd, 02nd, 01st, 01st 2007th, 4th, 3rd, 2nd, 1st, 1st four, three, two, one, zero fourth, third second, first, zeroeth
1
2
3
4
5
6
--The formatting suffixes appear to have problems dealing with some values like zeros and negative years 
select to_char( timestamp '2007-04-03 02:01:00', 'ssTH' ) as output
from dual; --ORA-01877: string is too long for internal buffer

select to_char( timestamp '-1001-01-01 00:00:00', 'syyyySP' ) as output
from dual; --ORA-01877: string is too long for internal buffer

# Function return more value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create or replace function edi_t_fn(p_deptno in number)
return sys.odcivarchar2list
as
l_name_list sys.odcivarchar2list;
begin
select s.ename
bulk collect into l_name_list
from scott.emp s
where s.deptno = p_deptno;

return l_name_list;
end;
/

select * from table(edi_t_fn(20));

--drop function edi_t_fn;
COLUMN_VALUE
SMITH
JONES
SCOTT
ADAMS
FORD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
create or replace type edi_cube_obj as object (
name varchar2(50),
value number
);

create or replace type edi_cube_tab_obj is table of edi_cube_obj;

create or replace function edi_squre_cube(p_number number) return edi_cube_tab_obj
as
l_var edi_cube_tab_obj := edi_cube_tab_obj();
begin
l_var.extend();
l_var(l_var.last()) := edi_cube_obj('SQUARE', p_number * p_number);

l_var.extend();
l_var(l_var.last()) := edi_cube_obj('CUBE', p_number * p_number * p_number);

return l_var;
end;
/

select * from table(edi_squre_cube(2));

--drop type edi_cube_obj;
--drop type edi_cube_tab_obj;
--drop function edi_squre_cube;
NAME VALUE
SQUARE 4
CUBE 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
create or replace function edi_squre_cube(p_number number) return sys_refcursor
as
l_ret_cursor sys_refcursor;
begin
open l_ret_cursor for
select 'SQURE' as name, p_number * p_number as value from dual
union all
select 'CUBE', p_number * p_number * p_number from dual;

return l_ret_cursor;
end;
/

declare
type ref_cur is ref cursor;
l_cur ref_cur;
l_name varchar2(10);
l_value number;
begin
l_cur := edi_squre_cube(2);

loop
fetch l_cur into l_name, l_value;
exit when l_cur%notfound;
dbms_output.put_line(l_name || '=' || l_value);
end loop;
end;

--SQURE=4
--CUBE=8

# Replace Non-breaking space (\u00A0)

UTF-8 encoding table and Unicode characters

Non-breaking space unicode point is U+00A0 ,UTF-8 represention is 0xc2 0xa0

Unicode code point character UTF-8 (hex.)
U+00A0   0xc2 0xa0

In PL/SQL Developer Hex Tab show charcter’s unicode point not UTF-8 represention. And the order is reversed, the U+00A0 is show A000 .

In VSCode use Hex Editor Extension.

Use CHR(160 USING NCHAR_CS) or NCHAR(160) replace Non-breaking space

1
2
3
4
select carrier_no,
replace(carrier_no, CHR(160 USING NCHAR_CS), ''),
replace(carrier_no, NCHR(160), '')
from som_edi_test;

SQL Language Reference

CHR returns the character having the binary equivalent to n as a VARCHAR2 value in either the database character set or, if you specify USING NCHAR_CS , the national character set.

For single-byte character sets, if n > 256, then Oracle Database returns the binary equivalent of n mod 256 . For multibyte character sets, n must resolve to one entire code point. Invalid code points are not validated, and the result of specifying invalid code points is indeterminate.

This function takes as an argument a NUMBER value, or any value that can be implicitly converted to NUMBER , and returns a character.

Note:Use of the CHR function (either with or without the optional USING NCHAR_CS clause) results in code that is not portable between ASCII- and EBCDIC-based machine architectures.

NCHR returns the character having the binary equivalent to number in the national character set. The value returned is always NVARCHAR2 . This function is equivalent to using the CHR function with the USING NCHAR_CS clause.

This function takes as an argument a NUMBER value, or any value that can be implicitly converted to NUMBER , and returns a character.

# Oracle Regex Bracket expression include ] and -

Multilingual Regular Expression Syntax

Operator Description
[ ] Bracket expression for specifying a matching list that should match any one of the expressions represented in the list. A non-matching list expression begins with a circumflex (^) and specifies a list that matches any character except for the expressions represented in the list.
To specify a right bracket (]) in the bracket expression, place it first in the list (after the initial circumflex (^), if any).
To specify a hyphen in the bracket expression, place it first in the list (after the initial circumflex (^), if any), last in the list, or as an ending range point in a range expression.
1
2
3
4
--Find the location of the first character that is not in ][0-9a-z_ ().,;{}''"/-
select regexp_instr(']-' || nchr(160), '[^][0-9a-z_ ().,;{}''"/-]', 1, 1, 0)
from dual;
--3

# CTE

# Caculate PI

半径为 1 的正方形中包含半径为 1 的 1/4 圆,落在圆中的概率为 1/4 圆的面积除以正方形的面积即 1/4 * PI * 1 * 1 / 1 * 1 = 1/4 * PI

1
2
3
4
5
6
7
8
with t(n, x, y) as (
select 1, dbms_random.value, dbms_random.value from dual
union all
select n+1, dbms_random.value, dbms_random.value from t
where n <= 1000000
)
select 4 * sum(1 - floor(x * x + y * y)) / count(*) as PI from t;
--3.13991686008314

# Snail climbing well

一口井深二十米,蜗牛白天向上爬五米,晚上向下滑三米,问第几天能爬出井?

1
2
3
4
5
6
7
8
with t(n, high) as (
select 1, 5 from dual
union all
select n + 1, high + case when mod(n + 1, 2) = 0 then -2 else 5 end
from t
where high < 20
)
select max(n+1) / 2 from t;

# PLSQL

# Sqlcode Sqlerrm

Oracle 内置函数 SQLCODESQLERRM 是一般用在异常 OTHERS 处理器中,分别用来返回 Oracle 的错误代码和错误消息。

在一个内在的异常中,SQLCODE 返回 Oracle 错误的序号,而 SQLERRM 返回的是相应的错误消息。

  • SQLCODE 返回 +100, SQLERRM 返回 “ORA-01403:NO DATA FOUND”;
  • SQLCODE 返回 +1, SQLERRM 返回的是 “User-Defined Exception”;
  • SQLCODE 返回 0, SQLERRM 返回 “ORA-0000:normal, successful completion”。

# Error Backtrace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
declare
l_char varchar2(20);
begin
select 1 into l_char from dual where 1 <> 1;
exception when others then
-- format_error_backtrace会打印出错行号
dbms_output.put_line('format_error_backtrace: ' || dbms_utility.format_error_backtrace);
dbms_output.put_line('format_error_stack: '|| dbms_utility.format_error_stack);
dbms_output.put_line('format_call_stack: ' || dbms_utility.format_call_stack);
dbms_output.put_line('sqlerrm: ' || sqlerrm);
dbms_output.put_line('sqlcode: ' || sqlcode);
end;

/*
format_error_backtrace: ORA-06512: at line 4
format_error_stack: ORA-01403: no data found
format_call_stack: ----- PL/SQL Call Stack -----
object line object
handle number name
0x458e9f088 8 anonymous block
sqlerrm: ORA-01403: no data found
sqlcode: 100
*/

注意:
dbms_output.put 只会输出内容到缓存
dbms_output.new_line 输出缓存内容并换行

# Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
--固定数组
declare
type type_array is varray(10) of varchar2(20); --一维数组,字符串类型
var_array type_array := type_array('java', 'python', 'php');
begin
--数组的长度用count(索引是从1开始的)
for i in 1 .. var_array.count loop
dbms_output.put_line(var_array(i));
end loop;
end;

--可变数组
declare
type type_array is table of varchar2(20) index by binary_integer;
var_array type_array;
begin
--下标必须是连续的,并且索引从1开始
var_array(1) := 'java';
var_array(2) := 'php';
for i in 1 .. var_array.count loop
dbms_output.put_line(var_array(i));
end loop;
end;

--类似二维数组
--IS TABLE OF:指定是一个集合的表的数组类型,简单的来说就是一个可以存储一列多行的数据类型。
--INDEX BY BINARY_INTEGER:指索引组织类型。
--BULK COLLECT:指是一个成批聚合类型,简单的来说,它可以存储一个多行多列存储类型,采用BULK COLLECT可以将查询结果一次性地加载到集合中。
declare
type type_array is table of som_edi_temp_d%rowtype index by binary_integer;
var_array type_array;
begin
select * bulk collect into var_array from som_edi_temp_d;
for i in 1 .. 10 loop
dbms_output.put_line('订单id: ' || var_array(i).edi_id || ', 订单类型: ' || var_array(i)
.edi_type);
end loop;
end;

--数组的相关属性
COUNT 返回集合中元素的个数
DELETE 删除集合中所有元素
DELETE(x) 删除元素下标为x的元素,对VARRAY非法
DELETE(x,y) 删除元素下标从X到Y的元素,对VARRAY非法
EXIST(x) 如果集合元素x已经初始化,则返回TRUE, 否则返回FALSE
EXTEND 在集合末尾添加一个元素,对Index_by非法
EXTEND(x) 在集合末尾添加x个元素,对Index_by非法
EXTEND(x,n) 在集合末尾添加元素n的x个副本,对Index_by非法
FIRST 返回集合中的第一个元素的下标号,对于VARRAY集合始终返回1
LAST 返回集合中最后一个元素的下标号,对于VARRAY返回值始终等于COUNT.
LIMIT 返回VARRY集合的最大的元素个数,Index_by集合和嵌套表无用
NEXT(x) 返回在第x个元素之后及紧挨着它的元素值,如果x是最后一个元素,返回null.
PRIOR(x) 返回在第x个元素之前紧挨着它的元素的值,如果x是第一个元素,则返回null
TRIM 从集合末端开始删除一个元素,对于index_by不合法
TRIM(x) 从集合末端开始删除x个元素

# Multiset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare 
type num_tab_type is table of number;
l_num_list1 num_tab_type := num_tab_type(1, 2, 3, 4, 5, 4);
l_num_list2 num_tab_type := num_tab_type(4, 5, 6, 7);
l_num_list3 num_tab_type;
begin
--l_num_list3 := l_num_list1 multiset union all l_num_list2;
--l_num_list3 := l_num_list1 multiset union distinct l_num_list2;
--l_num_list3 := l_num_list1 multiset intersect l_num_list2;
--l_num_list3 := l_num_list1 multiset except l_num_list2;
l_num_list3 := l_num_list1 multiset except distinct l_num_list2;
for i in 1 .. l_num_list3.count loop
dbms_output.put_line(l_num_list3(i));
end loop;
end;

# DBMS_LOCK Sleep

DBMS_SESSION.SLEEP Replaces DBMS_LOCK.SLEEP in Oracle 18c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
begin
dbms_output.put_line('Time 1: ' || to_char(systimestamp, 'hh24:mi:ss.ff'));

-- Pause for 1 second.
dbms_lock.sleep(1);

dbms_output.put_line('Time 2: ' || to_char(systimestamp, 'hh24:mi:ss.ff'));

-- Pause for half a second.
dbms_lock.sleep(0.5);

dbms_output.put_line('Time 3: ' || to_char(systimestamp, 'hh24:mi:ss.ff'));
end;
/

--Time 1: 16:19:00.959801000
--Time 2: 16:19:01.960960000
--Time 3: 16:19:02.461997000

# DBMS_RANDOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
l_seed BINARY_INTEGER;
BEGIN
--l_seed := TO_NUMBER(TO_CHAR(SYSDATE,'YYYYDDMMSS'));
l_seed := TO_NUMBER(TO_CHAR(timestamp '2024-01-13 12:00:00','YYYYDDMMSS'));
DBMS_RANDOM.initialize (val => l_seed);
FOR cur_rec IN 1 ..10 LOOP
DBMS_OUTPUT.put_line('----');
DBMS_OUTPUT.put_line('value : ' || TO_CHAR(DBMS_RANDOM.value));
DBMS_OUTPUT.put_line('value(low => 1, high => 10): ' || TO_CHAR(DBMS_RANDOM.value(low => 1, high => 10)));
END LOOP;
DBMS_RANDOM.terminate;
END;
/
1
2
3
4
5
6
7
8
BEGIN
FOR cur_rec IN 1 ..10 LOOP
DBMS_OUTPUT.put_line('----');
DBMS_OUTPUT.put_line('value : ' || TO_CHAR(DBMS_RANDOM.value));
DBMS_OUTPUT.put_line('value(low => 1, high => 10): ' || TO_CHAR(DBMS_RANDOM.value(low => 1, high => 10)));
END LOOP;
END;
/

# DBMS_UTILITY.comma_to_table & table_to_comma

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
DECLARE
l_list1 VARCHAR2(50) := 'A,B,C,D,E,F,G,H,I,J';
l_list2 VARCHAR2(50);
l_tablen BINARY_INTEGER;
l_tab DBMS_UTILITY.uncl_array;
BEGIN
DBMS_OUTPUT.put_line('l_list1 : ' || l_list1);

DBMS_UTILITY.comma_to_table (
list => l_list1,
tablen => l_tablen,
tab => l_tab);

FOR i IN 1 .. l_tablen LOOP
DBMS_OUTPUT.put_line(i || ' : ' || l_tab(i));
END LOOP;

DBMS_UTILITY.table_to_comma (
tab => l_tab,
tablen => l_tablen,
list => l_list2);

DBMS_OUTPUT.put_line('l_list2 : ' || l_list2);
END;
/


/*
l_list1 : A,B,C,D,E,F,G,H,I,J
1 : A
2 : B
3 : C
4 : D
5 : E
6 : F
7 : G
8 : H
9 : I
10 : J
l_list2 : A,B,C,D,E,F,G,H,I,J
*/

# Other

# PLSQL use := as assignment

PLSQL uses := as the assignment operator to avoid confusion with equality. equality use = directly.

# Tnsping

tnsping 可以检查 Oracle 是否连通, tnsping instance

tnsping TEST

# SQL Plus Connect DB

1
2
3
4
5
6
7
8
9
10
11
12
13
sqlplus name/pwd@ip:port/service_name

--------tnsnames.ora--------
DEV =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = tcp)(HOST = xxx)(PORT = yyy))
)
(CONNECT_DATA =
(SERVICE_NAME = DEV1)
)
)
--------tnsnames.ora--------
1
2
3
4
5
6
7
8
9
10
11
12
-- 查询数据库 db_link (user_db_links, all_db_links, dba_db_links)
select * from all_db_links;

-- 可以跨不同 DB 查询远程数据库数据,如下在 Oracle 中查询 SQLServer 中的资料
select * from "dbo"."ssc_t_edi_head"@sqlserver_db_link where "delivery_no" = 'xxx'

-- 获取当前数据库 ip
select utl_inaddr.get_host_address from dual;

-- 在 Oracle Prod 环境中 Test 环境的 DB Link: test_db_link
-- 可以通过如下方式把 Prod 环境中的资料写到 Test 环境中
insert into ysc_edi_inbound_header@test_db_link select * from ysc_edi_inbound_header;

# Build Id Seq

1
2
3
4
5
6
7
8
9
10
11
12
13
select level as id from dual connect by  level < 5;
/*
1 1
2 2
3 3
4 4
5 5
*/
select rownum as id from xmltable('1 to 10');
select level + 5 as id from dual connect by level <= 10 - 5;

--可以使用 connect by 生成测试数据
insert into TMP_TABLE select 'TMP'|| lpad(rownum, 7, 0), rownum, sysdate from dual connect by level <= 1000000;

# & Placeholder

1
2
3
4
5
select * from dual where 1 = &p;

DUMMY
-----
X

# PL/SQL Developer AutoReplace

Tools/Preferences/User Interface/Editor/AutoReplace

snippet.txt

可以在名称前添加特殊符号,防止与表名简写冲突,例如 #,测试发现 !不可以 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
w=where
f=from
o=order by
dt=delete
sf=select * from
df=delete from
sc=select count(*) from
g=group by
tt=truncate table
dr=drop table
ct=create table
sd=select distinct
qs=select som_edi_custom_utils_pkg.str_quotify('') from dual
qstr=qstr.tpl
#qstr=qstr.tpl

qstr.tpl

Define AutoReplace MultiLines

1
2
3
select som_edi_custom_utils_pkg.str_quotify('
[#]
') from dual;

# Calculate Luhn Checksum

Luhn algorithm - Wikipedia

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function calculate_luhn_checksum(p_number in varchar2) return number is
l_digit number;
l_multiplier number;
l_sum_digits number;
l_sum number := 0;
begin
begin
for i in 1 .. length(p_number) loop
l_digit := to_number(substr(p_number, -i, 1));
l_multiplier := case when mod(i, 2) != 0 then 2 else 1 end;
l_sum_digits := mod(l_digit * l_multiplier, 10) + trunc(l_digit * l_multiplier / 10);
l_sum := l_sum + l_sum_digits;
end loop;

l_sum := mod(10 - mod(l_sum, 10), 10);
exception
when others then
raise;
end;

return l_sum;
end calculate_luhn_checksum;

# EDI Parse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
create table edi_t (
id number,
data varchar2(4000)
);

insert into edi_t
select 1, 'ISA*00* *00* *ZZ*123456789ABCDEF*14*XXXXXXXTEST *230420*1008*U*00401*000000009*0*T*>' from dual union all
select 2, 'GS*SH*123456789ABCDEF*XXXXXXXTESTT*20230420*1008*000000009*X*004010' from dual union all
select 3, 'ST*861*0001' from dual union all
select 4, 'BRA*ABC0001*20130719*00*2' from dual union all
select 5, 'DTM*050*20130719' from dual union all
select 6, 'N1*WH*270711' from dual union all
select 7, 'RCD**144*EA' from dual union all
select 8, 'LIN*000001*BP*ITEM1' from dual union all
select 9, 'REF*LS*PALLET101' from dual union all
select 10, 'RCD**144*EA' from dual union all
select 11, 'LIN*000001*BP*ITEM1' from dual union all
select 12, 'REF*LS*PALLET102' from dual union all
select 13, 'RCD**120*EA' from dual union all
select 14, 'LIN*000002*BP*ITEM2' from dual union all
select 15, 'REF*LS*PALLET201' from dual union all
select 16, 'RCD**120*EA' from dual union all
select 17, 'LIN*000002*BP*ITEM2' from dual union all
select 18, 'REF*LS*PALLET202' from dual union all
select 19, 'CTT*4' from dual union all
select 20, 'SE*18*000090001' from dual union all
select 21, 'GE*1*000000009' from dual union all
select 22, 'IEA*1*000000009' from dual;

select * from edi_t;

with t as (
select id, data, regexp_substr(data, '(.*?\*){' || (column_value - 1) || '}([^\*]*)', 1, 1, '', 2) as w, column_value - 1 as r
from edi_t,
table(cast(multiset (select level l
from dual
connect by level <= regexp_count(data, '\*') + 1) as sys.odcinumberlist)
)
),
t1 as (
select id, data, case when r = 0 then w else null end as s, w, r from t
),
t2 as (
select id, data, last_value(s ignore nulls) over (order by id, r) s, w, r
from t1
order by id, r
)
select *
from t2
--where s = 'RCD' and r = 2;


with t as (
select id, data, regexp_substr(data, '(.*?\*){' || (l - 1) || '}([^\*]*)', 1, 1, '', 2) as w, l - 1 as r
from edi_t,
(
select level l
from dual
connect by level <= (select max(regexp_count(data, '\*') + 1) from edi_t)
)
where l <= regexp_count(data, '\*') + 1
),
t1 as (
select id, data, case when r = 0 then w else null end as s, w, r from t
),
t2 as (
select id, data, last_value(s ignore nulls) over (order by id, r) s, w, r
from t1
order by id, r
)
select *
from t2;

# Create a unique filename using only DateTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
begin
execute immediate 'select 1 from som_edi_test_header for update';

select max(sh.creation_date)
into l_max_creation_date
from som_edi_test_header sh;

update som_edi_test_header sh
set sh.creation_date = l_max_creation_date + interval '1' second --`sh.creation_date = sh.creation_date + interval '1' second` can not be used here, because the programs run concurrently, `sh.creation_date + '1' second` maybe already exists.
where sh.header_id = p_header_id
and to_char(sh.creation_date, 'yyyymmddhh24miss') in
(select to_char(creation_date, 'yyyymmddhh24miss')
from som_edi_test_header
where header_id != p_header_id); --The current record needs to be excluded here, or condition is always true.

commit;

select 'ASN'
|| '_' || to_char(sh.creation_date, 'yyyymmddhh24miss')
|| '.csv'
into l_asn_filename
from som_edi_test_header sh
where sh.header_id = p_header_id;
end;

# Create a unique filename using Counter and Date

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
begin
begin
execute immediate 'select 1 from som_edi_test_header for update';

update som_edi_test_header seh
set seh.file_counter =
(select nvl(max(sh.file_counter), 0) + 1
from som_edi_test_header sh
where sh.transfer_flag = 'Y'
and sh.creation_date >= trunc(sysdate)
and sh.creation_date < trunc(sysdate) + interval '1' day)
where seh.header_id = p_header_id;

--Not commit here, commit after the transfer_flag is 'Y'. Because the where condition includes transfer_flag = 'Y'
--If the where not condition includes transfer_flag = 'Y', then an rror(transfer_flag != 'Y') will add up the file_counter, the file_counter will not continuous
--commit;

select 'ASN'
|| '_' || to_char(sh.creation_date, 'yyyymmdd')
|| '_' || sh.file_counter
|| '.txt'
into l_asn_filename
from som_edi_test_header sh
where sh.header_id = p_header_id
group by sh.creation_date, sh.file_counter;
end;

--Logical Code

if nvl(retcode, '0') = '0' then
update som_edi_test_header sh
set sh.file_name = l_asn_filename,
sh.transfer_flag = 'Y',
sh.transfer_date = sysdate
where sh.header_id = p_header_id;

commit; --Commit Here
else
rollback;

update som_edi_test_header sh
set sh.file_name = l_asn_filename,
sh.transfer_flag = 'N',
sh.transfer_date = sysdate
where sh.header_id = p_header_id;

commit;
end if;
end;

# Caculate Count and Counter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
select som_edi_pkg.char_format('QCLD', 5, 'R')
|| som_edi_pkg.char_format(count(*) over(), 6, 'R') --Containter Count
|| som_edi_pkg.char_format(row_number() over (order by container_value), 11, 'R') --Containter Counter
|| som_edi_pkg.char_format('CTN', 4, 'R')
|| case when sel.container_value is not null then
chr(10)
|| som_edi_pkg.char_format('QREF', 5, 'R')
|| ssom_edi_pkg.char_format('SI', 4, 'R')
|| ssom_edi_pkg.char_format(sel.container_value, 30, 'R')
else
null
end
data,
sel.container_value
from som_edi_856_header seh,
som_edi_856_line sel
where seh.edi856_id = sel.edi856_id
and seh.edi856_id = 60214
and seh.customer_po = '2020417008'
group by sel.container_value
order by sel.container_value;
DATA CONTAINER_VALUE
QCLD 2 1 CTN QREF SI AC001 AC001
QCLD 2 2 CTN QREF SI AC002 AC002
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
select  som_edi_pkg.char_format('ZCLD', 5, 'R')                                                 
|| som_edi_pkg.char_format(count(*) over(), 6, 'R') --Carton Count
|| som_edi_pkg.char_format(count(*), 11, 'R') --Item Count in Carton
|| som_edi_pkg.char_format('BOX', 4, 'R')
|| case when sel.carton_id_value is not null then
chr(10)
|| som_edi_pkg.char_format('ZREF', 5, 'R')
|| som_edi_pkg.char_format('97', 4, 'R')
|| som_edi_pkg.char_format(sel.carton_id_value, 30, 'R')
else
null
end
data,
sel.carton_id_value
from som_edi_856_header seh,
som_edi_856_line sel
where seh.edi856_id = sel.edi856_id
and seh.edi856_id = 60214
and seh.customer_po = '2020417008'
and nvl(sel.container_value, 'N/A') = nvl('AC001', 'N/A')
group by sel.carton_id_value
order by sel.carton_id_value;
DATA CARTON_ID_VALUE
ZCLD 12 4 BOX ZREF 97 TCT001 TCT001
ZCLD 12 4 BOX ZREF 97 TCT002 TCT002
ZCLD 12 4 BOX ZREF 97 TCT003 TCT003
ZCLD 12 4 BOX ZREF 97 TCT004 TCT004
ZCLD 12 4 BOX ZREF 97 TCT005 TCT005
ZCLD 12 4 BOX ZREF 97 TCT006 TCT006
ZCLD 12 4 BOX ZREF 97 TCT007 TCT007
ZCLD 12 4 BOX ZREF 97 TCT008 TCT008
ZCLD 12 4 BOX ZREF 97 TCT009 TCT009
ZCLD 12 4 BOX ZREF 97 TCT010 TCT010
ZCLD 12 4 BOX ZREF 97 TCT011 TCT011
ZCLD 12 4 BOX ZREF 97 TCT012 TCT012

# ERP and Form

# ERP Request recursive call

有的时候程式 Parent Request 调用 Sub Request 时不小心将 Parrent Request 需要调用的 Sub Request 的名字写成了自己,会造成循环调用 Request 无法停止,这时可以将 Request 调用的 Package 故意修改成编译产生错误的形式,然后编译,此时 Request 会错误然后停止。

# ERP Request Concurrent

在一个 Parent Request 中先后调用两个 Sub Request 时,不能依赖它们的先后执行顺序,并且这两个 Sub Request 不应该共用同一张表的同一个 Flag。 以 A 和 B 两个 Request 对同一个表 T 生成不同的 A_ASN, B_ASN 为例,具体为 A 抓取表 T 的 transfer_flag 为 ‘N’ 的资料,生成 A_ASN, 然后将 transfer_flag 更新为 ‘Y’,
B 抓取表 T 的 transfer_flag 为 ‘N’ 的资料,生成 B_ASN, 然后将 transfer_flag 更新为 ‘Y’。可能的结果有:

A 执行生成 A_ASN, B 执行生成 B_ASN,A 更新 transfer_flag, B 更新 transfer_flag

A 执行生成 A_ASN, A 更新 transfer_flag, B 此时抓不到资料

B 执行生成 B_ASN, B 更新 transfer_flag,A 此时抓不到资料

这种代码可能测试时总是第一种期望的情况,但是这里是存在问题的,ERP 网络环境,表结构,程式结构的更改都可能造成下一次的运行结果与上一次不一致。

# ERP Form Error

在做 Transact Move Orders 时弹出错误: 累计拣货数量超过报关数量

  1. 搜索 累计拣货数量超过报关数量 ,检查 form 的 trigger 都没有发现相关代码和描述
  2. 在 plsql 中使用 select * from all_source s where s.TEXT like '%累计拣货数量超过报关数量%'; 结果在一个 table trigger 中找到相关文字,查看 trigger 代码可以看到实际原因是报关资料抄写没有抄写成功。

# ERP Form setting item property to Unspecified

How to set item propert to Unspecified value. For example, i have radio button with Background color sets to ‘gray’ and i want set it to Unspecified to get transparency effect and i can’t do this. If i set property value to blank forms builder shout “color by this name does not exist”.

Answser: Go to the item in question and press the ‘Inherit’ button in the ‘Property Palette’.

# Get Form Rquest Name and Excustion File Name

1
2
3
4
5
6
7
8
9
select fv.USER_EXECUTABLE_NAME, fv.EXECUTION_FILE_NAME
from FND_EXECUTABLES_FORM_V fv
where upper(fv.USER_EXECUTABLE_NAME) like 'SOMP%EDI%'
or upper(fv.USER_EXECUTABLE_NAME) like 'SOMP%HUB%'
order by fv.USER_EXECUTABLE_NAME

--USER_EXECUTABLE_NAME EXECUTION_FILE_NAME
--SOMP3101: EDI Outbound 855/865 SOM_EDI855865_OUTBOUND_PKG.MAIN
--SOMP4031: HUB ASN som_hub_pkg.asn

# Get Organization ID and CODE from MTL_PARAMETERS

1
2
3
4
5
6
7
8
9
10
select mp.organization_id,
mp.organization_code
from mtl_parameters mp;
-- organization_id organization_code
-- 85 SN

select mp.organization_code
from oe_order_lines_all ol,
mtl_parameters mp
where ol.ship_from_org_id = mp.organization_id;

# SET_POLICY_CONTEXT

oe_transaction_types_v 直接查询会查不到数据,需要设置 CONTEXT.

1
2
3
4
5
6
declare
begin
MO_GLOBAL.SET_POLICY_CONTEXT('S', 81);
end;

select * from oe_transaction_types_v;

# Get Profile

1
2
select fnd_profile.value('ORG_ID')
from dual;

# Get FlexField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
--1. fnd_flex_value_sets 这张表记录了所有值集的名称和值集id
select *
from fnd_flex_value_sets
where upper(flex_value_set_name) = 'NOTES_STATUS';

--2. fnd_flex_values这张表记录了每一个值集对应的可选值
select flex_value
from fnd_flex_values
where flex_value_set_id in
(select flex_value_set_id
from fnd_flex_value_sets
where upper(flex_value_set_name) = 'NOTES_STATUS');
--flex_value
--10
--20

--3. fnd_flex_values_tl对应每一个值集对应的可选值的说明
select description
from fnd_flex_values_tl
where flex_value_id in
(select flex_value_id
from fnd_flex_values
where flex_value_set_id in
(select flex_value_set_id
from fnd_flex_value_sets
where upper(flex_value_set_name) = 'NOTES_STATUS'));

# Request Check

1
2
3
4
5
6
7
8
9
10
select r.argument1, r.argument2, r.request_date
from sfn_fnd_concurrent_requests r,
fnd_concurrent_programs_tl t
where 1 = 1
and t.concurrent_program_id = r.concurrent_program_id
--and r.request_id = 33534323
--and r.concurrent_program_id = 62486
and r.last_update_date > to_date('2020-05-01 18:14:15', 'yyyy-mm-dd hh24:mi:ss')
and t.user_concurrent_program_name = 'SOMP4024: 3C3 Invoice OUTPUT'
and r.status_code = 'E';

# In Trigger call ERP Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
begin
if som_edi_po_insert_pkg.check_is_ok(:new.vendor_id) = 'Y' then
l_result := fnd_request.set_mode(true);
l_req_id := fnd_request.submit_request(application => 'YSC',
program => 'SOMP4081',
description => null,
start_time => null,
sub_request => false,
argument1 => :new.po_header_id);
end if;
exception
when others then
null;
end;

# In PL/SQL call ERP Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
--Initialize apps environment in pl/sql developer, not needed if called by ERP Request.
--1175:MIS, 50705:SC_OM_SUPERUSER, 660:ONT
--select fu.user_id from fnd_user fu where fu.user_name = 'MIS'--1175
--select fr.responsibility_id, fr.application_id from fnd_responsibility fr where fr.responsibility_key = 'SC_OM_SUPERUSER'--50705,660
--fnd_global.APPS_INITIALIZE(user_id => 1175, resp_id => 50705, resp_appl_id => 660);
begin
begin
select fu.user_id
into l_user_id
from fnd_user fu
where fu.user_name = 'MIS';

select fr.responsibility_id, fr.application_id
into l_responsibility_id, l_application_id
from fnd_responsibility fr
where fr.responsibility_key = 'SC_OM_SUPERUSER';

fnd_global.APPS_INITIALIZE(user_id => l_user_id, resp_id => l_responsibility_id, resp_appl_id => l_application_id);
exception
when others then
l_err_msg := 'APPS_INITIALIZE Error: ' || sqlerrm;
debug_error;
end;

l_req_id := fnd_request.submit_request(application => 'YSC',
program => 'SOMP4057',
description => null,
start_time => null,
sub_request => false,
argument1 => l_file_name);

if l_req_id = 0 then
l_err_msg := 'Call SOMP4057 Error';
end if;
end;

如果调用成功,可以通过 ERP / View / Find Requests 查看到对应的 Request。

# Customer Address Relation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
create or replace view som_edi_customer_sites_v as
select party.party_name,
party.duns_number_c duns_number,
cust_acct.cust_account_id, --mtl_customer_items.customer_id
cust_acct.account_number,
cust_acct.attribute19 as edi_customer_no,
site_use.org_id, --Operating Unit
site_use.status,
site_use.site_use_id, --Order ship_to_org_id/invoice_to_org_id
site_use.site_use_code, --SHIP_TO/BILL_TO
site_use.location,
site_use.attribute2 as edi_ship_bill_to_code,
site_use.attribute3 as edi_ship_bill_to_customer,
site_use.attribute4 as customer_site_name, --Title for Customer Site
site_use.attribute5 as edi_region_code,
party_site.party_site_number,
loc.address1,
loc.address2,
loc.address3,
loc.country, --US
loc.city,
loc.state,
loc.province,
loc.postal_code,
territory.territory_short_name, --United States
territory.iso_territory_code, --USA
territory.iso_numeric_code --country_code
from hz_parties party,
hz_cust_accounts cust_acct,
hz_cust_acct_sites_all acct_site,
hz_cust_site_uses_all site_use,
hz_party_sites party_site,
hz_locations loc,
fnd_territories_vl territory
where party.party_id = cust_acct.party_id
and cust_acct.cust_account_id = acct_site.cust_account_id
and acct_site.cust_acct_site_id = site_use.cust_acct_site_id
and acct_site.party_site_id = party_site.party_site_id
and party_site.location_id = loc.location_id
and loc.country = territory.territory_code(+);

# Oracle userenv(‘LANG’) and NLS_LANG

为什么 Oracle 安装完成之后要在注册表中设置 NLS_LANG ?

NLS_LANG 的设定会影响到 userenv (‘LANG’),如果设定不正确,可能导致使用 userenv (‘LANG’) 的查询不到资料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function get_order_salesperson(p_delivery_id in varchar2) return varchar2 
is
  l_return   varchar2(100);
begin
  begin
    select rss.resource_name into l_return
      from wsh_new_deliveries           wnd,
            wsh_delivery_assignments     wda,
            wsh_delivery_details         wdd,
            oe_order_headers_all         ooh,
            hr_all_organization_units_tl hou,
          (SELECT JRE.RESOURCE_NAME, JRS.SALESREP_ID, JRS.ORG_ID, JRS.SALESREP_NUMBER
            FROM JTF_RS_SALESREPS         JRS,
                  JTF_RS_RESOURCE_EXTNS    JRR,
                  JTF_RS_RESOURCE_EXTNS_TL JRE
            WHERE JRS.RESOURCE_ID = JRR.RESOURCE_ID
              AND JRE.LANGUAGE = USERENV('LANG')
              AND JRR.RESOURCE_ID = JRE.RESOURCE_ID
              AND JRR.CATEGORY = JRE.CATEGORY
              AND JRR.CATEGORY IN
                  ('EMPLOYEE', 'OTHER', 'PARTY', 'PARTNER', 'SUPPLIER_CONTACT')
            ) rss
      where 1 = 1
        and wnd.delivery_id = p_delivery_id
        and wdd.delivery_detail_id = wda.delivery_detail_id
        and wnd.delivery_id = wda.delivery_id
        and wdd.source_header_id = ooh.header_id
        and ooh.salesrep_id = rss.salesrep_id
        and rss.org_id = hou.organization_id
        and hou.language = userenv('LANG')
      group by rss.resource_name;
    exception
      when others then
        l_return := null;
    end;
   
    return l_return;
end get_order_salesperson;

SIMPLIFIED CHINESE_CHINA.ZHS16GBK

1
select userenv('LANG') from dual; --ZHS

AMERICAN_AMERICA.ZHS16GBK

1
2
3
select userenv('LANG') from dual;

--US

# ERP Term

Term Meaning
EDI Electronic Data Interchange 电子数据交换
ETD Estimated Time of Departure 预计出发时间,出货日
ETA Estimated Time of Arrival 预计抵达时间,到货日
EXW Ex Works 工厂交货
FOB Free On Board 离岸价格
FCA Free Carrier 货交承运人
RMA Return Material Authorization 退料审查。是处理用户不良产品退货、换货的主要流程。
VMI Vendor Managed Inventory 供应商管理库存
Incoterm International Commercial Term 贸易条款
Freight Terms 运输条款
Payment Term 付款条款,一般是按照合同约定多少天付款,如: 0600
SCAC Standard Carrier Alpha Code The Standard Carrier Alpha Code, a two-to-four letter identification, is used by the transportation industry to identify freight carriers in computer systems and shipping documents such as Bill of Lading, Freight Bill, Packing List, and Purchase Order.
Edited on