Original URL: https://amazonaws-china.com/cn/blogs/database/code-conversion-challenges-while-migrating-from-oracle-or-microsoft-sql-server-to-postgresql/
核子可乐译
PostgreSQL已经成为当下最流行的开源关系数据库系统之一。当客户从Oracle及微软SQL Server等商业数据库向外迁移时,PostgreSQL已经成为一大首选替代方案。目前,AWS为大家提供两种PostgreSQL托管选项:Amazon RDS与Amazon Aurora。
除了提供托管PostgreSQL服务之外,AWS还准备了一系列用于协助迁移的工具与资源。AWS Schema Conversion Tool(SCT)就是一款免费AWS工具,可帮助您转换现有schema,且同时支持多个源数据库与目标数据库。AWS Database Migration Service(数据库迁移服务,简称DMS)则用于在异构数据库与同构数据库之间完成数据的传输与连接复制。当然,AWS还提供迁移指导手册,其中包含关于商业数据库以及开源数据库(包括PostgreSQL)之间的大量功能映射说明。
在今天的文章中,我们将介绍从PL/SQL转换为PL/pgSQL的技巧与最佳实践,希望帮助大家在顺利将代码转换为PostgreSQL形式的同时,获取良好的数据库运行性能。本文主要面向从事数据库迁移的开发人员,并要求您预先掌握关于数据库及PL/SQL的基础知识。
性能考量
本节主要探讨一系列在从商业或传统数据库(例如SQL Server及Oracle)向PostgreSQL迁移时,可能对数据库性能造成影响的具体因素。虽然大部分数据库中包含类似的对象,但其中仍有部分对象可能在迁移之后影响到系统的运作方式。本节将向大家介绍如何通过调整存储流程、函数以及SQL语句获得更好的性能。
数据类型
为了避免不必要的返工,在正式启动迁移项目之前,大家需要将目标数据库中的数据类型与源系统正确映射起来。下表总结了从Oracle到SQL Server、再到PostgreSQL的一系列常见数据类型映射。
Oracle |
PostgreSQL |
SQL Server |
备注 |
Number |
Small Integer |
Tinyint / Smallint |
通常用于对数值受限的表进行查找。 |
Number |
Integer / Bigint |
Integer / Bigint |
|
Number |
Double Precision / Float / Numeric |
Double Precision / Float / Numeric |
对于金融等需要在应用内保存高精度值的使用场景,您可以将其配置为数字/小数。而在其他场景下,使用双精度或浮点数即可。 |
Varchar |
Char(n) Varchar(n) Varchar Text Character varying |
Nchar Nvarchar Ntext |
|
Timestamp(6) |
Timestamp without timezone |
DateTime2(p) DateTime |
|
Clob |
Text |
|
|
Blob |
Raw |
|
|
|
Bytea |
Binary, Image, VarBinary |
|
Boolean |
Boolean |
Bit |
|
XML |
XML |
XML |
|
为什么要使用smallint/integer/bigint,而不直接使用数字?
要在数据库中获取最佳性能,选择最适合的数据类型非常重要。
如果您的表列中最多只能包含四位数字,那么具有2字节(smallint)的列数据类型就足以完成任务,意味着我们不必将其定义为4字节(整数/实数)、8字节(bigint/双精度)或者可变字节(数字)等更占资源的数据类型。
数值是一种可以容纳13万1千个数位的复杂类型,主要用于表示货币金额及其他少数需要极高精度的数量。但与整数类型或者浮点类型相比,数字的运算符处理速度很慢,因此计算速度也相当缓慢。
在下表的示例当中,我们可以看到在分别使用smallint/int/bigint建立无索引非精确列时,表整体大小发生的变化。
数据类型 |
大小 |
外部大小 |
插入值 |
numericsize |
16 KB |
8192 bytes |
插入numericsize值 (1234678) |
smallintsize |
8192 bytes |
0 bytes |
插入numericsize值(1234) |
intsize |
8192 bytes |
0 bytes |
插入numericsize值(123457) |
bigintsize |
8192 bytes |
0 bytes |
插入numericsize值(123486) |
下表使用与上表相同的信息,但包含索引。在此表中,大小指的是表的总体大小,外部大小则代表相关对象(例如索引)的大小。
数据类型 |
大小 |
外部大小 |
numericsize |
32 KB |
24 KB |
smallintsize |
24 KB |
16 KB |
intsize |
24 KB |
16 KB |
bigintsize |
24 KB |
16 KB |
AWS SCT在不了解实际数据大小的情况下,也能够将数字与表中的数字数据类型映射起来。这款工具还提供选项,帮助用户在转换过程中配置/映射正确的数据类型。
存储过程与函数
PostgreSQL 10及较早版本并不支持存储过程。Oracle与SQL Server中的所有存储过程与函数都将被映射为PostgreSQL中的函数。但从版本11开始,PostgreSQL也引入了存储过程支持,其基本原理与Oracle类似。
PostgreSQL支持三种波动函数类别,您需要在迁移当中根据函数特性指定适当的类别,即:Volatile
、Stable
与Immutable
。正确标记函数类别,有望给我们的数据库性能带来显著提升。
Volatile
Volatile
类型表示那些即使在单一表扫描中也能够变更的函数值,因此无法做出进一步优化。一般来说,大部分数据库函数并不会选择volatile类型,例如ndom()
、currval()
以及timeofday()
。只有那些具有副作用的函数(即使结果具有可预测性)才会被归类为volatile,例如setval()
。如果在函数创建期间未提供volatility类型,则默认情况下所有新函数都将被标记为volatile。
下面是一条示例函数,用以说明执行Volatile
函数需要花费多长时间。
Create Or Replace Function add_ten_v(num int) Returns integer AS $$
Begin
Perform pg_sleep(0.01);
Return num + 10;
End
$$ Language 'plpgsql' Volatile;
执行以下函数即可查看执行成本。
lab=>Explain Analyze Select add_ten_v(10)FROM generate_series(1,100,1);
Query plan
-----------------------------------------------------------------------------
Function Scan on generate_series (cost=0.00..260.00 rows=1000 width=4) (actual time=10.200..1015.461 rows=100 loops=1)
Planning time: 0.030 ms
Execution time: 1015.501 ms
(3 rows)
Stable
类型表示该函数无法修改数据库。此外,Stable还表示在单一表扫描操作当中,它对于相同的参数值将始终返回相同的结果,但具体结果可能会在不同SQL语句之间有所区别。如果需要创建一条结果取决于数据库查找或者参数变量(例如当前时区)的函数,那么Stable类型往往是理想的选择。current_timestamp
函数家族就是其中的典型代表,它们的值在事务执行过程中始终保持不变。
下面是一条示例函数,用以显示执行Stable函数需要花费多长时间。
Create Or Replace Function add_ten_s(num int) Returns integer AS $$
Begin
Perform pg_sleep(0.01);
Return num + 10;
End
$$ Language 'plpgsql' Stable;
执行以下函数即可查看执行成本。
lab=> Explain Analyze Select add_ten_s(10) From generate_series(1,100,1);
Query Plan
-------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series (cost=0.00..260.00 rows=1000 width=4) (actual time=10.153..1013.814 rows=100 loops=1)
Planning time: 0.031 ms
Execution time: 1013.846 ms
(3 rows)
Immutable
Immutable
类型表示该函数无法修改数据库,而且在给定相同的参数值时将始终返回相同的结果。这意味着其无法执行数据库查找,也无法使用参数列表中未直接存在的信息。如果选定此选项,对该函数的一切全常数参数调用都将被立即替换为该函数的值。
下面是一条示例函数,用以显示执行Immutable函数需要花费多长时间。
Create Or Replace Function add_ten_i(num int) Returns integer AS $$
Begin
Perform pg_sleep(0.01);
Return num + 10;
End
$$ Language 'plpgsql' Immutable;
执行以下函数即可查看执行成本。
lab=> Explain Analyze Select Add_Ten_I(10) From Generate_Series(1,100,1);
Query Plan
--------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series (cost=0.00..10.00 rows=1000 width=4) (actual time=0.009..0.016 rows=100 loops=1)
Planning time: 10.185 ms
Execution time: 0.030 ms
(3 rows)
Time: 10.681 ms
以上所有函数都将返回以下值:
lab=> Select Add_Ten_V(10), Add_Ten_S(10), Add_Ten_I(10);
add_ten_v | add_ten_s | add_ten_i
-----------+-----------+-----------
20 | 20 | 20
(1 row)
尽管以上各函数都返回相同的值,但根据函数的不同,我们可能需要从中选择特定一种以获得更好的数据库性能。
对各函数执行的测试结果表明,这些函数的基本功能完全相同,但其中Immutable函数的执行时长最短。这是因为Immutable类别允许优化程序在查询调用期间通过常量参数对该函数进行预评估。
视图与查询中的函数调用
许多应用程序都会使用包含函数调用的视图与查询。如上一节所述,在PostgreSQL当中,函数调用有可能占用大量资源,特别是在未能正确设置函数volatility类别的情况下。此外,函数调用本身也会增加相应的查询成本。
为此,我们需要根据函数功能为函数选择适当类别。如果您的函数更适合Immutable
或者Stable
条件,那么正确设置以取代默认的Volatile
将带来一定性能优势。
以下示例代码,为一条包含Volatile
函数调用的查询。
Explain Analyze
Select Empid,
Empname,
Getdeptname(Deptid),
Salary,
Doj,
Address
From Emp
其中的getDeptname()
函数被标记为volatile。该查询的总运行时长为2秒886毫秒。
下面来看包含Stable
函数调用的查询示例。
Explain Analyze
Select Empid,
Empname,
Getdeptnames(Deptid),
Salary,
Doj,
Address
From Emp
其中的getDeptname()
函数被标记为stable,其总运行时长为2秒644毫秒。
以下示例代码将函数替换为功能。
Explain Analyze
Select Empid,
Empname,
Deptname,
Salary,
Doj,
Address
From Emp E
Join Dept D On D.Deptid = E.Deptid
函数逻辑被成功转换至新查询内,而总运行时长仅为933毫秒。
Exception优化
PostgreSQL允许用户使用Exception
与Raise
语句捕捉并触发错误的功能。这项功能虽然具有现实意义,但也要付出一定代价。Raise
语句会在PL/pgSQL函数的执行过程中引发错误与异常。在默认情况下,PL/pgSQL函数内部发生的任何错误都会导致执行中止以及变更回滚。为了从错误中正常恢复,PL/pgSQL可以使用Exception
子句捕捉具体错误。要实现这项功能,我们需要保证PostgreSQL在输入还有异常处理的代码段之前保存事务状态。这项操作会占用大量资源,因此间接增加了运行成本。
为了避免这部分成本,我们的建议是:要么在应用程序端捕捉异常,要么确保提前进行必要验证、使得函数永远不会发生异常。
以下代码示例展示了在函数调用中纳入异常,会给数据库性能造成怎样的影响。
Create Or Replace Function empsal (eid int)
Returns Integer AS $total$
Declare
Total Integer;
Begin
Update Emp Set Salary = Salary * 0.20 Where Empid = Eid;
Return 1;
End;
$$ Total Language Plpgsql;
Create Or Replace Function Empsalexcep (Eid Int)
Returns Integer AS $Total$
Declare
Total Integer;
Begin
Update Emp Set Salary = Salary * 0.20 Where Empid = Eid;
RETURN 1;
Exception
When Others Then
Raise Notice 'Salary Update Failed ';
END;
$$ Total Language Plpgsql;
Select * From Empsal(3) – 78ms -- without exception handling
Select * From Empsalexcep(3) - 84ms -- with exception handling
如果大家无法在无异常状况下进行验证,那么异常将不可避免。在以上示例中,我们可以检查诊断结果以跟踪是否存在需要关注的变更。另外,如果可能,请尽量不要使用异常处理机制。
无需提取操作的计数器
不少应用程序需要进行游标循环并获取计数,才能完成记录内容的提取工作。由于提取操作会在无对应记录时返回null,因此最好能用提取状态来替代声明两项变量的传统计数检查方法。如此一来,我们可以避免声明额外变量并对其进行检查,从而减少需要执行的语句数量并获得更好的性能。具体请参见以下代码示例。
Select Count(1) Into Count_Value
From Tab1
Where Tab1.A = Value
Counter = 0
Open Dvscriptcursor For Select Id From Tab1;
While (Counter < Count_Value)
Loop
Fetch Id Into Var_Id
……..
…….
Counter = Counter +1;
End Loop
我们也可以按照以下步骤重新编写上述代码,其中使用游标本体进行迭代并利用游标状态中断/退出循环,这就有效避免了引入两个新的变量。
OPEN Dvscriptcursor For Select Id From Tab1;
Loop
Fetch Id Into Var_Id
Exit When Not Found
……..
…….
…….
End Loop
检查EXISTS,而非直接计数
在旧版应用程序当中编写SQL查询时,我们首先需要查找匹配的记录数,而后才能应用所需的业务逻辑。如果表中包含数十亿条记录,那么获取记录数量往往需要占用大量资源。
以下代码示例演示了如何先检查行数,而后更新数据。
Create Or Replace Function Empsal (Eid Int)
Returns Integer As $Total$
Declare
Total Integer;
Begin
If (Select Count(*) From Emp Where Empid = Eid) > 0 Then -- Wrong Usage
Update Emp Set Salary = Salary * 0.20 Where Empid = Eid ;
End If;
Return 1;
End;
$Total$ Language plpgsql;
此查询的总运行时长为163毫秒。
我们也可以重新编写代码以检查一列——而非一整行,这样可以节约成本并提高性能。具体请参见以下代码示例。
Create Or Replace Function Empsal (Eid Int)
Returns Integer AS $Total$
Declare
Total Integer;
Begin
If Exists (Select 1 From Emp Where Empid = Eid) Then. – Right Usage
Update Emp Set Salary = Salary * 0.20 Where Empid = Eid ;
End If;
RETURN 1;
END;
$$ Total Language plpgsql;
这条查询的总运行时长为104毫秒。
在DML语句后记录计数结果
在大多数旧版应用程序中,我们可以通过记录计数结果来判断数据操作语句是否引发了变更。在PostgreSQL中,这部分信息被保留在统计信息当中,用户可以随时检索以避免在操作之后对值进行计数。我们可以使用诊断程序检索受影响的行数,具体如以下代码示例所示。
Create Or Replace Function Empsal (Eid Int)
Returns Integer AS $Total$
Declare
Total Integer;
Rows_Affected Int;
Begin
If Exists (Select 1 From Emp Where Empid = Eid) Then
Update Emp Set Salary = Salary * 0.20 Where Empid = Eid ;
Get Diagnostics Rows_Affected = ROW_COUNT;
End If;
RETURN 1;
END;
$$ Total Language plpgsql;
模式匹配与搜索
从表中检索数据时,常见的做法是将通配符%
或 _
与LIKE
表达式配合使用(在进行非敏感搜索时也可以使用ILIKE
)。如果通配符位于给定schema的开头,那么即使存在索引,查询规划程序也无法使用该索引。在这种情况下,我们就必须使用顺序扫描,而这是一项相当耗时的操作。为了在处理数百万条记录时获得良好性能,并保证查询规划程序正常使用可用的索引,大家需要在谓词的中间或结尾处(而非开头)使用通配符,从而强制引导规划程序使用索引。
除了LIKE
表达式之外,大家也可以使用pg_trgm
模块/扩展进行模式匹配。其中pg_trgm
模块将为我们提供用于确定字母数字文本相似性的函数与运算符,同时提供支持相似字符串快速搜索的索引运算符类。关于更多详细信息,请参阅PostgreSQL网站上发布的pg_tram说明文档。
在Oracle、SQL Server以及PostgreSQL之间进行映射转换
本节主要介绍在Oracle、SQL Server以及PostgreSQL数据库中编写SQL语句方面的不同之处。
默认FROM子句
在Oracle当中,FROM
子句具有强制性,因此只能在代码中使用Select 1 from Dual
;。而在PostgreSQL与SQL当中,大家可以选择使用代码Select 1
;。
生成值集合
通过指定开始数字与结束数字,我们可以生成一个值集合。
在Oracle中,我们不需要起始数字,但可以提供结束数字。具体代码示例如下。
Select Rownum As Rownum_Value From Dual Connect By Level <= 64
在使用起始与结束数字时,使用以下代码。
With t(n) As (
Select 1 from dual
Union All
Select n+1 From t Where n < 64
)
Select * From t;
在PostgreSQL中,使用以下代码。
Select Generate_Series(1,64) AS Rownum_Value
在SQL Server当中,使用以下代码。
;With n(n) As
(
Select 1
Union All
Select n+1 From n Where n < 64
)
Select n From n Order By n
联接(+)运算符
在Oracle中,左联接运算的实现需使用以下代码。
Select b.id, b.title, b.author, b.year_published, l.name language
From books b, ibrary.languages l
Where l.id (+)= b.language_id
Order By b.id
要实现右联接,使用以下代码。
Select b.id, b.title, b.author, b.year_published, l.name language
From books b, ibrary.languages l
Where l.id = b.language_id (+)
Order BY b.id
若需了解更多详细信息,请参阅Oracle数据库网站上的SQL新手指南(第五部分):联接。
PostgreSQL与SQL Server上并不存在“+”这种可对表进行左右联接的功能;相反,二者使用以下两项查询。
Select b.id, b.title, b.author, b.year_published, l.name language
From books b,
Left join ibrary.languages l
On l.id = b.language_id
Order BY b.id
Select b.id, b.title, b.author, b.year_published, l.name language
From books b,
Right join ibrary.languages l
On l.id = b.language_id
Order By b.id
将类型作为函数参数
在SQL Server中,我们可以使用Type
数据类型传递多条记录。要在PostgreSQL中实现相同的效果,大家可以通过JSON格式或者数组形式将该类型视为JSON或者文本数据类型。在以下示例代码中,JSON格式的文本数据类型就包含有多条记录。您可以将其插入临时表,并在后续代码中执行进一步处理。
Create Table emptable1
(
empid integer,
last_name varchar(100),
first_name varchar(100),
deptid integer,
salary double precision
)
Oracle
以下代码所示,为多条记录如何在Oracle的varchar数据类型中实现传递。
DECLARE
StructType Varchar2(1000) Default '[{"empid" : 1, "last_name":"AccName1", "first_name":"AccName1", "deptid":"1", "salary":"1234.578"}
,{"empid" : "2", "last_name":"AccName2", "first_name":"AccName2", "deptid":"2", "salary":"4567.578"}
]';
Begin
Insert Into emptable1 (empid,last_name,first_name,deptid,salary)
With Json As
( Select StructType --'[{"firstName": "Tobias", "lastName":"Jellema"},{"firstName": "Anna", "lastName":"Vink"} ]' doc
from dual
)
Select empid,last_name,first_name,deptid,salary
From json_table( (Select StructType from json) , '$[*]'
Columns ( empid PATH '$.empid'
,last_name Path '$.last_name'
, first_name Path '$.first_name'
,deptid Path '$.deptid'
,salary Path '$.salary'
)
);
End;
SQL Server
以下代码所示,为多条记录如何在SQL Server的表类型中实现传递。
--Create Type structure
Create Type empTableType as Table
(
empid integer,
last_name varchar(100),
first_name varchar(100),
deptid integer,
salary double precision
);
--Create Procedure
Create Procedure InsertEmpTable
@InsertEmpt_TVP empTableType READONLY
As
Insert Into emptable1(empid,last_name,first_name,deptid,salary)
Select * FROM @InsertEmpt_TVP;
Go
--Calling the SP with dynamic block and type
Declare @EmpTVP AS empTableType;
Insert Into @EmpTVP(empid,last_name,first_name,deptid,salary)
Values (1,'FirstName','Last_name',1,1234.566),
(2,'FirstName','Last_name',1,1234.566),
(3,'FirstName','Last_name',1,1234.566),
(4,'FirstName','Last_name',1,1234.566),
(5,'FirstName','Last_name',1,1234.566);
Exec InsertEmpTable @EmpTVP;
PostgreSQL
以下代码所示,为多条记录如何在PostgreSQL中实现与之前Oracle及SQL Server相同的传递效果。
Do $$
Declare
StructType Text Default '[{"empid" : "1", "last_name":"AccName1", "first_name":"AccName1", "deptid":"1", "salary":"1234.578"},
{"empid" : "2", "last_name":"AccName2", "first_name":"AccName2", "deptid":"2", "salary":"4567.578"}]';
Begin
Insert Into emptable
Select * From json_to_recordset(StructType::json)
as x("empid" Int, "last_name" Varchar, "first_name" Varchar, "deptid" Int, "salary" Double Precision);
Pivoting转换
在PostgreSQL当中,pivoting功能无法直接启用,需要额外扩展提供支持。tablefunc
扩展带来的crosstab
函数可用于创建数据pivot表,其功能与SQL Server及Oracle类似。以下是Oracle、SQL Server以及PostgreSQL中的pvioting功能代码。
Create Table crosstabFunc (
id Number,
customer_id Number,
product_code Varchar2(5),
quantity Number
);
Insert Into crosstabFunc values (1, 1, 'A', 10);
Insert Into crosstabFunc Values (2, 1, 'B', 20);
Insert Into crosstabFunc Values (3, 1, 'C', 30);
Insert Into crosstabFunc Values (4, 2, 'A', 40);
Insert Into crosstabFunc Values (5, 2, 'C', 50);
Insert Into crosstabFunc Values (6, 3, 'A', 60);
Insert Into crosstabFunc Values (7, 3, 'B', 70);
Insert Into crosstabFunc Values (8, 3, 'C', 80);
Insert Into crosstabFunc Values (9, 3, 'D', 90);
Insert Into crosstabFunc Values (10, 4, 'A', 100);
Oracle
使用以下代码在Oracle中实现pivoting功能。
Select *
From (Select customer_id, product_code, quantity
From crosstabFunc)
Pivot (Sum(quantity) As sum_quantity For (product_code) In ('A' AS a, 'B' AS b, 'C' AS c))
SQL Server
使用以下代码在SQL Server中实现pivoting功能。
Select * From
(Select customer_id, product_code, quantity
From crosstabFunc) as cf
Pivot (Sum(quantity) For product_code In (A,B,C))
as cf1
PostgreSQL
使用以下代码在PostgreSQL中实现pivoting功能。
Create Extension tablefunc;
Select * From Crosstab
(' Select customer_id, product_code, quantity
From crosstabFunc' )
as T ( customer_id Int, "A" Int, "B" Int, "C" Int)
对数组进行unpivoting
PostgreSQL同样无法直接提供Unpivot
函数。在将SQL Server或者Oracle转换为PostgreSQL时,unpivot函数会被映射为一个数组。具体请参见以下代码示例。
Create Table Students
(
Id Int Primary Key Identity,
Student_Name Varchar (50),
Math_marks Int,
English_marks Int,
History_marks Int,
Science_marks Int
)
Go
Insert Into Students Values ('Sally', 87, 56, 78, 91 )
Insert Into Students Values ('Edward', 69, 80, 92, 98)
Oracle
使用以下代码示例在Oracle中实现unpivoting功能。
Select StudentName, course,score
From Students
Unpivot (score For course In (Math_marks AS 'Maths', English_marks AS 'English', History_marks AS 'History', Science_marks As 'Science'));
SQL Server
使用以下代码示例在SQL Server中实现unpivoting功能。
Select Student_Name, Course, Score
From Students
Unpivot
(
Score
For Course in (Math_marks, English_marks, History_marks, Science_marks)
) AS SchoolUnpivot
PostgreSQL
使用以下代码示例在PostgreSQL中实现unpivoting功能。
Select Student_Name, course, score From
(
Select
Student_Name,
Unnest (Array[ 'Math', 'English','History', 'Science']
) As course,
Unnest (Array[ Math_marks, English_marks,History_marks,Science_marks]
) As score
From StudentsP
) AS Unpvt
从单一函数处返回多个结果集
SQL Server可以将多条结果按多行形式直接返回。大家可以使用游标在PostgreSQL与Oracle中实现相同的效果,如以下示例所示。
Oracle
使用以下代码在Oracle中通过单一过程返回多个结果集。
Create Procedure Spgetdept23
(P_Cur Out Sys_Refcursor, P_Cur12 Out Sys_Refcursor)
Is
Begin
Open P_Cur For
Select * From employees;
Open P_Cur12 For
Select * From dept;
End;
var cur Refcursor
var cur2 Refcursor
Exec Spgetdept23(:cur,:cur2);
Print cur;
SQL Server
使用以下代码在SQL Server中通过单一过程返回多个结果集。SQL Server不需要使用额外其他参数。
Create Procedure Dbo.Multiple_Reseultset
As
Begin
Select * From HumanResources.Employee
Select * From HumanResources.Department
End
To execute the procedure in SQL Server, enter the following code.
Exec Dbo.Multiple_Reseultset
使用以下代码在SQL ServerL中通过单一过程返回多个结果集。
Exec Dbo.Multiple_Reseultset
PostgreSQL
使用以下代码在PostgreSQL中通过单一过程返回多个结果集。
Create Or Replace Function Multiple_Reseultset()
Returns Setof Refcursor As
$$
Declare
cur1 Refcursor;
cur2 Refcursor;
Begin
Open cur1 For
Select * From HumanResources.employee;
Return Next cur1;
Open cur2 For
Select * From HumanResources. Department;
Return Next cur2;
End
要在PostgreSQL中执行此过程,请输入以下代码。
Begin
Select * From Public.Multiple_Reseultset( )
Fetch All In "<unnamed portal 1>"
Fetch All In "<unnamed portal 2>"
End
带别名的内联查询
PostgreSQL语义可将内联视图称为Subselect
或者Subquery
。Oracle支持在内部语句中省略别名,PostgreSQL与SQL Server则要求必须使用别名。
Oracle
使用以下代码在Oracle中执行内联查询演示。
Select a.col1, col2_fromSubquery -- you can specify the columns directly from the subquery with out any prefix of subquery unless have common columns names.
from emplyee a,
(select * from salary )
SQL Server与PostgreSQL
在Oracle中编写的示例内联查询若要在SQL Server及PostgreSQL中运行,则必须使用别名。
Select a.col1, b.col2_fromSubquery
from emplyee a,
(select * from salary ) b
数据顺序
将数据从Oracle或SQL Server迁移至PostgreSQL之后,数据的检索顺序也可能发生改变。这种改变可能是受到了插入顺序、列数据类型及具体数值、或者排序规则的影响。
为了保证数据顺序的正确性,我们需要确定业务需求并在查询当中采用Order by
子句以匹配数据内容。
dblink与外部数据包装器
dblink是一项负责在同构与异构数据库间实现通信的功能。截至本文撰写之时,Amazon RDS与Aurora PostgreSQL还不提供异构支持,但已经能够支持跨PostgreSQL数据库间的通信。
跨同构数据库通信
PostgreSQL可利用dblink与外部数据包装器(FDW)实现跨数据库间的正常通信。在本节中,我们将具体聊聊dblink与FDW的使用方法。
使用外部数据包装器
Create Extension postgres_fdw;
Create Server server_name1 Foreign Data Wrapper
postgres_fdw Options (host abcd.rds.amazonaws.com' dbname abcd, port '5432');
- 使用以下代码创建用户映射,借此访问外部数据库中的表。
Create User Mapping For Current_User
Server server_name1
Options (user 'pgar1234', password 'pgar1234');
- 将全部外部表导入本地schema,这样我们就能像访问常规表那样访问外部表中的数据。以下为从外部数据库及schema执行表导入的示例代码。
Create Schema imported_public2 -- created local schema
Import Foreign Schema public From Server server_name1
Into imported_public2; -- This will import all the tables
Select * From imported_public2.emptable
跨异构数据库通信
PostgreSQL不支持跨数据库通信。在实现跨数据库的异构通信方面,Amazon Aurora PostgreSQL确实存在一定局限,但大家可以在源环境(例如Oracle或者SQL Server)上建立指向目标(PostgreSQL)的dblink
,而后对数据执行pull或push操作。
若需了解更多详细信息,请参阅 在Compose PostgreSQL上进行跨数据库查询。
使用dblink为外部数据库表创建视图
dblink
属于一项PostgreSQLcontrib
扩展,可帮助用户在其他数据库中执行简短的即席查询。要使用dblink选项,大家必须以对其他用户可见的明文形式提供并保存密码。除非别无选择,否则我们不建议您使用这一选项。
若需了解更多详细信息,请参阅外部数据包装器与postgres_fdw说明文档。
选项一:在SQL语句之内提供目标数据库的访问细节
在这种选项中,每一次主机或连接的细节信息发生变化,我们都需要反复在主机连接与数据库凭证处做出多项对应调整。
Create Or Replace View emptable_dblink As
Select emptable.empid, emptable.last_name , emptable.first_name
From Dblink('host=abcd.rds.amazonaws.com user=abcd
password=abcd dbname=abcd port=5432',
Select empid,last_name,first_name FROM emptable')
AS emptable(empid Int,last_name Varchar , first_name Text );
Select * From emptable_dblink;
选项二:对访问细节进行拆分,并使用连接对象
在这种选项中,主机与连接细节在同一个位置进行定义,并使用连接名称实现跨数据库连接。
Select Dblink_Connect('conName','dbname=abcd user=abcd
password=abcd host= abcd.rds.amazonaws.com ');
Create Or Replace View mytabview1 As
Select mytable.* From
Dblink('conName', Select empid,last_name,first_name FROM emptable')
As mytable(empid Int,last_name Varchar , first_name Text);
Select * From mytabview1;
使用dblink进行函数调用
以下代码为来自外部PostgreSQL数据库的函数,运行后将返回一个整数。
Select * From
Dblink('host=abcd.rds.amazonaws.com user=abcd
password=abcd dbname=postgres port=5432',
'Select public.add_ten(10)')
As add_ten(a Int);
以下代码为来自外部PostgreSQL数据库的函数,运行后将返回一个表类型。
Select Dblink_Connect('conName','dbname=pgar1234 user=pgar1234
password=pgar1234 host=pgar1234.ctegx79rcs0q.ap-south-1.rds.amazonaws.com');
Select Dblink_Open('conName','foo2',
'Select * From public.tabletypetest(10)');
Select * From Dblink_Fetch('conName','foo2', 5) As (empid Int, last_name Varchar);
从一组数字中找出最大与最小值
在面向PostgreSQL进行迁移时,我们可能需要找出最大与最小值。PostgreSQL中包含一项最大/最小值查找函数,具体参见以下示例代码。
Select Greatest(1,2,3,50,100)
-> 100
Select Least(1,2,3,50,100)
-> 1
考虑使用自联接以实现更新
当在select语句中的from
子句内使用相同的源表(正在更新的表)时,PostgreSQL与SQL Server的具体更新机制将有所区别。与SQL Server不同,PostgreSQL中from
子句的第二次引用将独立于第一次引用,且变更将被应用于整个表。
以下示例代码,用于更新部门1中员工的薪水。
Update employee
Set salary = employee.salary + employee.salary * 0.10
From Employee e
Join dept d on d.deptid = e.deptid
此函数在SQL Server的起效方式并无区别;但在迁移之后,同一SQL语句的更新范围将由当前部门扩展至整个表。这是由于PostgreSQL会假定两个employee
表彼此独立,这与SQL Server完全不同。
要更新单一部门中的数据,应将DML转换为以下代码。
Update Employee e
Set salary = e.salary + e.salary * 0.10
From dept d
Where d.deptid = e.deptid
And d.deptid=1
如果使用Oracle,则将DML转换为以下代码。
Update Employee e
Set Salary = e.salary + e.salary * 0.10
Where Exists (Select 1 from dept d where d.deptid = e.deptid
And d.deptid=1 )
总结
本文从商业数据库到PostgreSQL的迁移场景出发,向开发者朋友们分享了一些技巧与最佳实践。本文的重点在于介绍迁移过程中需要面对的种种决策,以及决策结果给数据库性能造成怎样的影响。在迁移过程中,请牢记这些性能方面的影响因素,这将帮助大家提前避免随后可能因迁移出现的种种性能问题。
如果您对本文还有任何疑问或者建议,请在下方评论中分享您的看法。
本篇作者