Procedimentos e funções armazenados estão implementando a lógica de negócios do banco de dados. Ao migrar o banco de dados do SQL Server para o PostgreSQL, será necessário converter os procedimentos e funções armazenados adequadamente, prestando atenção ao tratamento de parâmetros, recuperação de conjuntos de linhas e outras construções de sintaxe específicas.
SQL Server usa um dialeto SQL chamado Transact-SQL (ou T-SQL) para procedimentos e funções armazenados, enquanto o PostgreSQL usa Linguagem Procedural/PostgreSQL (ou PL/pgSQL) para o mesmo. Essas linguagens têm sintaxe e capacidades significativamente diferentes, portanto, procedimentos e funções armazenados devem ser cuidadosamente analisados e convertidos.
Além disso, alguns recursos do T-SQL não têm equivalentes diretos no PL/pgSQL e, portanto, é necessária uma implementação alternativa para esses casos. Por fim, os procedimentos e funções armazenados devem ser otimizados para o mecanismo do PostgreSQL para garantir que funcionem 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. 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 PostgreSQL pode parecer assim:
SELECT * FROM GetCustomerOrders(5);
Se o conjunto de linhas retornado for mais complicado e for difícil determinar o tipo de dados de 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 PostgreSQL pode parecer 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. 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, as 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; tabelas temporárias devem ser usadas em seu lugar:
CREATE TEMP TABLE Products
(
ProductID int,
ProductTitle varchar(100),
ProductPrice decimal (8,2)
)
Lembre-se de que as tabelas temporárias são automaticamente removidas no final da sessão ou da transação atual. Se precisar gerenciar explicitamente a vida útil da tabela, use a instrução DROP TABLE IF EXISTS
.
Preste atenção na adequada conversão de tipos de SQL Server para PostgreSQL ao declarar as variáveis.
Último Valor da Coluna de Autoincremento
Após executar a consulta INSERT
, talvez seja necessário obter o valor gerado da coluna de autoincremento. 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 o 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 e funções armazenadas de SQL Server para 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 no PostgreSQLPOSITION
- Função
CONVERT
deve ser migrada para o PostgreSQL de acordo com as regras especificadas neste artigo - Função
DATEADD($interval, $n_units, $date)
pode ser convertida em expressões PostgreSQL que usam o operador+
dependendo do valor de$interval
como segue:DIA / DD / D / DIA_DO_ANO / DY
($date + $n_units * interval ‘1 day’)::date
HORA / HH ($date + $n_units * interval ‘1 hour’)::date MINUTO / MI / N ($date + $n_units * interval ‘1 minute’)::date MÊS / MM / M ($date + $n_units * interval ‘1 month’)::date TRIMESTRE / QQ / Q ($date + $n_units * 3 * interval ‘1 month’)::date SEGUNDO / SS / S ($date + $n_units * interval ‘1 second’)::date SEMANA / WW / WK ($date + $n_units * interval ‘1 week’)::date DIA_DA_SEMANA / DW / W ($date + $n_units * interval ‘1 day’)::date ANO / YY ($date + $n_units * interval ‘1 year’)::date
- Função
DATEDIFF($interval, $date1, $date2)
do SQL Server pode ser emulado no PostgreSQL viaDATE_PART
da seguinte forma:DIA / DD / D / DIA_DO_ANO / DY date_part(‘day’, $date2 – $date1)::int
HORA / HH 24 * date_part(‘day’, $date2 – $date1)::int + date_part(‘hour’, $date2 – $date1) MINUTO / MI / N 1440 * date_part(‘day’, $date2 – $date1)::int + 60 * date_part(‘hour’, $date2 – $date1) + date_part(‘minute’, $date2 – $date1) MÊS / MM / M (12 * (date_part(‘year’, $date2) – date_part(‘year’, $date1))::int + date_part(‘month’, $date2) – date_part(‘month’, $date1))::int SEGUNDO / SS / S 86400 * date_part(‘day’, $date2 – $date1)::int + 3600 * date_part(‘hour’, $date2 – $date1) + 60 * date_part(‘minute’, $date2 – $date1) + date_part(‘second’, $date2 – $date1) SEMANA / WW / WK TRUNC(date_part(‘day’, $date2 – $date1) / 7) DIA_DA_SEMANA / DW / W date_part(‘day’, $date2 – $date1)::int ANO / YY (date_part(‘year’, $date2) – date_part(‘year’, $date1))::int - Cada ocorrência de
DATEPART
deve ser substituída porDATE_PART
- A função do SQL Server
GETDATE
deve ser convertida em PostgreSQLNOW()
- O operador condicional
IIF($condition, $first, $second)
deve ser convertido emCASE 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 procedimentos armazenados e funções entre dois SGBDs é um procedimento bastante complicado que requer muito tempo e esforço. Embora não possa ser totalmente automatizado, algumas ferramentas disponíveis online podem ajudar a automatizar parcialmente o procedimento.
Source:
https://dzone.com/articles/convert-stored-procedures-sql-server-to-postgresql