Data: 1.01.2026

Tworzenie funkcji skalarnych (UDF) w SQL Server

Tworzenie funkcji skalarnych (UDF) w SQL Server

Spis treści

W tym artykule wyjaśnię, jak pisać skalarne funkcje użytkownika (UDF) w SQL Server. Pokażę Ci przykłady własnych funkcji, za pomocą których usprawnisz pracę z danymi.

Tworzenie funkcji skalarnych (UDF) w SQL Server

Skalarne funkcje użytkownika (ang. Scalar-valued Functions) to najprostszy typ funkcji, który dla wprowadzonych parametrów (lub bez nich) zwraca pojedynczą wartość określonego typu danych. Funkcja może zwracać np.

  • liczbę (INT, DECIMAL)
  • ciąg znaków (VARCHAR, CHAR)
  • datę (DATE, DATETIME)

Funkcje skalarne działają podobnie do wbudowanych funkcji systemowych, takich jak UPPER(), LEN() czy ROUND(). Możesz ich używać w każdym miejscu zapytania SQL, gdzie oczekiwana jest pojedyncza wartość np.:

  • na liście kolumn w instrukcji SELECT
  • w warunkach filtrowania w klauzuli WHERE
  • wewnątrz instrukcji CASE
  • w definicjach kolumn obliczanych

Czym jest funkcja użytkownika w SQL Server

Funkcja użytkownika (User Defined Function) w SQL Server to obiekt bazy danych, który umożliwia zdefiniowanie własnej logiki obliczeniowej i jej wielokrotne wykorzystanie w zapytaniach SQL.

Funkcja może:

  • przyjmować parametry wejściowe
  • wykonywać określone operacje na danych
  • zwracać wynik w postaci pojedynczej wartości (wartość skalarna) lub zestawu danych (tabela).

Dzięki temu te same obliczenia nie muszą być za każdym razem zapisywane od nowa w różnych miejscach kodu.

Tworzenie funkcji skalarnych (UDF) w SQL Server pozwoli Ci uniknąć wielokrotnego kopiowania tych samych fragmentów kodu. Wewnątrz funkcji możesz zamknąć złożoną logikę biznesową lub skomplikowane obliczenia, a następnie wywoływać je w różnych miejscach zapytania.

Główne zapytania stają się krótsze i bardziej czytelne, gdyż skomplikowane operacje ukryte są pod jedną nazwą funkcji. Dodatkowo, takie podejście ułatwia utrzymanie bazy danych. Jeśli zajdzie potrzeba zmiany algorytmu, zmodyfikujesz go tylko w jednym miejscu (w funkcji), a zmiana będzie widoczna we wszystkich skryptach, które z tej funkcji korzystają.

Rodzaje funkcji użytkownika w SQL Server

Ze względu na rodzaj zwracanego wyniku funkcje użytkownika dzielimy na:

  • funkcje skalarne – zwracają pojedynczą wartość zgodną z typem zdefiniowanym w klauzuli RETURNS
  • funkcje tabelaryczne – w wyniku działania zwracają tabelę. Możemy ich używać bezpośrednio w klauzuli FROM.

Informacje na temat funkcji tabelarycznych znajdziesz w artykule Tworzenie funkcji tabelarycznych w SQL Server

Składnia i tworzenie funkcji skalarnej w SQL Server

Aby utworzyć funkcję skalarną musimy:

  • określić typ zwracanych danych w klauzuli RETURNS
  • podać w ciele funkcji pomiędzy BEGINEND jakie obliczenia będą wykonywane
  • zwrócić wynik za pomocą instrukcji RETURN

Przykład funkcji skalarnej, która oblicza cenę brutto

-- 1. Nazwa funkcji (razem ze schematem np. dbo)
CREATE FUNCTION [dbo].[ObliczBrutto]
(
-- 2. Argument (Parametry wejściowe)
-- Nazwa parametru | Typ danych
@CenaNetto DECIMAL(10,2),
@StawkaVat DECIMAL(4,2)
)
-- 3. Typ wyniku (co funkcja zwróci)
RETURNS DECIMAL(10,2)
AS
BEGIN
-- 4. Logika (ciało funkcji)
DECLARE @Wynik DECIMAL(10,2);
SET @Wynik = @CenaNetto * (1 + @StawkaVat);
-- 5. Wynik (zwracana przez funkcję wartość)
RETURN @Wynik;
END
GO

Wyjaśnienie:

  • CREATE FUNCTION dbo.ObliczBrutto – tworzymy funkcję w schemacie dbo i nadajemy jej nazwę „ObliczBrutto”. Podczas tworzenia funkcji nie musimy podawać schematu, ale przy wywoływaniu funkcji podanie schematu jest niezbędne.
  • podajemy argumenty (parametry wejściowe) – „@CenaNetto” – to nazwa parametru, a „DECIMAL(10,2)” to typ danych. Znak małpy (@) umieszczony przed nazwą oznacza, że mamy do czynienia ze zmienną lokalną lub parametrem
  • RETURNS DECIMAL(10,2) – określamy, jaki typ danych funkcja zwróci
  • AS BEGIN ... END – definiuje ciało funkcji, w którym zawarta jest cała logika działania funkcji. Wewnątrz:
    • deklarujemy zmienną pomocniczą o nazwie „Wynik” (DECLARE @Wynik)
    • wykonujemy obliczenia i przypisujemy wynik obliczeń do zmiennej pomocniczej (SET)
    • za pomocą instrukcji RETURN zwracamy wartość wyliczeń jako wynik działania funkcji.

Folder w SSMS przechowujący funkcje skalarne

Funkcje skalarne widoczne są w SQL Server Management Studio w folderze Scalar-valued Functions:

Title

Funkcja skalarna w SELECT – obliczanie ceny brutto

Aby wywołać funkcję skalarną w klauzuli SELECT wpisujemy nazwę funkcji oraz parametry. W przykładzie poniżej jako parametry podajemy wartość z kolumny Freight w tabeli Orders oraz „0.23” jako wartość podatku VAT. Funkcja przetwarza dane z każdego wiersza i wyświetla wynik w nowej kolumnie.

SELECT
OrderID,
CustomerID,
Freight AS CenaNetto,
-- Wywołanie funkcji
dbo.ObliczBrutto(Freight, 0.23) AS FreightBrutto
FROM Orders;

Wynik zapytania:

OrderIDCustomerIDCenaNettoFreightBrutto
10248VINET32,3839.83
10249TOMSP11,6114.28
10250HANAR65,8380.97
10251VICTE41,3450.85

Funkcja skalarna w SELECT – obliczanie dni roboczych

W poniższym przykładzie tworzymy funkcję, za pomocą której wyliczmy dni robocze od poniedziałku do piątku między dwiema datami.

CREATE FUNCTION dbo.DniRobocze
(
@DataOd DATE,
@DataDo DATE
)
RETURNS INT
AS
BEGIN
DECLARE @Licznik INT = 0;
-- zamieniamy daty jeśli ktoś poda odwrotnie
IF @DataOd > @DataDo
BEGIN
DECLARE @Tmp DATE = @DataOd;
SET @DataOd = @DataDo;
SET @DataDo = @Tmp;
END;
WHILE @DataOd <= @DataDo
BEGIN
-- 1 = niedziela, 7 = sobota (przy domyślnym DATEFIRST)
IF DATEPART(WEEKDAY, @DataOd) NOT IN (1, 7)
SET @Licznik += 1;
SET @DataOd = DATEADD(DAY, 1, @DataOd);
END;
RETURN @Licznik;
END;
GO

Wyjaśnienie funkcji:

  • tworzymy w schemacie dbo funkcję o nazwie DniRobocze

  • funkcja przyjmuje dwa parametry typu DATE – @DataOd – data początkowa i @DataDo – data końcowa

  • funkcja zwraca wartość typu INT – liczbę dni roboczych

  • zmienna @Licznik przechowuje liczbę dni roboczych. Początkowa wartość to 0

  • w bloku

    IF @DataOd > @DataDo
    BEGIN
    DECLARE @Tmp DATE = @DataOd;
    SET @DataOd = @DataDo;
    SET @DataDo = @Tmp;
    END;

    funkcja sprawdza, czy użytkownik nie podał dat w złej kolejności. Jeśli @DataOd jest późniejsza niż @DataDo, daty są zamieniane miejscami za pomocą zmiennej tymczasowej @Tmp

  • w bloku

    WHILE @DataOd <= @DataDo
    BEGIN
    -- 1 = niedziela, 7 = sobota (przy domyślnym DATEFIRST)
    IF DATEPART(WEEKDAY, @DataOd) NOT IN (1, 7)
    SET @Licznik += 1;
    SET @DataOd = DATEADD(DAY, 1, @DataOd);
    END;

    pętla sprawdza aktualną datę (@DataOd), ustala, czy data to dzień roboczy od poniedziałku do piątku. Jeśli data to dzień roboczy zwiększana jest wartość w zmiennej @Licznik. Pętla powtarza się dla każdego wiersza aż do daty w zmiennej @DataDo. Na końcu zawsze przesuwa datę o 1 dzień RETURN zwraca wynik znajdujący się w zmiennej @Licznik

Sprawdzenie działania funkcji:

SELECT dbo.DniRobocze('20260101','20260109')
--Wynik: 7

Funkcja skalarna w klauzuli SELECT – obliczanie dni roboczych z wykluczeniem świąt

W praktyce liczą się dni robocze – z pominięciem weekendów oraz świąt ustawowo wolnych od pracy. To szczególnie istotne przy wyliczaniu terminów realizacji, czasów obsługi zgłoszeń (SLA), harmonogramów projektów czy analiz kadrowych.

W poniższym przykładzie pokazuję, w jaki sposób wyliczyć dni robocze z pominięciem świąt oraz dni ustawowo wolnych od pracy. Funkcja przyjmuje zakres dat i zwraca liczbę dni roboczych, automatycznie pomijając soboty i niedziele oraz daty zapisane w tabeli ze świętami.

Zastosowanie osobnej tabeli na święta pozwala zarządzać kalendarzem – dodanie nowego dnia wolnego nie wymaga zmiany kodu funkcji, a jedynie uzupełnienia danych w tabeli świąt.

--Tworzenie tabeli ze świętami
CREATE TABLE dbo.Swieta
(
DataSwieta DATE PRIMARY KEY,
Opis NVARCHAR(100)
);
Uzupełnianie świąt w tabeli
INSERT INTO dbo.Swieta (DataSwieta, Opis)
VALUES
('2026-01-01', 'Nowy Rok'),
('2026-01-06', 'Trzech Króli');
CREATE FUNCTION dbo.DniRoboczePomSwieta
(
@DataOd DATE,
@DataDo DATE
)
RETURNS INT
AS
BEGIN
DECLARE @Licznik INT = 0;
-- zamiana dat jeśli zostaną podane odwrotnie
IF @DataOd > @DataDo
BEGIN
DECLARE @Tmp DATE = @DataOd;
SET @DataOd = @DataDo;
SET @DataDo = @Tmp;
END;
WHILE @DataOd <= @DataDo
BEGIN
-- pon–pt
IF DATEPART(WEEKDAY, @DataOd) NOT IN (1, 7)
AND NOT EXISTS (
SELECT *
FROM dbo.Swieta s
WHERE s.DataSwieta = @DataOd
)
BEGIN
SET @Licznik += 1;
END;
SET @DataOd = DATEADD(DAY, 1, @DataOd);
END;
RETURN @Licznik;
END;
GO

Wyjaśnienie przykładu:

  • poleceniem CREATE TABLE tworzymy nową tabelę o nazwie „Swieta” w schemacie dbo

  • poleceniem INSERT INTO wstawiamy do tabeli „Swieta” dwie daty

  • tworzymy funkcję o nazwie DniRoboczePomSwieta w schemacie dbo

  • funkcja przyjmuje dwa parametry typu DATE – @DataOd – data początkowa i @DataDo – data końcowa

  • funkcja zwraca wartość typu INT – liczbę dni roboczych

  • zmienna @Licznik przechowuje liczbę dni roboczych. Początkowa wartość to 0

  • w bloku

    IF @DataOd > @DataDo
    BEGIN
    DECLARE @Tmp DATE = @DataOd;
    SET @DataOd = @DataDo;
    SET @DataDo = @Tmp;
    END;

    funkcja sprawdza, czy użytkownik nie podał dat w złej kolejności. Jeśli @DataOd jest późniejsza niż @DataDo, daty są zamieniane miejscami za pomocą zmiennej tymczasowej @Tmp

  • w bloku

    WHILE @DataOd <= @DataDo
    BEGIN
    -- pon–pt
    IF DATEPART(WEEKDAY, @DataOd) NOT IN (1, 7)
    AND NOT EXISTS (
    SELECT *
    FROM dbo.Swieta s
    WHERE s.DataSwieta = @DataOd
    )
    BEGIN
    SET @Licznik += 1;
    END;
    SET @DataOd = DATEADD(DAY, 1, @DataOd);
    END;

    pętla WHILE

    • przechodzi dzień po dniu przez wskazany zakres dat i działa tak długo, jak długo zmienna @DataOd jest mniejsza lub równa @DataDo
    • dla każdej kolejnej daty sprawdza, czy dany dzień przypada od poniedziałku do piątku oraz czy nie występuje w tabeli Swieta jako dzień świąteczny. Jeśli obydwa warunki są spełnione, zwiększana jest wartość licznika zmiennej @Licznik, co oznacza zliczenie dnia roboczego.
    • na końcu każdej iteracji pętli data początkowa jest przesuwana o jeden dzień do przodu za pomocą funkcji DATEADD(), co pozwala pętli przejść do sprawdzenia kolejnego dnia

Wykorzystywanie skalarnych funkcji użytkownika w zapytaniach SQL

Funkcje skalarne możemy stosować m.in. w klauzulach SELECT, WHERE, ORDER BY, GROUP BY, HAVING, JOIN, a także wewnątrz wyrażeń CASE i definicji kolumn obliczeniowych.

Poniżej przykładowa funkcja oraz jej wykorzystanie w klauzulach SQL

W przykładzie tworzymy funkcję, która przelicza cenę netto na cenę brutto przy założeniu 23-procentowej stawki VAT.

CREATE FUNCTION dbo.CalculateGrossPrice (@NetPrice MONEY)
RETURNS MONEY
AS
BEGIN
-- Zwracamy cenę powiększoną o 23% VAT
RETURN ISNULL(@NetPrice, 0) * 1.23
END;
GO

Funkcja przyjmuje jeden argument o nazwie @NetPrice typu MONEY i zwraca pojedynczą wartość typu MONEY, która stanowi finalną cenę brutto.

Operacje wewnątrz bloku BEGIN ... END:

  • ISNULL(@NetPrice, 0) – jeśli ktoś przekaże do funkcji wartość pustą (NULL), funkcja zamieni ją na 0. Zapobiega to błędom w obliczeniach (wynikiem mnożenia NULL przez cokolwiek zawsze byłby NULL)
  • mnożenie przez 1.23 – funkcja mnoży wartość netto przez 1,23, co matematycznie odpowiada powiększeniu wartości netto o 23%

Funkcje skalarne w klauzuli SELECT

W zapytaniu pobieramy nazwy produktów oraz ich ceny netto. Następnie za pomocą funkcji użytkownika o nazwie dbo.CalculateGrossPrice zwracamy odpowiadające im ceny brutto.

SELECT
ProductName,
UnitPrice AS Netto,
dbo.CalculateGrossPrice(UnitPrice) AS Brutto
FROM Products;

Wynik zapytania:

ProductNameNettoBrutto
Chai18,0022,14
Chang19,0023,37
Aniseed Syrup10,0012,30
Chef Anton’s Cajun Seasoning22,0027,06
Chef Anton’s Gumbo Mix21,3526,2605
Grandma’s Boysenberry Spread25,0030,75

Funkcje skalarne w klauzulach WHERE oraz ORDER BY

W zapytaniu:

  • filtrujemy rekordy na podstawie wyniku funkcji dbo.CalculateGrossPrice – szukamy produktów, których cena brutto przekracza 50
  • sortujemy wyniki według wartości zwróconej przez funkcję dbo.CalculateGrossPrice
SELECT ProductName, UnitPrice AS Netto,
dbo.CalculateGrossPrice(UnitPrice) AS Brutto
FROM Products
WHERE dbo.CalculateGrossPrice(UnitPrice) > 50
ORDER BY dbo.CalculateGrossPrice(UnitPrice);

Wynik:

ProductNameNettoBrutto
Schoggi Schokolade43,9053,997
Vegie-spread43,9053,997
Rössle Sauerkraut45,6056,088
Ipoh Coffee46,0056,58
Tarte au sucre49,3060,639

Funkcje skalarne w klauzulach GROUP BYHAVING

W przykładzie:

  • grupujemy dane według wartości zwróconej przez funkcję dbo.CalculateGrossPrice(UnitPrice)
  • filtrujemy grupy na podstawie wyniku funkcji – wyświetlamy tylko te grupy cenowe, gdzie cena brutto jest większa niż 30
SELECT
dbo.CalculateGrossPrice(UnitPrice) AS GrossPrice,
COUNT(*) AS ProductCount
FROM Products
GROUP BY dbo.CalculateGrossPrice(UnitPrice)
HAVING dbo.CalculateGrossPrice(UnitPrice) > 30;

Wynik:

GrossPriceProductCount
41,821
42,8041
44,281
46,742
47,971
49,201
53,9972

Funkcje skalarne w instrukcji CASE

W przykładzie:

  • używamy funkcji dbo.CalculateGrossPrice(UnitPrice), aby najpierw obliczyć cenę z podatkiem
  • tworzymy kategorie produktów „Tani”, „Standard”, „Premium” na podstawie ceny brutto
SELECT
ProductName,
UnitPrice AS Netto,
dbo.CalculateGrossPrice(UnitPrice) AS Brutto,
CASE
WHEN dbo.CalculateGrossPrice(UnitPrice) < 20 THEN 'Tani'
WHEN dbo.CalculateGrossPrice(UnitPrice) <=100 THEN 'Standard'
WHEN dbo.CalculateGrossPrice(UnitPrice) > 100 THEN 'Premium'
ELSE 'Do wyceny'
END AS SegmentCenowy
FROM Products;

Wynik:

ProductNameNettoBruttoSegmentCenowy
Chai18,0022,14Standard
Chang19,0023,37Standard
Aniseed Syrup10,0012,30Tani
Chef Anton’s Cajun Seasoning22,0027,06Standard
Chef Anton’s Gumbo Mix21,3526,2605Standard
Grandma’s Boysenberry Spread25,0030,75Standard
Uncle Bob’s Organic Dried Pears30,0036,90Standard
Northwoods Cranberry Sauce40,0049,20Standard
Mishi Kobe Niku97,00119,31Premium

W powyższym przykładzie funkcja dbo.fn_CalculateGrossPrice zostanie wykonana wielokrotnie dla każdego wiersza – raz w kolumnie Brutto i po jednym razie dla każdego warunku WHEN, w którym występuje. Może to wpłynąć negatywnie na wydajność zapytania.

Aby zoptymalizować zapytanie i uniknąć wielokrotnego wywoływania tej samej funkcji dla jednego wiersza, możemy użyć operatora CROSS APPLY

SELECT
p.ProductName,
p.UnitPrice AS Netto,
ca.Brutto,
CASE
WHEN ca.Brutto < 20 THEN 'Tani'
WHEN ca.Brutto <= 100 THEN 'Standard'
WHEN ca.Brutto > 100 THEN 'Premium'
ELSE 'Do wyceny'
END AS SegmentCenowy
FROM Products p
CROSS APPLY (
SELECT dbo.CalculateGrossPrice(p.UnitPrice) AS Brutto
) ca;

Modyfikowanie i usuwanie własnych funkcji skalarnych

Aby zmodyfikować skalarną funkcję użytkownika możemy użyć polecenia ALTER FUNCTION

W przykładzie zmieniamy stawkę podatku z 23% na 8% oraz dodajemy zaokrąglanie wyniku do dwóch miejsc po przecinku.

ALTER FUNCTION dbo.CalculateGrossPrice
(
@NetPrice MONEY
)
RETURNS MONEY
AS
BEGIN
-- Cena powiększona o 8% VAT i zaokrąglona do 2 miejsc po przecinku
RETURN ROUND(ISNULL(@NetPrice, 0) * 1.08, 2)
END;
GO

Aby usunąć funkcję używamy polecenia DROP FUNCTION

Jeśli funkcja nie jest już wykorzystywana przez inne obiekty bazy danych, można ją usunąć

DROP FUNCTION dbo.CalculateGrossPrice;
GO

Wyzwania wydajnościowe funkcji skalarnych

Tworzenie funkcji skalarnych (UDF) w SQL Server umożliwia porządkowanie logiki biznesowej kodu i poprawę czytelności zapytań. Używanie skalarnych funkcji może jednak wiązać się z problemami wydajnościowymi gdyż:

  • Funkcje skalarne są wykonywane w trybie wiersz po wierszu, co powoduje wielokrotne wywołania tej samej logiki i znacząco wydłuża czas wykonania zapytań
  • Logika zawarta w skalarnej UDF jest w dużej mierze niewidoczna dla optymalizatora zapytań, co ogranicza jego zdolność do tworzenia efektywnych planów wykonania
  • Zapytania korzystające ze skalarnych UDF często nie mogą być wykonywane równolegle, co prowadzi do braku możliwości wykorzystania dostępnych zasobów sprzętowych
  • Zastosowanie funkcji skalarnej w klauzulach SELECT, WHERE lub JOIN może uniemożliwić efektywne użycie indeksów
  • Koszt wykonania skalarnej UDF nie jest jednoznacznie widoczny w planach zapytań, co utrudnia analizę i diagnozowanie problemów wydajnościowych

W wielu przypadkach lepszym rozwiązaniem może być użycie wyrażeń inline lub funkcji tabelarycznych inline.

Tworzenie funkcji skalarnych (UDF) w SQL Server to ważny element pracy z bazami danych. W praktyce warto łączyć je ze świadomym podejściem do wydajności i analizy planów wykonania zapytań.

Jeśli chcesz pójść dalej w pracy z SQL Server i przećwiczyć omawiane zagadnienia w uporządkowanej formie, możesz skorzystać z kursu SQL Server – poziom zaawansowany, na którym prezentujemy funkcje użytkownika w szerszym kontekście.

Paula Gajewska
Paula Gajewska
Programistka Python, SQL
Udostępnij wpis:udostępnij Facebookudostępnij Linkedinudostępnij e-mail

Polecane