PL/SQL简介、语法(结构、命名规则、运算符)、变量(变量定义、变量类型(属性类型、复合类型(记录类型、集合类型(索引表、嵌套表、varry)))、PL/SQL流程控制语句、动态SQL

文章目录

  • PL/SQL简介
  • PL/SQL 的优点
  • PL/SQL语法
    • PL/SQL结构
    • PL/SQL命名规则
    • PL/SQL运算符
    • PL/SQL实例
  • PL/SQL变量
    • PL/SQL变量定义和初始化
    • PL/SQL变量数据类型
      • PL/SQL属性类型
      • PL/SQL复合类型
        • PL/SQL记录类型
        • PL/SQL集合类型(PL/SQL表(索引表)、嵌套表、varry)
  • PL/SQL流程控制语句
    • 选择结构
    • 循环结构
  • 动态SQL


PL/SQL简介

PL/SQL语言(Procedural Language/SQL)是结合了结构化查询与Oracle自身过程控制为一体的强大语言,PL/SQL不但支持更多的数据类型,拥有自身的变量声明、赋值语句,而且还有条件、循环等流程控制语句。过程控制结构与SQL数据处理能力(DML) 无缝的结合形成了强大的编程语言,可以创建过程和函数以及程序包

PL/SQL是一种块结构的语言,它将一组语句放在一个块中,一次性发送给服务器,PL/SQL引擎分析收到 PL/SQL语句块中的内容,把其中的过程控制语句由PL/SQL引擎自身去执行,把PL/SQL块中的SQL语句交给服务器的SQL语句执行器执行。如图所示:

PL/SQL块发送给服务器后,先被编译然后执行,对于有名称的PL/SQL块(如子程序—即存储函数和存储过程) 可以单独编译,永久的存储在数据库中,随时准备执行。

PL/SQL 的优点

支持 SQL:PL/SQL支持所有的SQL数据操纵命令、游标控制命令、事务控制命令、SQL 函数、伪列

支持面向对象编程:在PL/SQL中可以创建类型,可以对类型进行继承,可以在子程序中重载方法

更好的性能:SQL是非过程语言,只能一条一条执行。PL/SQL把一个PL/SQL块统一进行编译后执行,同时还可以把编译好的PL/SQL块存储起来,以备重用

可移植性:使用PL/SQL编写的应用程序,可以移植到任何操作系统平台上的Oracle服务器,同时还可以编写可移植程序库,在不同环境中重用。

安全性:可以通过存储过程对客户机和服务器之间的应用程序逻辑进行分隔,这样可以限制对Oracle数据库的访问,数据库还可以授权和撤销其他用户访问的能力

PL/SQL语法

PL/SQL结构

PL/SQL是一种块结构的语言,一个PL/SQL程序包含了一个或者多个逻辑块(嵌套),每个块都可以划分为三个部分。

PL/SQL简介、语法(结构、命名规则、运算符)、变量(变量定义、变量类型(属性类型、复合类型(记录类型、集合类型(索引表、嵌套表、varry)))、PL/SQL流程控制语句、动态SQL_第1张图片

PL/SQL命名规则

PL/SQL简介、语法(结构、命名规则、运算符)、变量(变量定义、变量类型(属性类型、复合类型(记录类型、集合类型(索引表、嵌套表、varry)))、PL/SQL流程控制语句、动态SQL_第2张图片

PL/SQL运算符

类型 符号
赋值运算符 :=
连接运算符 ||
标签分隔符,只为了标识程序特殊位置。 << >>
范围操作符,比如 1…5 标识从 1 到 5 ..
求幂运算符,比如:3**2=9 **
算术运算符 +,-,*,/
关系运算符 >,<,>=,<=,=,<>,!=
逻辑运算符 AND,OR,NOT
比较运算符 LIKE,BETWEEN,IN,IS NULL

使用GOTO可以跳到标记<< outer >>的位置。标签后必须紧跟可执行语句或者 PL/SQL 块。GOTO 不能跳转到 IF 语句、CASE 语句、LOOP 语句、或者子块中。举例如下:

DECLARE
BEGIN
	FOR i IN 1..100 LOOP
		IF i > 10 THEN
			GOTO outer;
		END IF;
	END LOOP;
	<<outer>>
	dbms_output.put_line('loop 循环了10次提前结束了!' );
END;

比较运算符可用于判断语句

//更多请阅读:https://www.yiibai.com/plsql/plsql_comparison_operators.html
DECLARE 
	PROCEDURE compare (value  VARCHAR2,  pattern VARCHAR2) is 
	BEGIN 
	   IF value LIKE pattern THEN 
	      dbms_output.put_line ('True'); 
	   ELSE 
	      dbms_output.put_line ('False'); 
	   END IF; 
	END;  
BEGIN 
   compare('Zara Ali', 'Z%A_i'); 
   compare('Nuha Ali', 'Z%A_i'); 
END;

PL/SQL实例

1、打开SQL PLUS并登录,输入以下命令:

set serverout on;
set serveroutput on;

DECLARE
    out_text VARCHAR2(20);
BEGIN
    out_text := 'hello world!';
    dbms_output.put_line(out_text);
EXCEPTION
    WHEN others THEN
        dbms_output.put_line('有异常');
END;

2、输入“/”显示执行结果:

PL/SQL简介、语法(结构、命名规则、运算符)、变量(变量定义、变量类型(属性类型、复合类型(记录类型、集合类型(索引表、嵌套表、varry)))、PL/SQL流程控制语句、动态SQL_第3张图片

PL/SQL变量

PL/SQL变量定义和初始化

  • 变量使用前必须先定义,变量必须定义在DECLARE块中,也可以声明变量时对变量初始化
    • 声明变量:变量名 [CONSTANT] datatype [NOT NULL] [:= | DEFAULT initial_value]
    • 其中声明常量时也可以使用 DEFAULT 关键字赋值
  • PL/SQL支持 SQL 中的数据类型,PL/SQL 中也支持 NUMBER、VARCHAR2、DATE 等 Oracle SQL 数据类型
  • 可以通过SELECT……INTO语句为变量赋值:语句从数据库中查询数据对变量进行赋值,查询的结果只能是一行记录,不能是零行或者多行记录。
DECLARE
    v_name VARCHAR2(20) := 'jerry';
    message  VARCHAR2(30) := 'What''s yiibai.com!';     --要在字符串文字中嵌入单引号,请将两个单引号放在一起
BEGIN
    SELECT ename INTO v_name FROM emp WHEARE empno = '1001';     --将SQL查询结果分配给PL/SQL变量
	dbms_output.put_line(v_name);
	dbms_output.put_line(message);     --What's yiibai.com!
EXCEPTION 
    WHEN others THEN
		dbms_output.put_line('异常');
END;

PL/SQL变量数据类型

PL/SQL是强类型的语言,使用变量时必须指定变量的数据类型。PL/SQL常见的数据类型有:

  • VARCHAR2(length)
  • NUMBER(n[,m])
  • DATE/TIMESATMP
  • LONG 变长大字符串类型
  • BOOLEAN 逻辑类型 注意:Oracle SQL 中没有 boolean 类型
  • %ROWTYPE 引用某一个记录类型变量或者数据库表中一行作为数据类型,
  • %TYPE 引用某一个基本类型变量或者数据库表中某一列的类型作为当前变量的类型

PL/SQL属性类型

%ROWTYPE 和 %TYPE称之为属性类型。可以使用%TYPE 和 %ROWTYPE来声明变量,而不必指定特定的数据类型

1、ROWTYPE类型使用

DECLARE
    myemp emp%ROWTYPE;
BEGIN
    SELECT * INTO myemp FROM emp WHERE empno='1001';
    dbms_output.put_line(myemp.ename);
EXCEPTION
    WHEN others THEN
        dbms_output.put_line('异常');
END;

2、TYPE类型使用:

%TYPE引用某个变量或者数据库的列的类型作为某变量的数据类型。

DECLARE
    v_name emp.ename%TYPE;
BEGIN
    SELECT ename INTO v_name FROM emp WHERE empno='1001';
    dbms_output.put_line(v_name);
EXCEPTION
    WHEN others THEN
        dbms_output.put_line('异常');
END;

PL/SQL复合类型

用于存放多个数值的变量
种类:PL/SQL记录(record)、PL/SQL集合(PL/SQL表(别名索引表)、嵌套表、VARRAY)

PL/SQL记录类型

记录是可以容纳不同种类的数据项的数据结构。记录由不同的字段组成,类似于数据库表的一行
PL/SQL可以处理以下类型的记录:基于表的记录、基于游标的记录、用户定义的记录

1、基于表的记录

customer_rec customers%ROWTYPE;    --customers是表

2、基于游标的记录

CURSOR customer_cur 
IS 
   SELECT id, name, address  
   FROM customers; 
customer_rec customer_cur%ROWTYPE; 

3、我们使用 TYPE关键字来自定义记录类型,其语法为:

TYPE 记录类型名 IS RECORD
(
  变量1 数据类型 [NOT NULL] [:= value]
  变量2 数据类型 [NOT NULL] [:= value]
  ……
  变量n 数据类型 [NOT NULL] [:= value]
);
DECLARE
    TYPE emp_type IS RECORD
    (
        empno emp.empno%TYPE,
        ename emp.ename%TYPE
    );
    v_emp emp_type;
BEGIN
    SELECT empno,ename INTO v_emp FROM emp WHERE empno='1001';    --将查找到数据赋值给record变量
    dbms_output.put_line(v_emp.empno || '-' || v_emp.ename);
EXCEPTION
    WHEN others THEN
        dbms_output.put_line('异常');
END;

1)一般在PL/SQL块中从表中取出一行进行处理时用PL/SQL RECORD。
2)SELECT语句查询的列值的数量与顺序,必须与创建记录类型变量中定义的字段数量和顺序相匹配。
3)一个记录类型的变量仅仅能保存从数据库中查询出的一行记录,若查询出了多行记录。就会出现错误。

访问字段

DECLARE 
   TYPE books IS RECORD
   (
	   title VARCHAR(50), 
	   author VARCHAR(50)
   ); 
   book1 books; 
   book2 books; 
BEGIN 
   -- Book 1 specification 
   book1.title  := 'C Programming'; 
   book1.author := 'TanHao';  
   -- Book 2 specification 
   book2.title := 'Telecom Billing'; 
   book2.author := 'LiDawei'; 
   
  -- Print book 1 record 
   dbms_output.put_line('Book 1 title : '|| book1.title); 
   dbms_output.put_line('Book 1 author : '|| book1.author); 
   -- Print book 2 record 
   dbms_output.put_line('Book 2 title : '|| book2.title); 
   dbms_output.put_line('Book 2 author : '|| book2.author); 
END; 
/* 结果
Book 1 title : C Programming
Book 1 author : TanHao
Book 2 title : Telecom Billing
Book 2 author : LiDawei
*/

将记录作为子程序参数
可以像传递任何其他变量一样将记录作为子程序参数传递。还可以像访问上面的示例一样访问记录字段

DECLARE 
	TYPE books IS RECORD
	(
		title VARCHAR(50), 
		author VARCHAR(50)
	); 
	book1 books; 
	book2 books;  
	-- 在DECLARE中声明临时子程序
	PROCEDURE printbook (book books) IS 
	BEGIN 
		dbms_output.put_line ('Book  title :  ' || book.title); 
		dbms_output.put_line('Book  author : ' || book.author);
	END; 
BEGIN 
   -- Book 1 specification 
   book1.title  := 'C Programming'; 
   book1.author := 'Haoqiang Tang'; 

   -- Book 2 specification 
   book2.title := 'Telecom Billing'; 
   book2.author := 'Maxsu'; 
   
   -- Use procedure to print book info 
   printbook(book1); 
   printbook(book2); 
END; 
/* 结果
Book 1 title : C Programming
Book 1 author : TanHao
Book 2 title : Telecom Billing
Book 2 author : LiDawei
*/  

PL/SQL集合类型(PL/SQL表(索引表)、嵌套表、varry)

类型 可存储于数据库 元素个数 是否需初始化 初始下标值
PL/SQL表 无限 不需
嵌套表 无限 1
可变数组 有限(自定义) 1

PL/SQL表(索引表)、嵌套表、varry 三个都可以作为集合类型来存放数据,只不过它们本身又具有独特的一些特性

个人认为,如果仅仅是在存储过程中当做集合变量使用,PL/SQL表(索引表)是最好的选择

另外Oracle专门为集合类型(PL/SQL表(别名索引表)、嵌套表、VARRAY)提供了集合变量的方法及嵌套调用等一系列特征,将在本博客单独一遍讲述。

PL/SQL索引表
不能存储于数据库中,元素个数没有限制,下标可以为负值

--element_type:指定索引表**元素的数据类型**
--NOT NULL表示不允许引用NULL元素
--key_type指定索引表元素**下标的数据类型**(binary_integer、pls_integer、varchar2(9i后允许))。
TYPE type_name is table of element_type [NOT NULL] INDEX BY key_type;

1)使用binary_integer或pls_integer定义PL/SQL索引表下标。

--示例说明:emp.ename%type表示存放元素的数据类型,binary_integer表示元素下标的数据类型
declare
	type v_table is table of emp.ename%type index by binary_integer;    
	st v_table;
begin
	select ename into st(-1) from emp where empno=28; 	  --元素值为字段ename的值,下标为-1
end;

declare
	type emp_table_type is table of emp%rowtype index by binary_integer;
	et emp_table_type;
begin
	select * into et(1) from emp where empno=&no;
	dbms_output.put_line('姓名:'||et(1).ename);
	dbms_output.put_line('工资:'||et(1).sal);
	dbms_output.put_line('岗位:'||et(1).job);
	dbms_output.put_line('时间:'||et(1).hiredate);
end;

2)使用varchar2定义PL/SQL索引表的下标

declare
	type areaTableType is table of number index by varchar(10);
	atab areaTableType;
begin
	atab('japan'):=1;
	atab('China'):=2;
	atab('American'):=3;
	atab('England'):=4;
	atab('Portugal'):=5;
	dbms_output.put_line('第一个元素'||atab.first);
	dbms_output.put_line('最后一个元素'||atab.last);
end;

嵌套表
定义:Nested Table,PL/SQL索引表没有index by子句就是嵌套表。嵌套表简单来说就是把一个表中的字段定义为一个表,这个字段表的数据存储在外部的一个表中

说明:数组类型,元素个数无限制,下标从1开始,元素存储可以是稀疏的,使用前必须使用其构造方法进行初始化。(嵌套表可以作为列的数据类型,但PL/SQL索引表不行)

语法:type type_name is table of element_type;    --element_type指定元素的数据类型。

例一:可以单独的操作嵌套表

declare
	type ename_table_type is table of emp.ename%type; --定义嵌套表类型
	et ename_table_type; --声明
begin
	et:=ename_table_type('MARY','CLARK','DOG'); --初始化3个元素(对嵌套表进行初始化可以使用它的构造函数) 
	select ename into et(2) from emp where empno=&no;
	dbms_output.put_line('雇员名:'||et(2)); --输出是CLARK
end;

例二:对包含嵌套表的表的操作

create or replace type phone_type is table of varchar2(20); --定义嵌套表类型
create table person
(
	id number(4),
	name varchar2(10),
	sal number(6,2),
	phone phone_type
)nested table phone store as phone_table; --声明嵌套表类型,可以理解为表person中phone列保存着一个一维数组。

--inset
begin
	insert into person values(1,'scott',800,phone_type('0471-3456788','13804171235'));
end;

--select
declare
	pt phone_type;
begin
	select phone into pt from person where name = 'scott'for i in 1..pt.count loop
		dbms_output.put_line('第'||i||'个号码:'||pt(1));
end;

--update
declare
	pt phone_type := phone_type('025-58843287','15805175201','0714-6575787');
begin
	update person set phone = pt where id = 1;
end;

例三:嵌套表作为列的数据类型例子,建立对象类型emptype,嵌套表类型emparray,表类型department

create or replace type emptype as object    --创建对象类型
(
	name varchar2(10),
	salary number(6,2),
	hire datedate
);    
create or replace type emparray is table of emptype;    --根据对象类型创建嵌套表
create table department    --创建表类型department,嵌套表作为表列
(
	deptno number(2),
	dname varchar2(10),
	employee emparray
) nested table employee store as employee_table; --可以理解表department中employee列保存着一个二维数组。

--insert
insert into temp_department
values (1,'scott',temp_emparray(temp_emptype('0471',2, sysdate),temp_emptype('0473', 3, sysdate)));

varray
说明:变长数组,可以作为列和对象类型属性的数据类型,元素个数有限制,下标从1开始,使用前需要使用其构造方法初始化

语法:TYPE type_name is varray(size_limit) of element_type [not null];
--size_limit指定元素个数,element_type指定元素的数据类型。

例一:在PL/SQL块中使用varray

declare
	type job_array_type is varray(20) of emp.job%type;
	jarr job_arry_type:=job_array_type('HRQ','CLERK');  --初始化2个元素
begin
	select job into jarr(1) from emp where lower(ename)=lower('&name');  --覆盖了初始化的数值
	dbms_output.put_line('岗位:'||jarr(1));
end;

例二:phone_array作为worker表中某一列的数据类型,varchar(20)为phone_array数组的元素类型

create type phone_array is varray(20) of varchar(20);
create table worker(id number(4), name varchar2(10), sal number(6,2), phonephone_arra);

例三:article_array作为author表中某一列的数据类型,article_type为article_array数组的元素类型

create type article_type as object(title varchar2(30), pubdate date);
create type article_array is varray(20) of article_type;
create table author(id number(6), name varchar2(10), article article_array);

PL/SQL流程控制语句

PL/SQL 程序可通过顺序、条件或循环结构来控制命令执行的流程。

选择结构

1、 IF-THEN-END IF

DECLARE
    newsal emp.sal%TYPE;
BEGIN
    SELECT sal INTO newsal FROM emp WHERE ename = 'James';
    IF newsal >= 1500 THEN
        UPDATE emp SET comm = 1000 WHERE ename = 'James';
    END IF;
    COMMIT;
END;

2、IF-THEN-ELSE-END IF

DECLARE
    newsal emp.sal%TYPE;
BEGIN
    SELECT sal INTO newsal FROM emp WHERE ename = 'James';
    IF newsal >= 1500 THEN
        UPDATE emp SET comm = 1000 WHERE ename = 'James';
    ELSE
        UPDATE emp SET comm = 100 WHERE ename = 'James';
    END IF;
    COMMIT;
END;

3、IF-THEN-ELSIF-THEN-ELSE-END IF

DECLARE
    newsal emp.sal% TYPE;
BEGIN
    SELECT sal INTO newsal FROM emp WHERE ename='James';
    IF newsa l>= 1500 THEN
        UPDATE emp SET comm = 1000 WHERE ename='James';
    ELSE newsal >= 1000 THEN
        UPDATE emp SET comm = 800 WHERE ename='James';
    ELSIF
        UPDATE emp SET comm = 400 WHERE ename='James';
    END IF;
    COMMIT;
END;

4、CASE-WHEN-THEN-ELSE-END CASEZ(有 简单CASE语句可搜索CASE语句 之分)

简单CASE语句

--CASE 中存在 selector,不返回值
DECLARE
    v_grade VARCHAR2(10) := upper('&p_grade');
BEGIN
    CASE v_grade     
        WHEN 'A' THEN
            dbms_output.put_line('A');
        WHEN 'B' THEN 
            dbms_output.put_line('B');
        WHNE 'C' THEN 
            dbms_output.put_line('C');
        ELSE
            dbms_output.put_line('not found char!');
    END CASE;
END;
--CASE 中存在 selector,作为表达式使用
DECLARE
    v_grade CHAR(1):=UPPER('&grade');
    p_grade VARCHAR(20) ;
BEGIN 
    p_grade := 
    CASE v_grade 
    WHEN 'A' THEN 'Excellent' 
    WHEN 'B' THEN 'Very Good' 
    WHEN 'C' THEN 'Good' 
    ELSE 'No such grade' END;
    dbms_output.put_line('Grade:' ||v_grade||',the result is '||p_grade);

可搜索CASE语句

DECLARE 
	grade CHAR(1) := 'B'; 
BEGIN 
	CASE    
		WHEN grade = 'A' THEN dbms_output.put_line('Excellent'); 
		WHEN grade = 'B' THEN dbms_output.put_line('Very good'); 
		WHEN grade = 'C' THEN dbms_output.put_line('Well done'); 
		WHEN grade = 'D' THEN dbms_output.put_line('You passed'); 
		WHEN grade = 'F' THEN dbms_output.put_line('Better try again'); 
		WHEN dbms_output.put_line('No such grade'); 
	END CASE;
END; 

循环结构

1、LOOP-END LOOP(死循环,配合EXIT或者EXIT WHEN使用

配合EXIT

DECLARE
    v_sum NUMBER(10) := 0;
    i NUMBER(10) := 1;
BEGIN
	LOOP
		v_sum := v_sum + i;
		i := i+ 1;
		IF i > 100 THEN
			EXIT;
		END IF;
	END LOOP;
	dbms_output.put_line(v_sum);
END;

配合EXIT WHEN

DECLARE
	cou NUMBER ;
BEGIN
	cou := 1 ;
	LOOP
		dbms_output.put_line('cou = '||cou) ;
		EXIT WHEN cou > 10 ;
		cou := cou + 1 ;
	END LOOP ;
END ;

2、WHILE-LOOP-END LOOP

DECLARE
    v_sum NUMBER(10) := 0;
    i NUMBER (10) := 1;
BEGIN
    WHILE i <= 100 LOOP
        v_sum := v_sum + i ;
        i := i + 1;
    END LOOP;
    dbms_output.put_line(v_sum);
END;

3、FOR-IN-LOOP-END LOOP(正向FOR LOOP语句反转FOR LOOP语句

正向FOR LOOP语句

DECLARE
    v_sum NUMBER (10) := 0;
    i NUMBER (10) := 1;
BEGIN
    FOR i IN 1..100 LOOP
        v_sum : = v_sum + i;
    END LOOP;
    dbms_output.put_line(v_sum);
END;

反转FOR LOOP语句

DECLARE 
   a number(2) ; 
BEGIN 
   FOR a IN REVERSE 10 .. 20 LOOP 
      dbms_output.put_line('value of a: ' || a); 
   END LOOP; 
END; 

动态SQL

在 PL/SQL 程序开发中,可以使用 DML 语句和事务控制语句,但是还有很多语句(比如 DDL 语句)不能直接在 PL/SQL 中执行,这些语句可以使用**动态 SQL **来实现。

PL/SQL 块先编译然后执行,动态 SQL 语句在编译时不能确定,只有在程序执行时把 SQL 语句作为字符串的形式由动态 SQL 命令来执行。

在编译阶段,动态SQL 语句作为字符串存在,程序不会对字符串中的内容进行编译,在运行阶段再对字符串中的 SQL 语句进行编译和执行

动态 SQL语法格式:

EXECUTE IMMEDIATE 动态语句字符串    --动态SQL中的参数格式是:[:参数名],[:参数名]可以是[:数字]也可以是[:字符串]
[INTO 变量列表]    --如果动态语句是SELECT语句,可以把查询的结果保存到INTO后面的变量中。
[USING 参数列表]    --如果动态语句中存在参数,USING为语句中的参数传值。
DECLARE
	sql_stmt VARCHAR2(200);    --动态SQL语句
	emp_id NUMBER(4) := 7566;
	salary NUMBER(7,2);
	dept_id NUMBER(2) := 90;
	dept_name VARCHAR2(14) := 'PERSONNEL';
	location VARCHAR2(13) := 'DALLAS';
	emp_rec emp%ROWTYPE;
BEGIN
	--无子句的execute immediate
	EXECUTE IMMEDIATE 'CREATE TABLE bonus1 (id NUMBER, amt NUMBER)'; 
	----using子句的execute immediate
	sql_stmt := 'INSERT INTO dept VALUES (:1, :2, :3)';
	EXECUTE IMMEDIATE sql_stmt USING dept_id, dept_name, location; 
	----into子句的execute immediate
	sql_stmt := 'SELECT * FROM emp WHERE empno = :id';
	EXECUTE IMMEDIATE sql_stmt INTO emp_rec USING emp_id; 
	----returning into子句的execute immediate
	sql_stmt := 'UPDATE emp SET sal = 2000 WHERE empno = :1 RETURNING sal INTO :2';
	EXECUTE IMMEDIATE sql_stmt USING emp_id RETURNING INTO salary; 
	EXECUTE IMMEDIATE 'DELETE FROM dept WHERE deptno = :num' USING dept_id; 
END;

1)在 Oracle 的 insert,update,delete 语句都可以使用 RETURNING 子句把操作影响的行中的数据返回。使用该字句的效果,与进行insert、update之后执行select into,以及在delete之前进行select into的效果相同。
2)对 SQL 语句中存在 RETURNING 子句时,在动态执行时可以使用RETURNING INTO 来接收。

RETURNING子句详细见:https://blog.csdn.net/helloboat/article/details/43483071

你可能感兴趣的:(笔记,数据库)