SUBSTRING w SQL Server bez błędów - składnia i przykłady

Przemysław Kwiatkowski 1 września 2026
SQL Server: zapytanie z funkcją mssql substring do wyodrębnienia domeny z adresu email.

Spis treści

Praca z tekstem w SQL Server często sprowadza się do wyciągnięcia kilku znaków z dłuższej wartości: prefiksu numeru zamówienia, domeny z adresu e-mail albo fragmentu kodu produktu. Funkcja SUBSTRING robi to precyzyjnie, ale wymaga zrozumienia numerowania znaków, długości fragmentu i zachowania dla wartości NULL. Poniżej pokazuję składnię, praktyczne przykłady, typowe błędy oraz sytuacje, w których lepiej użyć CHARINDEX, LEFT, RIGHT albo STRING_SPLIT.

Najważniejsze zasady wycinania fragmentów tekstu w SQL Server

  • SUBSTRING przyjmuje wyrażenie, pozycję początkową i liczbę znaków.
  • Numerowanie znaków zaczyna się od 1, a nie od zera.
  • Ujemna długość powoduje błąd wykonania zapytania.
  • Pozycję fragmentu można wyznaczyć dynamicznie za pomocą CHARINDEX.
  • W warunku WHERE funkcja na kolumnie może pogorszyć wydajność i użycie indeksu.

Błąd w zapytaniu SQL Server: nieprawidłowy typ danych dla funkcji mssql substring.

Jak działa funkcja SUBSTRING w SQL Server

Podstawowa składnia jest prosta:

SUBSTRING(expression, start, length)

Expression to tekst, kolumna albo inne wyrażenie, z którego pobieramy fragment. Parametr start wskazuje pozycję pierwszego znaku, a length określa, ile znaków ma zostać zwróconych.

SELECT SUBSTRING('Kursdotnet.pl', 1, 4) AS Fragment;

Wynikiem będzie Kurs. Pierwszy znak ma pozycję 1, drugi pozycję 2, a kropka w adresie znajduje się na pozycji 11. To drobna różnica względem wielu języków programowania, gdzie indeksowanie często zaczyna się od zera. Sam kilka razy widziałem błąd w zapytaniu wynikający właśnie z automatycznego przeniesienia przyzwyczajeń z C# do T-SQL.

Najważniejsze reguły parametrów

  • Gdy start jest większy niż długość tekstu, SQL Server zwraca pusty wynik.
  • Gdy start jest mniejszy niż 1, funkcja zaczyna od pierwszego znaku, ale skraca zwracany fragment.
  • Jeżeli length wykracza poza koniec tekstu, zwracane są znaki dostępne do końca.
  • Ujemna wartość length kończy zapytanie błędem.
  • Jeżeli wyrażenie wejściowe ma wartość NULL, wynik również będzie NULL.
SELECT
    SUBSTRING('ABCDEF', 2, 3) AS NormalnyFragment,
    SUBSTRING('ABCDEF', 2, 20) AS ZaDlugieOkno,
    SUBSTRING('ABCDEF', 20, 3) AS PozaTekstem;

Pierwsze wyrażenie zwróci BCD, drugie BCDEF, a trzecie pustą wartość. Funkcja nie dopełnia tekstu spacjami ani zerami, dlatego wynik trzeba samodzielnie przygotować, jeśli aplikacja oczekuje stałego formatu.

Praktyczne przykłady z kolumnami i stałymi pozycjami

Najczęstszy scenariusz to pobranie fragmentu z kolumny tabeli. Załóżmy, że tabela przechowuje identyfikatory produktów w formacie ELEC-2026-0042.

SELECT
    ProductCode,
    SUBSTRING(ProductCode, 1, 4) AS Category,
    SUBSTRING(ProductCode, 6, 4) AS ProductYear,
    SUBSTRING(ProductCode, 11, 4) AS SequenceNumber
FROM Products;

To rozwiązanie działa dobrze, gdy format jest stały i każda część zawsze ma tę samą długość. Jeśli ktoś zmieni dane na ELEC-26-42, pozycje przestaną odpowiadać rzeczywistym fragmentom. Przy danych biznesowych nie zakładam więc stałych pozycji bez sprawdzenia, czy format jest wymuszany przez ograniczenie, walidację aplikacji albo procedurę importu.

Wycinanie prefiksu i końcówki

Jeżeli potrzebujesz tylko początku tekstu, krótszy i czytelniejszy będzie LEFT. Dla końca wartości analogicznie używa się funkcji RIGHT.

SELECT
    LEFT('ORD-2026-000145', 3) AS Prefix,
    RIGHT('ORD-2026-000145', 6) AS Numer;

SUBSTRING daje większą kontrolę, ale nie zawsze jest najlepszym wyborem. Do prostego prefiksu używam LEFT, bo od razu komunikuje zamiar osobie, która będzie utrzymywać zapytanie za kilka miesięcy.

Fragment adresu e-mail

Jeśli dane mają przewidywalny układ, można wyciągnąć domenę po znaku @. Samo SUBSTRING nie wyszukuje separatora, dlatego łączymy je z CHARINDEX.

SELECT
    Email,
    SUBSTRING(
        Email,
        CHARINDEX('@', Email) + 1,
        LEN(Email)
    ) AS DomainName
FROM Users
WHERE Email IS NOT NULL
  AND CHARINDEX('@', Email) > 0;

Warunek z CHARINDEX jest tu istotny. Bez niego rekord pozbawiony znaku @ może zwrócić nieoczekiwany rezultat albo utrudnić późniejszą walidację. Dla bardziej złożonych danych nie traktuję takiego wyrażenia jako pełnego parsera adresów e-mail, lecz jako prostą operację na poprawnie zweryfikowanych wartościach.

Dynamiczna długość fragmentu z CHARINDEX

Stała liczba znaków wystarcza przy kodach o jednolitym formacie. W praktyce częściej trzeba pobrać tekst do pierwszego separatora albo od separatora do końca. Wtedy pozycję i długość obliczamy podczas wykonywania zapytania.

SELECT
    FullName,
    SUBSTRING(
        FullName,
        1,
        CHARINDEX(' ', FullName + ' ') - 1
    ) AS FirstName
FROM Customers;

Dodanie spacji do wartości w CHARINDEX zabezpiecza przypadek, w którym klient ma tylko jedno imię i nie zawiera separatora. Dzięki temu funkcja zawsze znajdzie znak kończący pierwszy fragment. To mały zabieg, ale właśnie takie szczegóły decydują o tym, czy zapytanie działa stabilnie na danych produkcyjnych.

Tekst między dwoma separatorami

Załóżmy, że kolumna przechowuje wartości w formacie PL|MAZ|Warszawa. Chcemy pobrać drugi element, czyli kod województwa.

SELECT
    LocationCode,
    SUBSTRING(
        LocationCode,
        CHARINDEX('|', LocationCode) + 1,
        CHARINDEX('|', LocationCode, CHARINDEX('|', LocationCode) + 1)
            - CHARINDEX('|', LocationCode) - 1
    ) AS RegionCode
FROM Locations;

To działa, ale szybko staje się trudne do czytania. Przy kilku separatorach rozważam STRING_SPLIT albo zmianę modelu danych. Trzeba jednak pamiętać, że w starszych zastosowaniach STRING_SPLIT nie gwarantuje kolejności elementów bez dodatkowej obsługi, dlatego nie używam go bezrefleksyjnie, gdy pozycja fragmentu ma znaczenie.

Bezpieczne obliczanie długości

Najczęstszy błąd polega na przekazaniu do SUBSTRING wartości ujemnej. Dzieje się tak, gdy drugi separator nie istnieje albo znajduje się przed pierwszym. Przy niepewnych danych najpierw sprawdzam format, a dopiero później wykonuję wycinanie.

SELECT
    CASE
        WHEN CHARINDEX('|', LocationCode) > 0
         AND CHARINDEX('|', LocationCode, CHARINDEX('|', LocationCode) + 1) > 0
        THEN SUBSTRING(
            LocationCode,
            CHARINDEX('|', LocationCode) + 1,
            CHARINDEX('|', LocationCode, CHARINDEX('|', LocationCode) + 1)
                - CHARINDEX('|', LocationCode) - 1
        )
        ELSE NULL
    END AS RegionCode
FROM Locations;

W wielu projektach lepiej przenieść takie reguły do warstwy importu i zapisać osobne wartości w kolumnach. Dynamiczne parsowanie w każdym SELECT-u jest wygodne na początku, lecz przy dużej liczbie raportów szybko zwiększa koszt utrzymania.

SUBSTRING a inne funkcje tekstowe

Funkcje tekstowe częściowo się pokrywają, ale każda ma inne zastosowanie. Poniższe zestawienie pomaga wybrać prostsze rozwiązanie bez budowania skomplikowanego wyrażenia.

Funkcja Najlepsze zastosowanie Przykład
SUBSTRING Fragment wskazany pozycją i długością SUBSTRING(Code, 6, 4)
LEFT Początek tekstu LEFT(Code, 3)
RIGHT Koniec tekstu RIGHT(Code, 4)
CHARINDEX Odnalezienie pozycji znaku lub ciągu CHARINDEX('-', Code)
STUFF Usunięcie i zastąpienie fragmentu STUFF(Code, 1, 3, 'NEW')
STRING_SPLIT Podział tekstu na elementy według separatora STRING_SPLIT(Tags, ',')

Moja praktyczna zasada jest prosta. Do stałej pozycji wybieram SUBSTRING, do początku lub końca LEFT albo RIGHT, a do znalezienia separatora CHARINDEX. Jeżeli tekst jest listą elementów, lepiej użyć STRING_SPLIT niż wielokrotnie zagnieżdżać SUBSTRING.

Typowe błędy i wpływ na wydajność

Mylenie pozycji z indeksem

W SQL Server pierwszy znak ma pozycję 1. Dla tekstu ABC wyrażenie SUBSTRING('ABC', 1, 1) zwróci A, a SUBSTRING('ABC', 0, 1) nie oznacza pierwszego znaku w taki sam sposób jak indeks 0 w C#.

Brak kontroli wartości NULL

SUBSTRING nie zamienia NULL na pusty tekst. Jeżeli aplikacja oczekuje wartości domyślnej, użyj jawnie COALESCE.

SELECT COALESCE(SUBSTRING(DisplayName, 1, 1), '?') AS Initial
FROM Users;

To rozróżnienie ma znaczenie w raportach, filtrach i eksportach. Pusty tekst i NULL mogą być później interpretowane jako dwie różne informacje.

Używanie funkcji na kolumnie w WHERE

Zapytanie poniżej jest czytelne, ale przy dużej tabeli może wymagać odczytania wielu wierszy:

SELECT *
FROM Orders
WHERE SUBSTRING(OrderNumber, 1, 3) = 'ORD';

Jeśli chodzi tylko o prefiks, zwykle lepiej zapisać warunek jako:

SELECT *
FROM Orders
WHERE OrderNumber LIKE 'ORD%';

Powód jest praktyczny: funkcja wykonana na kolumnie może uniemożliwić efektywne użycie zwykłego indeksu. Gdy filtrowanie po wyciętym fragmencie jest częste, rozważam kolumnę obliczaną, ewentualnie utrwaloną i zindeksowaną. Takie rozwiązanie ma sens dopiero po sprawdzeniu planu wykonania i rzeczywistego czasu zapytań, a nie na podstawie samego przeczucia.

Przeczytaj również: COALESCE w PostgreSQL - jak bezpiecznie obsługiwać NULL?

Różnice między wersjami SQL Server

W SQL Server 2022 i starszych wersjach parametr length jest wymagany. W SQL Server 2025 i nowszych oraz w wybranych usługach Azure SQL można pominąć długość, aby pobrać tekst od wskazanej pozycji do końca.

SELECT SUBSTRING('Kursdotnet.pl', 6) AS OdSzostegoZnaku;

Jeżeli zapytanie ma działać w kilku środowiskach, nie korzystam z opcjonalnego parametru bez sprawdzenia wersji docelowej bazy. Jawne podanie długości jest bardziej przenośne i często czytelniejsze dla zespołu.

Jak podejść do SUBSTRING w projekcie produkcyjnym

Przed napisaniem wyrażenia sprawdzam trzy rzeczy: czy format danych jest stały, czy separator zawsze występuje oraz czy funkcja będzie używana tylko do prezentacji, czy także do filtrowania. Te trzy odpowiedzi zwykle pokazują, czy wystarczy prosty SUBSTRING, czy potrzebna jest walidacja, osobna kolumna albo zmiana struktury tabeli.

  • Dla stałych kodów stosuj pozycję i długość.
  • Dla tekstu z separatorami połącz SUBSTRING z CHARINDEX i zabezpiecz brakujące znaki.
  • Dla prostego początku lub końca użyj LEFT albo RIGHT.
  • Dla częstego filtrowania rozważ indeksowaną kolumnę obliczaną.
  • Dla wieloelementowych list sprawdź, czy dane nie powinny trafić do osobnej tabeli.

Największa pułapka nie leży w samej składni, tylko w założeniu, że wszystkie wartości są poprawne. Jedna niepełna wartość może wygenerować ujemną długość, pusty fragment albo błędny wynik raportu. Dlatego przed wdrożeniem testuję przypadki normalne, NULL, brak separatora, zbyt krótki tekst i dane zawierające znaki narodowe.

SUBSTRING jest małą funkcją, ale dobrze użyta rozwiązuje wiele codziennych problemów w T-SQL. Najważniejsze jest zapamiętanie, że pozycje zaczynają się od 1, długość musi być poprawna, a dynamiczne parsowanie warto stosować tylko tam, gdzie rzeczywiście pasuje do jakości i struktury danych.

FAQ - Najczęstsze pytania

Numerowanie zaczyna się od 1, nie od 0. Na przykład SUBSTRING('ABCDEF', 2, 3) zwraca BCD, a pozycja większa niż długość tekstu zwraca pusty wynik.

Ujemna wartość parametru length powoduje błąd wykonania zapytania. Jeśli długość wykracza poza koniec tekstu, SQL Server zwraca tylko znaki dostępne do końca wartości.

Pozycję separatora można znaleźć funkcją CHARINDEX, a następnie użyć jej do obliczenia długości fragmentu. Przy danych, w których separator może nie występować, warto zabezpieczyć wyrażenie, na przykład dodając spację w CHARINDEX albo sprawdzając wynik przed wykonaniem SUBSTRING.

LEFT sprawdza się przy pobieraniu początku tekstu, a RIGHT przy pobieraniu jego końcówki. STRING_SPLIT jest wygodniejszy dla list elementów rozdzielonych separatorem, ale w starszych zastosowaniach nie gwarantuje kolejności bez dodatkowej obsługi.

Funkcja wykonana na kolumnie może ograniczyć efektywne użycie zwykłego indeksu i wymagać odczytania wielu wierszy. Przy filtrowaniu po prefiksie lepiej użyć warunku LIKE 'ORD%', a przy częstym filtrowaniu po fragmencie rozważyć utrwaloną, zindeksowaną kolumnę obliczaną po sprawdzeniu planu wykonania.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

substring
charindex
string_split
t-sql
kolumny obliczane
Autor Przemysław Kwiatkowski
Przemysław Kwiatkowski
Jestem Przemysław i od 15 lat zajmuję się programowaniem .NET, chmurą Azure oraz sztuczną inteligencją. Moja przygoda z tymi technologiami zaczęła się od fascynacji możliwościami, jakie dają, a z czasem przerodziła się w pasję do tworzenia rozwiązań, które realnie wpływają na pracę i życie ludzi. Na kursdotnet.pl staram się dzielić się swoją wiedzą w sposób przystępny, tłumacząc złożone zagadnienia i pomagając zrozumieć, jak te dynamicznie rozwijające się obszary IT mogą być wykorzystane w praktyce. Dokładam wszelkich starań, aby prezentowane przeze mnie materiały były rzetelne, aktualne i oparte na sprawdzonych źródłach, a także aby uporządkować wiedzę w sposób ułatwiający jej przyswojenie.

Udostępnij artykuł

Napisz komentarz