AdolphYang

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

存储过程和触发器

 

001 介绍存储过程和如何调用系统的存储过程

存储过程---就像数据库中运行的方法(函数)
由存储过程名/存储过程参数组成/可以由返回结果
if else/while/变量/insert/select等,都可以在存储过程中使用
优点:
执行速度更快 - 在数据库中保存的存储过程语句都是编译过的
允许模块化程序设计 - 类似方法的复用
减少网络流通量 - 只要传输 存储过程的名称

系统存储过程:
由系统定义,存放在master数据库中
名称以sp_开头或xp_开头,自定义的存储过程可以以usp_开头
自定义存储过程:
由用户自己在数据库中创建的存储过程usp

系统存储过程:
sp_databases 列出服务器上的所有数据库
sp_helpdb 显示有关数据库或所有数据库的信息
sp_renamedb 重命名数据库的名称
sp_tables 返回当前环境下某个表的表的信息
sp_collumns 返回某个表的列的信息
sp_help 显示某个表的所有信息
sp_helpconstraint 显示某个表的约束
sp_stored_procedures 列出当前环境下的所有存储过程
sp_password 添加或修改登录账户的密码
sp_helptext 显示默认值、未加密的存储过程、用户定义的存储过程、触发器或视图的实际文本。

exec sp_databases //exec调用这个系统存储过程,可以没有exec
sp_helptext sp_tables //显示某个存储过程sp_tables的代码
alter替换Create 就是修改存储过程
如果C#参数类型如果是string,则SQL Server中为nvarchar(50)

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

002 存储过程小案例

定义存储过程的语法
CREATE PROCEDURE 存储过程名
@参数1 数据类型 = 默认值 OUTPUT,
@参数n 数据类型 = 默认值 OUTPUT
AS
SQL语句

参数说明:参数可选,参数分为输入参数、输出参数,输入参数允许有默认值
EXEC 过程名 [参数]

 

1--创建存储过程:两个数相加
CREATE PROCEDURE usp_AddTwoNumber
@num1 int,
@num2 int
AS
BEGIN
SELECT @num1+@num2
END

--第一种调用:自定义存储过程的调用必须加EXEC
declare @n1 int=100,@n2 int=200
--EXEC usp_AddTwoNumber @num1=@n1,@num2=@n2
--第二章调用:
EXEC usp_AddTwoNumber @n1,@n2
--第三种调用:
EXEC usp_AddTwoNumber 100,200


2--创建存储过程:两数相加并返回
CREATE PROCEDURE usp_AddNumber
@num1 int,
@num2 int,
@sum int OUTPUT
AS
BEGIN
SET @sum=@num1+@num2
END

--调用这个存储过程:如果是存储过程中需要传出来的值,用OUTPUT进行修饰,外面调用的时候也需要加上OUTPUT
declare @n1 int=10,@n2 int=20,@sm int
EXEC usp_AddNumber @n1,@n2,@sm OUTPUT
SELECT @sm

 

Oralce中存储过程的重点:

|| 表示连接两个字符串,或者一个变量与字符串进行连接

" 双引号表示一个引号'

//Oracle中的求2数和:

CREATE OR REPLACE PROCEDURE PRO_AddTwoNumber(
num1 NUMBER,
num2 NUMBER
)
IS
strSql varchar(200);
BEGIN
       strSql:='select '||num1||'+'||num2||' FROM DUAL';
       execute immediate strSql;
EXCEPTION
       WHEN OTHERS THEN
       DBMS_output.PUT_LINE(SQLERRM);
END PRO_AddTwoNumber;
2数和
CREATE OR REPLACE PROCEDURE PRO_AddTwoNumber(
num1 NUMBER,
num2 NUMBER,
sum OUT NUMBER
)
IS
strSql VARCHAR2(200);
BEGIN
       strSql:='select '||num1||'+'||num2||' FROM DUAL';
       EXECUTE IMMEDIATE strSql INTO sum;
       EXCEPTION
       WHEN OTHERS THEN
       Dbms_Output.put_line(SQLERRM);
END PRO_AddTwoNumber;



set serveroutput on 
var sum number 
exec PRO_AddTwoNumber(100,200,:sum)
求2数和,并输出

 


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

003 模糊查询写成存储过程小案例

根据名字和年龄查询班级中的学生信系,并且把有多少个学生显示出来
select * from T_Name where name like '%m%' and age<30;
select COUNT(*) from T_Name where name like '%m%' and age<30;

3--创建存储过程:用户查询学生和数量
CREATE PROCEDURE usp_SelectStudent
@name nvarchar(50),
@age int,
@count int OUTPUT
AS
BEGIN
SET @count=(select COUNT(*) from T_Name where name like '%'+@name+'%' and age<@age);
select Id, name, age, salary from T_Name where name like '%'+@name+'%' and age<@age;
END

--调用这个存储过程:
DECLARE @num int
EXEC usp_SelectStudent 'm',30,@num OUTPUT
SELECT @num

 

create or replace procedure Query_By_Key
(
p_key1 in varchar2,
p_key2 in varchar2,
p_cursor out JN_TABLE.JN_CTable
)*
as
str_Sql  varchar2(500):=' select * from TableA' ||  
                        ' where   pname like :v_key1  and pname like :v_key2 ' ;
begin
         open p_cursor for str_Sql using  '%' || p_key1|| '%' , '%'|| p_key2||'%';
end  Query_By_Key;
Oracle的模糊查询
SQL> CREATE OR REPLACE PROCEDURE PRO_SelectStudent(
  2  name VARCHAR2,
  3  pay VARCHAR2,
  4  count OUT NUMBER
  5  )
  6  IS
  7  strSelectSql VARCHAR2(200);
  8  strCountSql VARCHAR2(200);
  9  BEGIN
 10               strSelectSql:='SELECT NAME,MOBILE,EMAIL FROM TD_STUDENT WHERE NAME LIKE '%'||name||'%' AND PAY=''||pay||''';
 11      EXECUTE IMMEDIATE strSelectSql;
 12               strCountSql:='SELECT COUNT(*) FROM TD_STUDENT WHERE NAME LIKE '%'||name||'%' AND PAY=''||pay||''';
 13               EXECUTE IMMEDIATE strCountSql INTO count;
 14  EXCEPTION
 15               WHEN OTHERS THEN
 16               DBMS_OUTPUT.PUT_LINE(SQLERRM);
 17  END PRO_SelectStudent;
 18  /
Warning: Procedure created with compilation errors
Oracle错误的模糊查询

 


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

04 分页的存储过程

--分页
--每页5条,当前第3页,PageSize=5,CurrentPage=3,[(3-1)*5+1 3*5]
select * from (
select *,ROW_NUMBER() over(Order by Id) rownum from T_User ) t Where rownum Between 1 and 5;
Select ( Ceiling((select COUNT(*) from T_User)*1.0/5))

4--创建存储过程:分页及总页数
CREATE PROCEDURE usp_Pager
@PageSize int=10,
@CurrentPage int=1,
@SumPage int OUTPUT
AS
BEGIN
--总页数
SET @SumPage =Ceiling( (SELECT COUNT(*) FROM T_User)*1.0/@PageSize);
--当前页数据
select * from (
select *,ROW_NUMBER() over(Order by Id) rownum from T_User ) t Where rownum Between (@CurrentPage-1)*@PageSize+1 and @CurrentPage*@PageSize;
END

--调用分页存储过程
DECLARE @SumPage int
EXEC usp_Pager 5,3,@SumPage OUTPUT ;
SELECT @SumPage;

 

//Oracle中分页查询(Oracle中没有Ceiling()这个函数)

//分页查询(返回总页数)

PageSize=5    (每页5条,当前2页 【(2-1)*5+1,2*5】)
TotalPage

//分页查询
SELECT * FROM (
SELECT NAME,MOBILE,QQ,ROW_NUMBER() over(order by Id) num FROM TD_STUDENT 
) t WHERE t.num BETWEEN  (2-1)*5+1 AND 2*5 

//计算总页数
Select ( Ceiling((select COUNT(*) from TD_STUDENT)*1.0/5)) FROM DUAL

//分页
CREATE OR REPLACE PROCEDURE PRO_Pager(
PageSize NUMBER,
CurrentPage NUMBER,
SumPage OUT NUMBER
)
IS 
strPageSql VARCHAR2(200);
strSumSql VARCHAR2(200);
BEGIN
    strSumSql :='Select ( Ceiling((select COUNT(*) from TD_STUDENT)*1.0/'||PageSize||')) FROM DUAL';
    EXECUTE IMMEDIATE strSumSql INTO SumPage;
    strPageSql :='SELECT * FROM ('||
        'SELECT NAME,MOBILE,QQ,ROW_NUMBER() over(order by Id) num FROM TD_STUDENT '||
        ') t WHERE t.num BETWEEN  ('||CurrentPage||'-1)*'||PageSize||'+1 AND '||CurrentPage||'*'||PageSize||' ';
    EXECUTE IMMEDIATE strPageSql;
EXCEPTION
             WHEN OTHERS THEN 
             DBMS_OUTPUT.PUT_LINE(SQLERRM);
END PRO_Pager;


//调用
set serveroutput on 
var SumPage NUMBER 
EXEC PRO_Pager(5,2,:SumPage) //错误:无法查询出总页数,因为没有Ceiling这个方法
 
分页查询(返回总页数)
//不返回总页数的分页
CREATE OR REPLACE PROCEDURE PRO_Pager(
PageSize NUMBER,
CurrentPage NUMBER
)
IS 
strPageSql VARCHAR2(200);
BEGIN
    strPageSql :='SELECT * FROM ('||
        'SELECT NAME,MOBILE,QQ,ROW_NUMBER() over(order by Id) num FROM TD_STUDENT '||
        ') t WHERE t.num BETWEEN  ('||CurrentPage||'-1)*'||PageSize||'+1 AND '||CurrentPage||'*'||PageSize||' ';
    EXECUTE IMMEDIATE strPageSql;
EXCEPTION
             WHEN OTHERS THEN 
             DBMS_OUTPUT.PUT_LINE(SQLERRM);
END PRO_Pager;



set serveroutput on 
EXEC PRO_Pager(5,2)
分页查询(不返回总页数)

 


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

05 提分案例

编写一个存储过程,为班级中没有及格的学生提分
要求:提分后,不及格的人数必须小于总人数的一半,并且获取提分的次数
提示:需要三个参数,及格分数线,每次提分的分数,循环次数

//查询没及格的学生数量,需要及格线PassLine
//查询总学生数量
//if 没及格>总数/2 就循环
{
//给不及格学生增加成绩,需要每次增加多少分数EveryScore
//再次查询没及格数量
//技术循环次数Count
}

5--创建存储过程:提分
CREATE PROCEDURE usp_SubmitScore
@PassLine int, --及格分数线
@EveryScore int, --每次提的分数
@Count int OUTPUT --提分次数
AS
BEGIN
SET @Count=0
DECLARE @TotalCount int ,@FailCount int;
SET @TotalCount =( SELECT COUNT(*) FROM T_StudentGrade );
SET @FailCount = (SELECT COUNT(*) FROM T_StudentGrade WHERE Math<@PassLine);
WHILE(@FailCount>=@TotalCount*1.0/2) --分奇偶
BEGIN
UPDATE T_StudentGrade SET Math=Math+@EveryScore ;
SET @FailCount = (SELECT COUNT(*) FROM T_StudentGrade WHERE Math<@PassLine);
SET @Count=@Count+1;
END
END

--调用提分存储过程
DECLARE @num int
EXEC usp_SubmitScore 300,5,@num OUTPUT
SELECT @num


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

06 C#调用存储过程

根据Id更改姓名和年龄
6--创建存储过程:更改指定id的name和age
Alter PROCEDURE usp_UpdateT_Name
@id int,
@name nvarchar(50),
@age int
AS
BEGIN
UPDATE T_Name SET name=@name,age=@age WHERE Id=@id
END

EXEC usp_UpdateT_Name 2,'我和你',20

 

//根据id更改Name
UPDATE TP_STUDENT SET NAME='dd' WHERE ID=1 

//根据id更改Name
CREATE OR REPLACE PROCEDURE PRO_UpdateStudent(
id NUMBER,
name VARCHAR2
)
IS 
strSql VARCHAR2(200);
BEGIN 
    strSql:='UPDATE TP_STUDENT SET NAME='''||name||''' WHERE ID='||id||'';
    EXECUTE IMMEDIATE strSql;
EXCEPTION
    WHEN OTHERS THEN 
    DBMS_OUTPUT.PUT_LINE(SQLERRM);
END PRO_UpdateStudent;


EXEC PRO_UpdateStudent(1,'TTTTTTT')
Oralce中根据id更改Name

 

#region C#调用存储过程
string constr="Data Source=.;Initial Catalog=Test;User ID=sa;Password=abcd5226584";
int id=2;
string name="庞统";
int age=18;
string sql="usp_UpdateT_Name";//调用这个存储过程
SqlParameter[] param={new SqlParameter(){ParameterName="@id",Value=id},
new SqlParameter("@name",name),
new SqlParameter(){ParameterName="@age",Value=18}};
using (SqlConnection conn = new SqlConnection(constr))
using(SqlCommand cmd=new SqlCommand(sql,conn))
{
conn.Open();
cmd.CommandType=CommandType.StoredProcedure;//指定命令文本的类型是存储过程
cmd.Parameters.AddRange(param);//调用存储过程时需要这些参数
cmd.ExecuteNonQuery();
}
#endregion

alter替换Create 就是修改存储过程
如果C#参数类型如果是string,则SQL Server中为nvarchar(50)


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

07 触发器案例

触发器的作用:自动化操作,减少了手动操作以及出错的几率。
触发器是一种特殊类型的存储过程,它不同于之前的我们介绍的存储过程。【在SQL内部把触发器看做事存储过程但是不能传递参数】
一般的存储过程通过存储过程名称被直接调用,而触发器主要是通过事件进行触发而被执行。
触发器是一个功能强大的工具,在表中数据发生变化时自动强制执行。触发器可以用于SQL Server约束、默认值和规则的完整性检查,还可以完成难以普遍约束实现的复杂功能。
那究竟何为触发器?在SQL Server里面也就是对某一个表的一定的操作,触发某种条件,从而执行的一段程序。触发器是一个特殊的存储工程。

触发器分为触发前执行还是触发后执行
After触发器:
在语句执行完毕之后触发
按语句触发,而不是所影响的行数,无论所影响的行数是多少都只触发一次。
只能建立在常规表上,不能建立在视图和临时表上。(*)
可以递归触发,最高可达32级。
Update(列),在UPDATE语句触发时,判断某列是否被更新,返回BOOLEAN值。
介绍如何编写AFTER触发器。

Instead Of 触发器
用来替换原来的操作
不会递归触发
可以在约束被检查之前触发
可以建立在表和视图上(*)
介绍如何编写Insert Of触发器

触发器语法--删除
CREATE TRIGGER Tr_DeleteStudent ON Student
for delete
AS
BEGIN
INSERT INTO BackupStudent SELECT * FROM Deleted
END

触发器使用建议:
尽量不在触发器执行耗时操作,因为触发器会与SQL语句认为在同一个事务中。
触发器编写时注意多行触发时的处理。
过多触发器会造成数据库及应用程序的维护困难,同时对触发器过多的依赖,势必影响数据库的结构,同时增加了维护的复杂程序。
能不用就不用。

INSERT、DELETE、UPDATE的原理:
数据表,insert临时表,delete临时表
当我们执行insert操作时,会同时把数据插入insert临时表和数据表;
当我们执行delete操作时,会在删除数据表中的数据时同时把这条数据放到delete临时表;
当我们执行update操作时,会先把数据从数据表干掉,进入delete临时表,然后产生一条新的数据,会把这条数据插入insert临时表,同时插入数据表。

--学生表--删除操作
--删除这条数据的时候,已经删除的数据存放到一个新的表中
--先创建一个和student表结构一样的表
SELECT top 0 * into T_NewStu from T_Student --从student表中查询所有数据的第0行放入新表newstu中
select * from T_NewStu
--触发器:删除数据表中数据时,会同时把删除后的数据放入新的表中
--创建一个触发器
CREATE Trigger Tr_DeleteTStu on T_Student
AFTER DELETE --该触发器在删除之后执行
AS
BEGIN
--把deleted表中的数据添加到这个新表中,DELETED是临时表
INSERT INTO T_NewStu SELECT * FROM DELETED
END
--执行失败,是因为原表的Id自增,则新标newstu也自增,而被删除的deleted中数据确是自带了id的,所以原表Id不能自增,至少newstu不能自增。

 

//创建与原表结构一样的表(不带数据)
CREATE TABLE TP_STUDENT_BACKUP AS SELECT * FROM TP_STUDENT WHERE 1=0;
//创建与原表结构一样的表(带数据)
CREATE TABLE TP_STUDENT_BACKUP AS SELECT * FROM TP_STUDENT WHERE 1=1;

//删除学生

CREATE OR REPLACE TRIGGER TRI_DeleteStudent AFTER DELETE 
ON TP_STUDENT 
BEGIN 
    INSERT INTO TP_STUDENT_BACKUP SELECT * FROM DELETED; --错误:Oracle的删除临时表不是Deleted
END;


CREATE OR REPLACE TRIGGER TRI_DeleteStudent BEFORE DELETE 
ON TP_STUDENT 
BEGIN 
    INSERT INTO TP_STUDENT_BACKUP SELECT * FROM TP_STUDENT; --Oracle的删除临时表不是Deleted,只好删除前触发,且全部加入备份表了
END;
Oracle中触发器--删除学生之后/之前触发
//创建自增触发器

create or replace trigger TRI_TM_USER

before insert on TM_USER

for each row when (new.id is null)

begin

select  SE_TM_USER.nextval into :new.id from dual;

end ;
Oracle中自增触发器

 

触发器案例(含IF)

在BORROW表上建立一个触发器,完成如下功能:如果读者借阅的书名是"数据库技术及应用",就将该读者的借阅记录保存在BORROW_SAVE表中(注ORROW_SAVE表结构同BORROW表)。

CREATE OR REPLACE TRIGGER TRI_TM_BORROW_ANDSAVE 
AFTER INSERT ON TM_BORROW 
FOR EACH ROW 
DECLARE 
    currbno VARCHAR2(17);
BEGIN 
    SELECT BNO INTO currbno FROM TM_BOOKS WHERE BNAME= '数据库技术及应用'; 
    IF 
    currbno = :new.BNO --插入借阅记录的书籍是'数据库技术及应用'
    THEN 
    INSERT INTO TM_BORROW_SAVE(ID,CNO,BNO,RDATE) 
    VALUES(:new.id,:new.CNO,:new.BNO,:new.RDATE);
    END IF;
END;
触发器案例:IF

 

posted on 2015-11-22 09:03  AdolphYang  阅读(494)  评论(0编辑  收藏  举报