Procedimentos armazenados e funções implementam a lógica de negócios do banco de dados. Ao migrar o banco de dados do SQL Server para o PostgreSQL, é necessário converter adequadamente os procedimentos armazenados e funções, prestando atenção ao manuseio de parâmetros, recuperação de conjuntos de linhas e outras construções de sintaxe específicas.
SQL Server utiliza um dialeto de SQL chamado Transact-SQL (ou T-SQL) para procedimentos armazenados e funções, enquanto o PostgreSQL utiliza Linguagem Procedural/PostgreSQL (ou PL/pgSQL) para o mesmo. Essas linguagens têm sintaxes e capacidades significativamente diferentes, portanto, os procedimentos armazenados e funções devem ser cuidadosamente analisados e convertidos.
Também, algumas funcionalidades do T-SQL não possuem equivalentes diretos no PL/pgSQL, e, portanto, é necessária uma implementação alternativa para esses casos. Por fim, os procedimentos armazenados e funções devem ser otimizados para o motor do PostgreSQL para garantir que sejam executados de forma eficiente.
Retornando um Conjunto de Linhas
O SQL Server e PostgreSQL permitem o retorno de um conjunto de linhas, geralmente o resultado de uma consulta SELECT
, de procedimentos armazenados ou funções, mas a sintaxe é distinta. Se o procedimento armazenado em T-SQL contiver SELECT
como a última instrução do corpo, isso significa que ele retorna um conjunto de linhas. O PL/pgSQL requer ou a declaração antecipada do conjunto de linhas retornado como uma tabela ou a recuperação de dados através de refcursor
.
Quando o conjunto de linhas retornado tem apenas algumas colunas com tipos claros, você pode usar o recurso RETURNS TABLE
do PostgreSQL.
No T-SQL:
CREATE PROCEDURE GetCustomerOrders @CustomerID INT
AS
SELECT OrderID, OrderDate, Amount FROM Orders WHERE CustomerID = @CustomerID;
GO
No PL/pgSQL, o mesmo pode parecer assim:
CREATE OR REPLACE FUNCTION GetCustomerOrders(CustomerID INT)
RETURNS TABLE(OrderID INT, OrderDate TIMESTAMP, Amount DECIMAL)
AS $$
BEGIN
RETURN QUERY SELECT OrderID, OrderDate, Amount FROM Orders WHERE CustomerID = GetCustomerOrders.CustomerID;
END;
$$ LANGUAGE plpgsql;
E o código chamador do PostgreSQL pode ser assim:
SELECT * FROM GetCustomerOrders(5);
Se o conjunto de linhas retornado for mais complicado e for difícil determinar o tipo de dados para cada coluna, a abordagem acima pode não funcionar. Para esses casos, a solução alternativa é usar refcursor
.
No T-SQL:
CREATE PROCEDURE GetSalesByRange
@DateFrom DATETIME,
@DateTo DATETIME
AS
SELECT C.CustomerID, C.Name AS CustomerName, C.FirstName, C.LastName,
C.Email AS CustomerEmail, C.Mobile, C.AddressOne, C.AddressTwo, C.City,
C.ZipCode, CY.Name AS Country, ST.TicketID, TT.TicketTypeID,
TT.Name AS TicketType, PZ.PriceZoneID, PZ.Name AS PriceZone,
ST.FinalPrice AS Price, ST.Created, ST.TransactionType,
COALESCE(VME.ExternalEventID, IIF(E.ExternalID = '', NULL, E.ExternalID), '0') AS ExternalID,
E.EventID, ES.[Name] AS Section, ST.RowName, ST.SeatName
FROM [Event] E WITH (NOLOCK)
INNER JOIN EventCache EC WITH (NOLOCK) ON E.EventID = EC.EventID
INNER JOIN SaleTicket ST WITH (NOLOCK) ON E.EventID = ST.EventID
INNER JOIN EventSection ES WITH (NOLOCK) ON ST.EventSectionID = ES.EventSectionID
INNER JOIN Customer C WITH (NOLOCK) ON ST.CustomerID = C.CustomerID
INNER JOIN Country CY WITH (NOLOCK) ON C.CountryID = CY.CountryID
INNER JOIN TicketType TT WITH (NOLOCK) ON ST.TicketTypeID = TT.TicketTypeID
INNER JOIN PriceZone PZ WITH (NOLOCK) ON ST.PriceZoneID = PZ.PriceZoneID
LEFT OUTER JOIN VenueManagementEvent VME ON VME.EventID = E.EventID
WHERE ST.Created BETWEEN @DateFrom AND @DateTo
ORDER BY ST.Created
GO
No PL/pgSQL:
CREATE OR REPLACE FUNCTION GetSalesByRange (
V_DateFrom TIMESTAMP(3),
V_DateTo TIMESTAMP(3),
V_rc refcursor
)
RETURNS refcursor
AS $$
BEGIN
OPEN V_rc FOR
SELECT C.CustomerID, C.Name AS CustomerName, C.FirstName, C.LastName,
C.Email AS CustomerEmail, C.Mobile, C.AddressOne, C.AddressTwo, C.City,
C.ZipCode, CY.Name AS Country, ST.TicketID, TT.TicketTypeID,
TT.Name AS TicketType, PZ.PriceZoneID, PZ.Name AS PriceZone,
ST.FinalPrice AS Price, ST.Created, ST.TransactionType,
COALESCE(
VME.ExternalEventID,
(CASE WHEN E.ExternalID = '' THEN NULL ELSE E.ExternalID END),
'0') AS ExternalID,
E.EventID, ES.Name AS Section, ST.RowName, ST.SeatName
FROM Event E
INNER JOIN EventCache EC ON E.EventID = EC.EventID
INNER JOIN SaleTicket ST ON E.EventID = ST.EventID
INNER JOIN EventSection ES ON ST.EventSectionID = ES.EventSectionID
INNER JOIN Customer C ON ST.CustomerID = C.CustomerID
INNER JOIN Country CY ON C.CountryID = CY.CountryID
INNER JOIN TicketType TT ON ST.TicketTypeID = TT.TicketTypeID
INNER JOIN PriceZone PZ ON ST.PriceZoneID = PZ.PriceZoneID
LEFT OUTER JOIN VenueManagementEvent VME ON VME.EventID = E.EventID
WHERE ST.Created BETWEEN V_DateFrom AND V_DateTo
ORDER BY ST.Created;
RETURN V_rc;
END;
$$ LANGUAGE plpgsql;
E o código chamador do PostgreSQL pode ser assim:
BEGIN;
SELECT GetSalesByRange(
'2024-01-01'::TIMESTAMP(3),
'2025-01-01'::TIMESTAMP(3),
'mycursorname'
);
FETCH 4 FROM mycursorname;
COMMIT;
Declaração de Variáveis Locais
O T-SQL permite que variáveis locais sejam declaradas em qualquer lugar dentro do corpo de um procedimento armazenado ou função. O PL/pgSQL requer que todas as variáveis locais sejam declaradas antes da palavra-chave BEGIN
:
CREATE OR REPLACE FUNCTION CreateEvent(…)
AS $$
DECLARE
v_EventID INT;
v_EventGroupID INT;
BEGIN
…
END;
$$ LANGUAGE plpgsql;
No SQL Server, variáveis de tabela podem ser declaradas da seguinte forma:
DECLARE @Products TABLE
(
ProductID int,
ProductTitle varchar(100),
ProductPrice decimal (8,2)
)
O PostgreSQL não suporta esse recurso; em vez disso, devem ser usadas tabelas temporárias:
CREATE TEMP TABLE Products
(
ProductID int,
ProductTitle varchar(100),
ProductPrice decimal (8,2)
)
Lembre-se de que as tabelas temporárias são automaticamente excluídas no final da sessão ou da transação atual. Se você precisar gerenciar explicitamente o tempo de vida da tabela, use a instrução DROP TABLE IF EXISTS
.
Preste atenção à apropriada conversão de tipos de SQL Server para PostgreSQL quando declarando variáveis.
Último Valor da Coluna de Auto-Incremento
Após executar a consulta INSERT
, você pode precisar obter o valor gerado da coluna de auto-incremento. No T-SQL, isso pode ser obtido como
CREATE TABLE aitest (id int identity, val varchar(20));
INSERT INTO aitest(val) VALUES ('one'),('two'),('three');
SELECT @LastID = SCOPE_IDENTITY();
O PostgreSQL permite acesso ao último valor inserido por meio de uma sequência automaticamente gerada que sempre tem o nome {nomedatabela}_{nomedacoluna}_seq
:
CREATE TABLE aitest (id serial, val varchar(20));
INSERT INTO aitest(val) VALUES ('one'),('two'),('three');
LastID := currval('aitest_id_seq’);
Funções Internas
Ao migrar procedimentos armazenados e funções do SQL Server para o PostgreSQL, todas as funções internas específicas e operadores devem ser convertidos em equivalentes de acordo com as regras abaixo:
- A função
CHARINDEX
deve ser substituída pelo equivalente do PostgreSQLPOSITION
- A função
CONVERT
deve ser migrada para o PostgreSQL de acordo com as regras especificadas neste artigo - A função
DATEADD($interval, $n_units, $date)
pode ser convertida em expressões PostgreSQL que utilizam o operador+
dependendo do valor de $interval da seguinte forma:DAY / DD / D / DAYOFYEAR / DY
($date + $n_units * interval ‘1 day’)::date HOUR / HH ($date + $n_units * interval ‘1 hour’)::date
MINUTE / MI / N ($date + $n_units * interval ‘1 minute’)::date
MONTH / MM / M ($date + $n_units * interval ‘1 month’)::date
QUARTER / QQ / Q ($date + $n_units * 3 * interval ‘1 month’)::date
SECOND / SS / S ($date + $n_units * interval ‘1 second’)::date
WEEK / WW / WK ($date + $n_units * interval ‘1 week’)::date
WEEKDAY / DW / W ($date + $n_units * interval ‘1 day’)::date
YEAR / YY ($date + $n_units * interval ‘1 year’)::date
- A função
DATEDIFF($interval, $data1, $data2)
do SQL Server pode ser emulado no PostgreSQL viaDATE_PART
da seguinte forma:DAY / DD / D / DAYOFYEAR / DY date_part(‘day’, $data2 – $data1)::int
HOUR / HH 24 * date_part(‘day’, $data2 – $data1)::int + date_part(‘hour’, $data2 – $data1) MINUTE / MI / N 1440 * date_part(‘day’, $data2 – $data1)::int + 60 * date_part(‘hour’, $data2 – $data1) + date_part(‘minute’, $data2 – $data1) MONTH / MM / M (12 * (date_part(‘year’, $data2) – date_part(‘year’, $data1))::int + date_part(‘month’, $data2) – date_part(‘month’, $data1))::int SECOND / SS / S 86400 * date_part(‘day’, $data2 – $data1)::int + 3600 * date_part(‘hour’, $data2 – $data1) + 60 * date_part(‘minute’, $data2 – $data1) + date_part(‘second’, $data2 – $data1) WEEK / WW / WK TRUNC(date_part(‘day’, $data2 – $data1) / 7) WEEKDAY / DW / W date_part(‘day’, $data2 – $data1)::int YEAR / YY (date_part(‘year’, $data2) – date_part(‘year’, $data1))::int - Cada ocorrência de
DATEPART
deve ser substituída porDATE_PART
- A função do SQL Server
GETDATE
deve ser convertida para PostgreSQLNOW()
- O operador condicional
IIF($condition, $first, $second)
deve ser convertido paraCASE WHEN $condition THEN $first ELSE $second END
- Cada ocorrência de
ISNULL
deve ser substituída porCOALESCE
- A função SQL Server
REPLICATE
deve ser convertida para o equivalente em PostgreSQL,REPEAT
- Cada ocorrência de
SPACE($n)
deve ser substituída porREPEAT(' ', $n)
Conclusão
A migração de stored procedures e funções entre dois SGDBs é um procedimento bastante complicado que requer muito tempo e esforço. Embora não possa ser completamente automatizada, algumas ferramentas disponíveis online poderiam ajudar a automatizar parcialmente o procedimento.
Source:
https://dzone.com/articles/convert-stored-procedures-sql-server-to-postgresql