控制结构

268
2023/08/01 19:34:26

本章将讨论 PL/pgSQL 的控制结构,包括:IF、CASE、LOOP、WHILE LOOP、FOR LOOP、EXIT和CONTINUE等语句。

IF 语句

PL/pgSQL 提供了三种形式的 if 语句:

  • IF THEN
  • IF THEN ELSE
  • IF THEN ELSIF

IF THEN 语句

它的最简单形式是:

if 条件 then
   语句;
end if;

示例:

do $$
declare
  selected_film film%rowtype;
  input_film_id film.film_id%type := 0;
begin  

  select * from film
  into selected_film
  where film_id = input_film_id;
  
  if not found then
     raise notice'The film % could not be found', 
	    input_film_id;
  end if;
end $$;
  • found 是 PL/pgSQL 中的全局变量,如果分配了行,则 SELECT INTO 语句将found 设置为 true;否则,将 found 设置为 false

IF-THEN-ELSE 语句 语法如下:

if 条件 then
  语句;
else
  其它语句;
END if;

示例:

do $$
declare
  selected_film film%rowtype;
  input_film_id film.film_id%type := 100;
begin  

  select * from film
  into selected_film
  where film_id = input_film_id;
  
  if not found then
     raise notice 'The film % could not be found', 
	    input_film_id;
  else
     raise notice 'The film title is %', selected_film.title;
  end if;
end $$;

IF-THEN-ELSIF 语句

语法如下:

if 条件1 then
  语句1;
elsif 条件2 then
  语句2;
...
elsif 条件n then
  语句n;
else
  其它语句;
end if;

示例:

do $$
declare
   v_film film%rowtype;
   len_description varchar(100);
begin  

  select * from film
  into v_film
  where film_id = 100;
  
  if not found then
     raise notice 'Film not found';
  else
      if v_film.length >0 and v_film.length <= 50 then
		 len_description := 'Short';
	  elsif v_film.length > 50 and v_film.length < 120 then
		 len_description := 'Medium';
	  elsif v_film.length > 120 then
		 len_description := 'Long';
	  else 
		 len_description := 'N/A';
	  end if;
    
	  raise notice 'The % film is %.',
	     v_film.title,  
	     len_description;
  end if;
end $$;

CASE 语句

CASE 语句根据条件从 WHEN 列表中选择要执行的部分,它有两种形式:

  • 简单的 CASE 语句
  • 搜索型 CASE语句

不要混淆 CASE 语句和 CASE 表达式。

case 计算表达式
   when 表达式1 [, 表达式2, ...] then
      语句
  [ ... ]
  [else
      其它语句 ]
END case;

示例:

do $$
declare 
	rate   film.rental_rate%type;
	price_segment varchar(50);
begin
    select rental_rate into rate 
    from film 
    where film_id = 100;
	
	if found then
		case rate
		   when 0.99 then
              price_segment =  'Mass';
		   when 2.99 then
              price_segment = 'Mainstream';
		   when 4.99 then
              price_segment = 'High End';
		   else
	    	  price_segment = 'Unspecified';
		   end case;
		raise notice '%', price_segment;  
    end if;
end $$;

搜索型CASE语句

语法:

case
    when 布尔表达式1 then
      语句1
  [ when 布尔表达式2 then
      语句2
    ... ]
  [ else
      其它语句 ]
end case;

示例:

do $$ 
declare
    total_payment numeric; 
    service_level varchar(25) ;
begin
     select sum(amount) into total_payment
     from Payment
     where customer_id = 100; 
	 
	 if found then
	    case 
		   when total_payment > 200 then
               service_level = 'Platinum' ;
           when total_payment > 100 then
	           service_level = 'Gold' ;
           else
               service_level = 'Silver' ;
        end case;
		raise notice 'Service Level: %', service_level;
     else
	    raise notice 'Customer not found';
	 end if;
end $$;

LOOP 语句

LOOP 定义一个无限循环,直到被 exitreturn 语句终止。语法如下:

<<标签>>
loop
   语句;
end loop;

通常会在循环体中使用 IF 来根据条件终止循环:

<<标签>>
loop
   语句;
   if 条件 then
      exit;
   end if;
end loop;

示例:计算斐波那契数列:

do $$
declare
   n integer:= 10;
   fib integer := 0;
   counter integer := 0 ; 
   i integer := 0 ; 
   j integer := 1 ;
begin
	if (n < 1) then
		fib := 0 ;
	end if; 
	loop 
		exit when counter = n ; 
		counter := counter + 1 ; 
		select j, i + j into i,	j ;
	end loop; 
	fib := i;
    raise notice '%', fib; 
end $$;

WHILE LOOP 语句

WHILE LOOP 语句循环执行代码块,直到条件计算结果为 false,语法如下:

[ <<标签>> ]
while 条件 loop
   语句;
end loop;

示例:

do $$
declare 
   counter integer := 0;
begin
   while counter < 5 loop
      raise notice 'Counter %', counter;
	  counter := counter + 1;
   end loop;
end$$;

FOR LOOP 语句

用于迭代一系列整数值或查询的结果集。

语法一:迭代一系列整数值:

[ <<label>> ]
for loop_counter in [ reverse ] from.. to [ by step ] loop
    statements
end loop [ label ];

示例:

do $$
begin
   for counter in 1..5 loop
	raise notice 'counter: %', counter;
   end loop;
end $$;

语法二:迭代查询结果集:

[ <<label>> ]
for target in query loop
    statements
end loop [ label ];

示例:

do
$$
declare
    f record;
begin
    for f in select title, length 
	       from film 
	       order by length desc, title
	       limit 10 
    loop 
	raise notice '%(% mins)', f.title, f.length;
    end loop;
end $$;

语法三:迭代动态查询的结果集:

[ <<label>> ]
for row in execute query_expression [ using query_param [, ... ] ] 
loop
    statements
end loop [ label ];

示例:

do $$
declare
    -- 排序: 1: 标题, 2: 年份
    sort_type smallint := 1; 
	-- 电影数量
	rec_count int := 10;
	-- 用于迭代每个记录
	rec record;
	-- 动态查询
    query text;
begin
		
	query := 'select title, release_year from film ';
	
	if sort_type = 1 then
		query := query || 'order by title';
	elsif sort_type = 2 then
	  query := query || 'order by release_year';
	else 
	   raise 'invalid sort type %s', sort_type;
	end if;

	query := query || ' limit $1';

	for rec in execute query using rec_count
        loop
	     raise notice '% - %', rec.release_year, rec.title;
	end loop;
end $$;

EXIT 语句

用于终止各种循环,语法:

exit [label] [when boolean_expression]

以下语句是等效的:

EXIT WHEN counter > 10;

-- 等效于
IF counter > 10 THEN
	EXIT;
END IF;
<<block_label>>
BEGIN
    -- some code
    EXIT [block_label] [WHEN condition];
    -- some more code
END block_label;

示例:

do $$
declare 
   i int = 0;
   j int = 0;
begin
  <<outer_loop>>
  loop 
     i = i + 1;
     exit when i > 3;
	 -- inner loop
	 j = 0;
     <<inner_loop>>
     loop 
		j = j + 1;
		exit when j > 3;
		raise notice '(i,j): (%,%)', i, j;
	 end loop inner_loop;
  end loop outer_loop;
end $$;

示例二:

CONTINUE 语句

用于结束当前循环,跳转到下一个循环。语法:

continue [loop_label] [when condition]

示例:打印10以内的奇数

do
$$
declare
   counter int = 0;
begin
  
  loop
     counter = counter + 1;
	 exit when counter > 10;
	 continue when mod(counter,2) = 0;
	 raise notice '%', counter;
  end loop;
end;
$$