Opgeslagen procedures en functies implementeren de bedrijfslogica van de database. Bij het migreren van de SQL Server-database naar PostgreSQL, moet u de opgeslagen procedures en functies op de juiste manier converteren, waarbij u let op de verwerking van parameters, het ophalen van rijensets en andere specifieke syntaxisconstructies.
SQL Server gebruikt een dialect van SQL genaamd Transact-SQL (of T-SQL) voor opgeslagen procedures en functies, terwijl PostgreSQL Procedural Language/PostgreSQL (of PL/pgSQL) gebruikt voor hetzelfde. Deze talen hebben aanzienlijk verschillende syntaxis en mogelijkheden, dus opgeslagen procedures en functies moeten zorgvuldig worden geanalyseerd en geconverteerd.
Daarnaast hebben sommige T-SQL-functies geen directe equivalenten in PL/pgSQL, en daarom is een alternatieve implementatie vereist voor die gevallen. Tenslotte moeten opgeslagen procedures en functies geoptimaliseerd worden voor de PostgreSQL-engine om ervoor te zorgen dat ze efficiënt presteren.
Het retourneren van een rijenset
Zowel SQL Server als PostgreSQL staan de terugkeer van een rijset toe, meestal het resultaat van een SELECT
query, vanuit opgeslagen procedures of functies, maar de syntaxis is verschillend. Als de opgeslagen procedure in T-SQL SELECT
als de laatste verklaring van de body bevat, betekent dit dat het een rijset retourneert. PL/pgSQL vereist ofwel een vooruitlopende declaratie van de geretourneerde rijset als een tabel of het ophalen van gegevens via refcursor
.
Wanneer de geretourneerde rijset slechts een paar kolommen met duidelijke types heeft, kun je de RETURNS TABLE
functie van PostgreSQL gebruiken.
In T-SQL:
CREATE PROCEDURE GetCustomerOrders @CustomerID INT
AS
SELECT OrderID, OrderDate, Amount FROM Orders WHERE CustomerID = @CustomerID;
GO
In PL/pgSQL kan het er als volgt uitzien:
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;
En de aanroepende PostgreSQL-code kan er als volgt uitzien:
SELECT * FROM GetCustomerOrders(5);
Als de geretourneerde rijset ingewikkelder is en het moeilijk is om het datatype voor elke kolom te bepalen, werkt de bovenstaande aanpak mogelijk niet. Voor die gevallen is de oplossing om refcursor
te gebruiken.
In 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
In 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;
En de aanroepende PostgreSQL-code kan er als volgt uitzien:
BEGIN;
SELECT GetSalesByRange(
'2024-01-01'::TIMESTAMP(3),
'2025-01-01'::TIMESTAMP(3),
'mycursorname'
);
FETCH 4 FROM mycursorname;
COMMIT;
Declaratie van Lokale Variabelen
T-SQL staat toe dat lokale variabelen overal binnen een opgeslagen procedure of functiebody worden gedeclareerd. PL/pgSQL vereist dat alle lokale variabelen worden gedeclareerd vóór het BEGIN
sleutelwoord:
CREATE OR REPLACE FUNCTION CreateEvent(…)
AS $$
DECLARE
v_EventID INT;
v_EventGroupID INT;
BEGIN
…
END;
$$ LANGUAGE plpgsql;
In SQL Server kunnen tabelvariabelen als volgt worden gedeclareerd:
DECLARE @Products TABLE
(
ProductID int,
ProductTitle varchar(100),
ProductPrice decimal (8,2)
)
PostgreSQL ondersteunt deze functie niet; in plaats daarvan moeten tijdelijke tabellen worden gebruikt:
CREATE TEMP TABLE Products
(
ProductID int,
ProductTitle varchar(100),
ProductPrice decimal (8,2)
)
Onthoud dat tijdelijke tabellen automatisch worden verwijderd aan het einde van de sessie of de huidige transactie. Als je de levensduur van de tabel expliciet moet beheren, gebruik dan de DROP TABLE IF EXISTS
-verklaring.
Let op het juiste SQL Server naar PostgreSQL typen mapping bij het converteren van variabelen declaratie.
Laatste waarde van Auto-Increment kolom
Na het uitvoeren van een INSERT
-query, moet je mogelijk de gegenereerde waarde van de auto-increment kolom ophalen. In T-SQL kan dit worden verkregen als
CREATE TABLE aitest (id int identity, val varchar(20));
INSERT INTO aitest(val) VALUES ('one'),('two'),('three');
SELECT @LastID = SCOPE_IDENTITY();
PostgreSQL staat toegang toe tot de laatst ingevoegde waarde via een automatisch gegenereerde sequentie die altijd de naam heeft {tabelnaam}_{kolomnaam}_seq
:
CREATE TABLE aitest (id serial, val varchar(20));
INSERT INTO aitest(val) VALUES ('one'),('two'),('three');
LastID := currval('aitest_id_seq’);
Ingebouwde Functies
Bij het migreren van opgeslagen procedures en functies van SQL Server naar PostgreSQL moeten alle specifieke ingebouwde functies en operatoren worden omgezet in equivalenten volgens de onderstaande regels:
- Functie
CHARINDEX
moet worden vervangen door het PostgreSQL-equivalentPOSITION
- Functie
CONVERT
moet worden gemigreerd naar PostgreSQL volgens de regels zoals gespecificeerd in dit artikel - Functie
DATEADD($interval, $n_units, $date)
kan worden omgezet in PostgreSQL-expressies die de operator+
gebruiken, afhankelijk van de waarde van$interval
als volgt:DAG / DD / D / DAGVJ / DY
($date + $n_units * interval ‘1 dag’)::date
UUR / HH ($date + $n_units * interval ‘1 uur’)::date MINUUT / MI / N ($date + $n_units * interval ‘1 minuut’)::date MAAND / MM / M ($date + $n_units * interval ‘1 maand’)::date KWARTAAL / QQ / Q ($date + $n_units * 3 * interval ‘1 maand’)::date SECONDE / SS / S ($date + $n_units * interval ‘1 seconde’)::date WEEK / WW / WK ($date + $n_units * interval ‘1 week’)::date WEEKDAG / DW / W ($date + $n_units * interval ‘1 dag’)::date JAAR / YY ($date + $n_units * interval ‘1 jaar’)::date
- Functie
DATEDIFF($interval, $date1, $date2)
van SQL Server kan worden geëmuleerd in PostgreSQL viaDATE_PART
als volgt:DAG / DD / D / DAGVANHETJAAR / DY date_part(‘day’, $date2 – $date1)::int
UUR / HH 24 * date_part(‘day’, $date2 – $date1)::int + date_part(‘hour’, $date2 – $date1) MINUUT / MI / N 1440 * date_part(‘day’, $date2 – $date1)::int + 60 * date_part(‘hour’, $date2 – $date1) + date_part(‘minute’, $date2 – $date1) MAAND / MM / M (12 * (date_part(‘year’, $date2) – date_part(‘year’, $date1))::int + date_part(‘month’, $date2) – date_part(‘month’, $date1))::int SECONDE / 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) WEEK / WW / WK TRUNC(date_part(‘day’, $date2 – $date1) / 7) WEEKDAG / DW / W date_part(‘day’, $date2 – $date1)::int JAAR / YY (date_part(‘year’, $date2) – date_part(‘year’, $date1))::int - Elk voorkomen van
DATEPART
moet vervangen worden doorDATE_PART
- SQL Server functie
GETDATE
moet worden geconverteerd naar PostgreSQLNOW()
- Conditionele operator
IIF($condition, $first, $second)
moet worden geconverteerd naarCASE WHEN $condition THEN $first ELSE $second END
- Elke voorkomen van
ISNULL
moet worden vervangen doorCOALESCE
- De SQL Server-functie
REPLICATE
moet worden geconverteerd naar de PostgreSQL-equivalent,REPEAT
- Elke voorkomen van
SPACE($n)
moet worden vervangen doorREPEAT(' ', $n)
Conclusie
De migratie van opgeslagen procedures en functies tussen twee DBMS’en is een vrij gecompliceerde procedure die veel tijd en moeite vereist. Hoewel het niet volledig geautomatiseerd kan worden, kunnen sommige beschikbare online tools helpen bij het gedeeltelijk automatiseren van de procedure.
Source:
https://dzone.com/articles/convert-stored-procedures-sql-server-to-postgresql