SQL - 存储过程

  • 假设你在开发一个应用,应用有一个数据库,你要在哪里写SQL语句?你不会在你的应用代码里写语句,它会让你的应用代码很混乱且难以维护。具体在哪里呢?在存储过程中或函数中。

  • 存储过程是一组为了完成特定功能的SQL语句集合,经编译存储在数据库中,常用于执行复杂的业务逻辑和事务处理

  • 创建一个存储过程
    *

    sql 复制代码
    -- 创建一个存储进程
    
    delimiter $$
    create procedure get_invoices_balance()
    begin
    	select *
    	from invoices
    	where invoice_total-payment_total>0;
    end$$
    
    delimiter ;
    -- 使用 '$$' 改变默认分隔符,告诉MySQL'$$'之间是一个整体,end$$结束
    
    call get_clients()	-- call 语句 可以调用 存储进程
  • 删除存储过程

    • drop procedure if exists 名字;
  • 参数

    • 我们一般使用参数为存储过程传递值,我们也可以使用参数为调用程序赋值
    sql 复制代码
    -- 参数
    drop procedure if exists get_clients_by_state;
    delimiter $$
    create procedure get_clients_by_state(state char(2))
    begin
    	select * 
        from clients c
        where c.state = state;
    end $$
    delimiter ;
    
    call get_clients_by_state('ca')
    • 带默认值的参数

      • 存储过程调用者无法提供参数,我们为参数配置默认值
      sql 复制代码
      -- 提供默认参数
      drop procedure if exists get_clients_by_state;
      delimiter $$
      create procedure get_clients_by_state(state char(2))
      begin
      	if state is null then set state='ca';
          end if ;	-- 表示 if 语句结束,因为if语句可以是多个
      	select * 
          from clients c
          where c.state = state;
      end $$
      delimiter ;
      
      call get_clients_by_state(null)
      
      
      -- 使用 if - else 
      drop procedure if exists get_clients_by_state;
      delimiter $$
      create procedure get_clients_by_state(state char(2))
      begin
      	if state is null then 
      		select * from clients;
          else 
      		select * from clients c where c.state=state;
          end if;
      end $$
      delimiter ;
      
      call get_clients_by_state(null)
      
      -- 使用 ifnull()
      drop procedure if exists get_payments;
      delimiter $$
      create procedure get_payments(client_id int,payment_method_id tinyint)
      begin
      	select *
          from payments p
          where p.client_id=ifnull(client_id,p.client_id) 
          and p.payment_method=ifnull(payment_method_id,p.payment_method);
      end$$
      delimiter ;
      
      call get_payments(null,null);
    • 参数验证

      • 当我们使用存储进程来插入、更新、删除数据时,我们要进行参数验证,确保我们的过程不会意外地往数据库存储错误数据。
      sql 复制代码
      -- 参数验证
      drop procedure if exists make_payment;
      delimiter $$
      create procedure make_payment
      (	invoice_id int,
          payment_amount decimal(9,2),
          payment_date date)
      begin
      	if payment_amount<=0 then 
      		signal sqlstate	'Data Exception'
      			set message_text = '不合理';	-- 错误,抛出异常,终止执行			
      	end if ;
          update invoices i 
          set i.payment_total=payment_amount,
      		i.payment_date=payment_date
      	where i.invoice_id=invoice_id;
      end$$
      delimiter ;
      
      call make_payment(1, -20, '2019-03-10');
    • 输出参数

      • 我们可以 使用 参数 给 调用程序 返回 值
      sql 复制代码
      drop procedure if exists get_unpaid_invoices_for_client;
      delimiter $$
      create procedure get_unpaid_invoices_for_client (
      	client_id int,
      	out invoices_count int,		-- 标记输出参数
          out invoices_total decimal(9,2)
          )
      begin
      	select count(*),sum(invoice_total)
          into invoices_count,invoices_total -- 读取数据,复制到这些输出参数上
          from invoices i
          where i.client_id=client_id and payment_total=0;
      end$$
      delimiter ;
      
      -- 可以使用 MYSQL工作台提供的图形化工具,更简便
      set @invoices_count = 0;	-- 用户变量
      set @invoices_total = 0;
      call sql_invoicing.get_unpaid_invoices_for_client(3, @invoices_count, @invoices_total);
      select @invoices_count, @invoices_total;
  • 变量

    • 1.用户变量(会话变量)

      • 调用有输出参数的存储过程时使用这些变量,通过传递这些变量,来获取输出参数值
      • set 语句定义变量,set @invoices_count=0
    • 2.本地变量

      • 在存储过程或函数中的变量,一旦结束就被清空
      • declare 语句声明变量
      sql 复制代码
      -- 本地变量
      drop procedure if exists get_risk_factor;
      delimiter $$
      create procedure get_risk_factor()
      begin
      	declare riskfactor decimal(9,2) default 0;
          declare invoices_total decimal;
          declare invoices_count int;
          
          select count(*),sum(invoice_total)
          into invoices_count,invoices_total
          from invoices;
          
          set riskfactor=invoices_total/invoices_count*5;
          select riskfactor;
      end$$
      delimiter ;
  • 函数

    • 函数只能返回单一值,通常用于计算和转换数据
    sql 复制代码
    -- 设置函数
    drop function if exists get_risk_factor_for_client;
    delimiter $$
    create function get_risk_factor_for_client (client_id int)
    returns int
    -- 设置函数属性,确定性,能读,能更改
    -- deterministic 
    -- modifies sql data
    reads sql data
    begin
    declare riskfactor decimal(9,2) default 0;
        declare invoices_total decimal;
        declare invoices_count int;
        
        select count(*),sum(invoice_total)
        into invoices_count,invoices_total
        from invoices i
        where i.client_id=client_id;
        
        set riskfactor=invoices_total/invoices_count*5;
        return ifnull(riskfactor,0);
    end$$
    delimiter ;
    
    -- 正常内置函数使用
    select client_id,name,get_risk_factor_for_client(client_id)
    from clients;
  • 其他约定

    • 驼峰命名法
    • 下划线命名法
相关推荐
阿里小阿希34 分钟前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神39 分钟前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员1 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java1 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿1 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴1 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU1 小时前
三大范式和E-R图
数据库
一江寒逸1 小时前
零基础从入门到精通MySQL(上篇):筑基篇——吃透核心概念与基础操作,打通SQL入门第一关
数据库·sql·mysql
@土豆1 小时前
Ubuntu 22.04 运行 Filebeat 7.11.2 崩溃问题分析及解决文档
linux·数据库·ubuntu
专注API从业者2 小时前
淘宝商品详情 API 与爬虫技术的边界:合法接入与反爬策略的技术博弈
大数据·数据结构·数据库·爬虫