读《PostgreSQL服务器编程》笔记

为什么在服务器中进行程序设计

  • 使用PL/pgSQL进行完整性检查
    • 示例:使用一个函数来完成收付款
    • 初始结构及数据
      -- 创建表
      CREATE TABLE ACCOUNTS(owner text,balance numeric);
      --插入数据
      INSERT INTO ACCOUNTS VALUES('Bob',100);
      INSERT INTO ACCOUNTS VALUES('Mary',200);
      
    • 函数如下
      CREATE OR REPLACE FUNCTION transfer(i_payer text,i_recipient text,i_amount numeric(15,2))
      RETURN text
      AS
      $$
      DECLARE
      	payer_bal numeric;
      BEGIN
      	SELECT balance INTO payer_bal FROM accounts WHERE owner = i_payer FOR UPDATE;
      	
      	IF NOT FOUND THEN
      		RETURN 'Payer account not found';
      	END IF;
      	IF payer_bal < i_amount THEN 
      		RETURN 'Not enough funds';
      	END IF;
      	
      	UPDATE accounts
      	SET balance = balance + i_amount
      	WHERE owner = i_recipient;
      	IF NOT FOUND THEN
      		RETURN 'Recipient does not exist';
      	END IF;
      
      	UPDATE accounts
      	SET balance = balance - i_amount
      	WHERE owner = i_payer;
      	RETURN 'OK';
      	
      END;
      $$ LANGUAGE plpgsql;
      	
      
    • 使用
      SELECT * FROM accounts;
      ------------------
      --"Bob"		100
      --"Mary"	200
      -----------------
      SELECT * FROM transfer('Bob','Mary',14.00);
      ----------------
      "OK"
      ---------------
      SELECT * FROM accounts;
      ----------------
      --"Mary"	214.00
      --"Bob"		86.00
      ----------------
      
    • 除此之外,我们还可以扩展该函数,去做更多检查工作,比如最小可转账金额,并确保这个可以避免执行,保证完整性
    • 三个可能会返回的错误
      SELECT * FROM transfer('Fred','Mary',14.00);
      ------------------------
      --"Payer account not found"
      ------------------------
      SELECT * FROM transfer('Bob','Fred',14.00);
      ------------------
      --"Recipient does not exist"
      ------------------
      SELECT * FROM transfer('Bob','Mary',500.00);
      ---------------
      --"Not enough funds"
      ---------------
      
  • psql工具的格式化输出
    • 切换到扩展显示
    • psql -x -c "SELECT 1 AS TEST"
    • 或者
      • \x
      • SELECT 1 AS test

超越简单函数

  • 使用运算符完成数据比较
    • 示例:定义类型fruit_qty来表示水果的数量,并且告诉PostgreSQL可以来比较苹果与橘子的价值,假设一个橘子等于1.5个苹果的价值

      -- 自定义类型
      CREATE TYPE FRUIT_QTY as (name text,qty int);
      -- 测试
      SELECT '("APPLE",3)'::FRUIT_QTY;
      --------------------
      -- "(APPLE,3)"
      --------------------
      -- 创建函数
      CREATE FUNCTION fruit_qty_larger_than(left_fruit FRUIT_QTY,right_fruit FRUIT_QTY)
      RETURNS BOOL
      AS $$
      BEGIN
      	IF (left_fruit.name = 'APPLE' AND right_fruit.name = 'ORANGE') THEN
      		RETURN left_fruit.qty > (1.5 * right_fruit.qty); 
      	END IF;
      	IF (left_fruit.name = 'ORANGE' AND right_fruit.name = 'APPLE') THEN
      		RETURN (1.5 * left_fruit.qty) > right_fruit.qty; 
      	END IF;
      	
      	RETURN left_fruit.qty > right_fruit.qty; 
      	
      END;
      $$
      LANGUAGE plpgsql;	
      
      -- 测试函数
      SELECT fruit_qty_larger_than('("APPLE",4)'::FRUIT_QTY,'("ORANGE",2)'::FRUIT_QTY);
      ------------------------
      -- true
      ------------------------
      SELECT fruit_qty_larger_than('("APPLE",3)'::FRUIT_QTY,'("ORANGE",2)'::FRUIT_QTY);
      ------------------------
      -- false
      ------------------------
      -- 简化,定义操作符
      CREATE OPERATOR > (
      	leftarg = FRUIT_QTY,
      	rightarg = FRUIT_QTY,
      	procedure = fruit_qty_larger_than,
      	commutator = >
      );
      -- 测试
      SELECT '("ORANGE",2)'::FRUIT_QTY > '("APPLE",2)'::FRUIT_QTY;
      ---------------------------------
      -- true
      ---------------------------------
      SELECT '("ORANGE",2)'::FRUIT_QTY > '("APPLE",3)'::FRUIT_QTY;
      ---------------------------------
      -- false
      ---------------------------------
      

使用触发器管理相关数据

  • 服务器程序设计也包括了对自动化的动作(触发器)的设定。设定了自动化动作后,数据库中的一些操作便可触发其他一些事情也跟随发生
  • 例如可以设定一个处理过程,一边对一些商品进行供给,一边在库存表里进行自动预定处理
--创建一个水果库存表
CREATE TABLE fruits_in_stock (
	name text PRIMARY KEY,
	in_stock integer NOT NULL,
	reserved integer NOT NULL DEFAULT 0,
	CHECK (in_stock between 0 and 1000),
	CHECK (reserved <= in_stock)
);
--这里CHECK约束对一些基本规则的执行进行了限制:你不能有多于1000的水果库存(太多了可能会坏掉),你也不能有负的库存,同时不能为别人供给多余当前库存的水果
-- 供给表
CREATE TABLE fruit_offer (
	offer_id serial PRIMARY KEY,
	recipient_name text,
	offer_date timestamp default current_timestamp,
	fruit_name text REFERENCES fruits_in_stock,
	offered_amount integer
);
-- offer表为每次供给设定一个ID(保证你可以区分以后的每一次供给)、接收者、日期、供给水果的名称和供给数量
-- 为了完成自动化的库存管理,你首先需要一个触发器函数。这个函数可以实现管理逻辑
CREATE OR REPLACE FUNCTION reserve_stock_on_offer() 
RETURNS trigger
AS $$
BEGIN 
	IF TG_OP = 'INSERT' THEN
		UPDATE fruits_in_stock
		SET reserved = reserved + NEW.offered_amount
		WHERE name = NEW.fruit_name;
	ELSIF TG_OP = 'UPDATE' THEN
		UPDATE fruits_in_stock
		SET reserved = reserved - OLD.offered_amount + NEW.offered_amount
		WHERE name = NEW.fruit_name;
	ELSIF TG_OP = 'DELETE' THEN
		UPDATE fruits_in_stock
		SET reserved = reserved - OLD.offered_amount
		WHERE name = NEW.fruit_name;
	END IF;
	RETURN NEW;
END;
$$ lANGUAGE plpgsql;
-- 创建触发器
CREATE TRIGGER manage_reserve_stock_on_offer_change 
AFTER INSERT OR UPDATE OR DELETE ON fruit_offer
FOR EACH ROW EXECUTE PROCEDURE reserve_stock_on_offer();
-- 新增测试数据
INSERT INTO fruits_in_stock(name,in_stock)VALUES('APPLE',500);
INSERT INTO fruits_in_stock(name,in_stock)VALUES('ORANGE',500);
--测试触发器
INSERT INTO fruit_offer(recipient_name,fruit_name,offered_amount)VALUES('Bob','APPLE',100);
SELECT * FROM fruits_in_stock;
--------------------
-- "ORANGE"	500	0
-- "APPLE"	500	100
--------------------
--更改预定情况
UPDATE fruit_offer 
SET offered_amount = 115
WHERE offer_id = 1;
-------------------------
-- UPDATE 1
-------------------------
-- 查看fruit_offer
-----------------------------------------------------------
-- 1	"Bob"	"2019-12-26 03:16:16.167456"	"APPLE"	115
-----------------------------------------------------------
-- 查看fruits_in_stock
-------------------------
-- "ORANGE"	500	0
-- "APPLE"	500	115
-------------------------

-- 尝试修改库存
UPDATE fruits_in_stock 
SET in_stock = 100
WHERE name = 'APPLE';
----------------------------------------------------------------
-- ERROR:  new row for relation "fruits_in_stock" violates check -- constraint "fruits_in_stock_check"
-- DETAIL:  Failing row contains (APPLE, 100, 115).
-- SQL state: 23514
----------------------------------------------------------------
-- 以上其实违反的是CHECK (reserved <= in_stock)

-- 修改订单
UPDATE fruit_offer
SET offered_amount = 1100
WHERE offer_id = 1;
---------------------------------------------------------------
-- ERROR:  new row for relation "fruits_in_stock" violates check constraint "fruits_in_stock_check"
-- DETAIL:  Failing row contains (APPLE, 500, 1100).
-- CONTEXT:  SQL statement "UPDATE fruits_in_stock
-- 		SET reserved = reserved - OLD.offered_amount + NEW.offered_amount
-- 		WHERE name = NEW.fruit_name"
-- PL/pgSQL function reserve_stock_on_offer() line 8 at SQL statement
-- SQL state: 23514
---------------------------------------------------------------
-- 以上数据,是不违反fruit_offer表中的约束的,但是存在触发器,在修改这条记录后,会尝试更新fruits_in_stock表中的库存数,但是1100远大于库存数(存在这条约束),导致这条更新操作(回滚了),下面查询fruit_offer表,便知道是不是一个事务下的了
--------------------------------------------
-- "ORANGE"	500	0
-- "APPLE"	500	115
--------------------------------------------
-- 由此可知,和触发器是在一个事务下的
-- 删除记录
DELETE FROM fruit_offer WHERE offer_id = 1;
-- DELETE 1
select * from fruit_offer;
-- 无记录
SELECT * FROM fruits_in_stock
------------------------------------
-- "ORANGE"	500	0
-- "APPLE"	500	0
------------------------------------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值