Postgresql RECORD与%ROWTYPE类型

2022-10-31 10:40:12 浏览数 (1)

Postgresql中支持两种行类型,两种类型使用上有一些区别,本篇结合实例简单介绍。

1 实例分析

ROWTYPE

Postgresql使用plpgsql定义函数时,支持定义行类型,即可以完全继承表的行定义:

row1 table1%ROWTYPE; row2 table2%ROWTYPE;

rowtype内部的字段名与表保持严格一致,且在定义后就完全继承表的字段名了。

RECORD

plpgsql中支持另一种行类型:record,这种类型在定义时不必指定具体类型:

rec1 record; rec2 record;

在使用时,record的内部结构由赋值时指定,PG中给行类型赋值只有两种方式:

  • SELECT into row1
  • SELECT into rec1
  • FOR row1 IN SELECT * FROM table1 WHERE c3 = 1;
  • FOR rec1 IN SELECT * FROM table1 WHERE c3 = 1;

record的行结构与结果集保持一致,也就是按照 查询结果中的列名(或列名as 别名)来定义record具体的字段名。

ROWTYPE实例

从实例结果来看有两点结论:

  • ROWTYPE变量内部字段名不会受结果集影响,与表字段保持一致
  • ROWTYPE变量内部字段的赋值,是按结果集顺序赋值的,与结果集中的字段名无关
代码语言:javascript复制
drop table tf1;
create table tf1(c1 int, c2 int);
insert into tf1 values(1,1000);
insert into tf1 values(2,2000);
insert into tf1 values(3,3000);
insert into tf1 values(4,4000);
insert into tf1 values(5,5000);
insert into tf1 values(6,6000);

CREATE  OR REPLACE PROCEDURE tfun1() AS $$
DECLARE
  row1 tf1%ROWTYPE;
  row2 tf1%ROWTYPE;
  row3 tf1%ROWTYPE;
BEGIN
	SELECT * INTO row1 from tf1 where c1 > 1;
	raise notice 'row1.c1: %', row1.c1;
	raise notice 'row1.c2: %', row1.c2;

	SELECT 2000,c1 INTO row2 from tf1 where c1 = 1;
	raise notice 'row2.c1: %', row2.c1;
	raise notice 'row2.c2: %', row2.c2;
	
	SELECT c2 as xxx INTO row3 from tf1 where c1 = 1;
	raise notice 'row3.c1: %', row3.c1;
	raise notice 'row3.c2: %', row3.c2;
	
END;
$$ LANGUAGE plpgsql;

postgres=# call tfun1();
NOTICE:  row1.c1: 2
NOTICE:  row1.c2: 2000
NOTICE:  row2.c1: 2000
NOTICE:  row2.c2: 1
NOTICE:  row3.c1: 1000
NOTICE:  row3.c2: <NULL>
CALL

RECORD实例

从实例结果来看有两点结论:

  • RECORD变量内部字段名会受结果集影响,字段名与结果集保持一致
  • RECORD变量内部字段的赋值,也是按结果集顺序赋值的
代码语言:javascript复制
drop table tf1;
create table tf1(c1 int, c2 int);
insert into tf1 values(1,1000);
insert into tf1 values(2,2000);
insert into tf1 values(3,3000);
insert into tf1 values(4,4000);
insert into tf1 values(5,5000);
insert into tf1 values(6,6000);

CREATE  OR REPLACE PROCEDURE tfun2() AS $$
DECLARE
  row1 record;
  row2 record;
  row3 record;
BEGIN
	SELECT * INTO row1 from tf1 where c1 > 1;
	raise notice 'row1.c1: %', row1.c1;
	raise notice 'row1.c2: %', row1.c2;

	SELECT 2000,c1 INTO row2 from tf1 where c1 = 1;
	raise notice 'row2.c1: %', row2.c1;
	-- raise notice 'row2.c2: %', row2.c2;
	
	SELECT c2 INTO row3 from tf1 where c1 = 1;
	-- raise notice 'row3.c1: %', row3.c1;
	raise notice 'row3.c2: %', row3.c2;

	SELECT c2 as xxx INTO row3 from tf1 where c1 = 1;
	raise notice 'row3.xxx: %', row3.xxx;
END;
$$ LANGUAGE plpgsql;


postgres=# call tfun2();
NOTICE:  row1.c1: 2
NOTICE:  row1.c2: 2000
NOTICE:  row2.c1: 1
NOTICE:  row3.c2: 1000
NOTICE:  row3.xxx: 1000
CALL

2 内部代码分析

2.0 总结

%ROWTYPE与RECORD不同点:

  • ROWTYPE的类型就是表的OID,RECORD的类型是统一的record(oid=2249)。
  • ROWTYPE用的表的tupledesc,RECORD用 的是SPI返回值的desc:SPI_tuptable->tupdesc

%ROWTYPE与RECORD相同点:

  • 都是用PLpgSQL_rec的expand record保存行数据
  • 都是用PLpgSQL_recfield记录字段名 和 字段对应数据在expand record中的位置

2.1 ROWTYPE

表结构:create table tf1(c1 int, c2 int);

row1 tf1%ROWTYPE;:编译后

代码语言:javascript复制
PLpgSQL_rec = 
  { dtype = PLPGSQL_DTYPE_REC, 
    dno = 1, 
    refname = 0x2b37e08 "row1", 
    lineno = 3, 
    isconst = false, 
    notnull = false, 
    default_val = 0x0, 
    datatype = 0x2b37d50,  --> {typname = 0x2b37da0 "tf1", typoid = 16637, ttype = PLPGSQL_TTYPE_REC }
    rectypeid = 16637,     -->【表的OID】
    firstfield = 5,  --> 【指向dno=5的recfield】
    erh = 0x0}

PLpgSQL_recfield = 
  { dtype = PLPGSQL_DTYPE_RECFIELD, 
    dno = 4,   <<<---------------------------------------------------------
    fieldname =  "c1",                                                      
    recparentno = 1,                                                        |
    nextfield = -1,                                                         |
    rectupledescid = 13,                                                    | 
    finfo = {fnumber = 1,  ftypeid = 23, ftypmod = -1, fcollation = 0}      |
  }                                                                         |
                                                                            |
PLpgSQL_recfield =                                                          |
  { dtype = PLPGSQL_DTYPE_RECFIELD,                                         |
    dno = 5,                                                                |
    fieldname =  "c2",                                                      |
    recparentno = 1,                                                       /
    nextfield = 4,   -----------------------------------------------------
    rectupledescid = 13, 
    finfo = {fnumber = 2,  ftypeid = 23, ftypmod = -1, fcollation = 0}}

SELECT * INTO row1 from tf1 where c1 > 1;如何赋值?

代码语言:javascript复制
exec_stmt_execsql
  exec_prepare_plan                【1】删掉into做计划:SELECT * from tf1 where c1 > 1
  SPI_execute_plan_with_paramlist  【2】执行:SELECT * from tf1 where c1 > 1
  if (stmt->into)
    
    SPITupleTable *tuptab = SPI_tuptable;                            【3】全局变量拿到结果元组
    target = (PLpgSQL_variable *) estate->datums[stmt->target->dno]; 【4】找到PLpgSQL_rec
    exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc); 【5】把第一行赋值给PLpgSQL_rec
      | newerh = make_expanded_record_for_rec(estate, rec, tupdesc, NULL) 【6】用tupdesc组装扩展元组
      | make_expanded_record_from_typeid
      |    【7】申请内存结构
      |    |----------------------|
      |    | ExpandedRecordHeader |
      |    | Datum                |   <----erh->dvalues
      |    | Datum                |
      |    | bool                 |   <----erh->dnulls
      |    | bool                 |
      |    【8】给ERH填充变量,记录类型信息
      | expanded_record_set_tuple
      |    【9】给变量赋值
      |    newtuple = heap_copytuple(tuple)
      |    【9】记录flat格式元组
      |    erh->fvalue = newtuple;
      |    【10】记录expand需要的数据
      |    erh->fstartpt = (char *) newtuple->t_data;
      | assign_record_var 【11】把新的erh赋值给PLpgSQL_rec->erh

2.2 RECORD

简化用例

代码语言:javascript复制
drop table tf1;
create table tf1(c1 int, c2 int);
insert into tf1 values(1,1000);
insert into tf1 values(2,2000);
insert into tf1 values(3,3000);
insert into tf1 values(4,4000);
insert into tf1 values(5,5000);
insert into tf1 values(6,6000);

CREATE  OR REPLACE PROCEDURE tfun2() AS $$
DECLARE
  row1 record;
BEGIN
	SELECT c1, c2 as xxx INTO row1 from tf1 where c1 = 1;
	raise notice 'row1.c1: %', row1.c1;
	raise notice 'row1.xxx: %', row1.xxx;
END;
$$ LANGUAGE plpgsql;


postgres=# call tfun2();
NOTICE:  row1.c1: 1
NOTICE:  row1.xxx: 1000
CALL

row1 record;:编译后

代码语言:javascript复制
PLpgSQL_rec = {
  dtype = PLPGSQL_DTYPE_REC, 
  dno = 1, 
  refname = 0x2b6e0c8 "row1", 
  lineno = 3, 
  isconst = false, 
  notnull = false, 
  default_val = 0x0, 
  datatype = 0x2b6e010,  ----> {typname = 0x2b6e060 "record", typoid = 2249, ttype = PLPGSQL_TTYPE_REC}
  rectypeid = 2249, 
  firstfield = 3, 
  erh = 0x0}
  
 PLpgSQL_recfield = {
   dtype = PLPGSQL_DTYPE_RECFIELD, 
   dno = 2, 
   fieldname = 0x2b6e510 "c1", 
   recparentno = 1, 
   nextfield = -1, 
   rectupledescid = 32, 
   finfo = {fnumber = 1, ftypeid = 23, ftypmod = -1, fcollation = 0}}

PLpgSQL_recfield = {
  dtype = PLPGSQL_DTYPE_RECFIELD, 
  dno = 3, 
  fieldname = 0x2b754b8 "xxx", 
  recparentno = 1, 
  nextfield = 2, 
  rectupledescid = 32, 
  finfo = {fnumber = 2, ftypeid = 23, ftypmod = -1, fcollation = 0}}

SELECT c1, c2 as xxx INTO row1 from tf1 where c1 = 1;如何赋值?

代码语言:javascript复制
exec_stmt_execsql
  exec_prepare_plan                【1】删掉into做计划:SELECT c1, c2 as xxx from tf1 where c1 = 1
  SPI_execute_plan_with_paramlist  【2】执行:SELECT c1, c2 as xxx from tf1 where c1 = 1
  if (stmt->into)
    exec_move_row

与ROWTYPE基本相同。

0 人点赞