Blog JSystems - uwalniamy wiedzę!
Blog JSystems - uwalniamy wiedzę!

W skrócieAI pisze testy SQL z danymi testowymi i oczekiwanym wynikiem oraz robi przegląd kodu jak doświadczony DBA, ale testy trzeba sprawdzić celowo zepsutą funkcją. Pokazujemy 9 testów od AI, test mutacyjny, procedurę podatną na SQL injection, jej poprawioną wersję i listę kontrolną 25 punktów.
To część 7 z 8 przewodnika AI w SQL. Wszystkie przykłady powstały na bazie DemoShop w SQL Server 2022: fikcyjnym sklepie internetowym z 2,4 mln zamówień. Polecenia wpisujemy do ChatGPT, ale te same możesz podać Microsoft Copilotowi, Claude albo Gemini. Przy skryptach korzystających z mechanizmów serwera w małej szarej ramce znajdziesz ich odpowiedniki dla Oracle i PostgreSQL.
Z tej części dowiesz się:
Pliki do tej części
Skrypt bazy uruchamiasz w SSMS na serwerze SQL Server 2019 lub nowszym. Tworzy bazę DemoShop, na której powstały wszystkie przykłady.
Testy zapytań i funkcji SQL pisze się rzadko, bo są żmudne: trzeba przygotować dane z zachowaniem kluczy obcych, wyliczyć oczekiwany wynik, porównać go z rzeczywistym i posprzątać. To dokładnie ten rodzaj pracy, który AI wykonuje w minutę. Nasz rachunek z tej sekcji brzmi tak: AI napisało ponad 1800 linii testów, my poświęciliśmy kwadrans na sprawdzenie, czy te testy mają sens. Ten kwadrans okazał się konieczny.
Testujemy funkcję, która liczy przychód z opłaconych zamówień każdego klienta w podanym miesiącu. W sekcji o błędach logicznych pokazaliśmy, że zamówienie z dwiema płatnościami potrafi po cichu podwoić przychód, więc ta funkcja jest dobrym kandydatem do zabezpieczenia testami.
/* Funkcja pod testami: przychód z opłaconych zamówień każdego klienta w podanym miesiącu.
Zamówienie jest opłacone, gdy ma co najmniej jedną płatność SUCCESS.
Klienci bez opłaconych zamówień w miesiącu też są w wyniku (0 zamówień, 0 zł). */
CREATE OR ALTER FUNCTION dbo.fn_PaidRevenueByCustomer (@MonthStart date)
RETURNS TABLE
AS
RETURN
SELECT c.CustomerID,
COUNT(o.OrderID) AS PaidOrders,
ISNULL(SUM(v.OrderValue), 0) AS PaidRevenue
FROM dbo.Customers AS c
LEFT JOIN dbo.Orders AS o
ON o.CustomerID = c.CustomerID
AND o.OrderDate >= @MonthStart
AND o.OrderDate < DATEADD(MONTH, 1, @MonthStart)
AND EXISTS (SELECT 1 FROM dbo.Payments AS p
WHERE p.OrderID = o.OrderID AND p.Status = 'SUCCESS')
OUTER APPLY (SELECT SUM(oi.Quantity * oi.UnitPrice * (100 - oi.DiscountPct) / 100) AS OrderValue
FROM dbo.OrderItems AS oi
WHERE oi.OrderID = o.OrderID) AS v
GROUP BY c.CustomerID;
Testy uruchamiamy w osobnej, pustej bazie DemoShop_Test o identycznym schemacie, nigdy na danych produkcyjnych. Skrypt tworzący taką bazę jest w plikach do pobrania. W poleceniu wymieniliśmy przypadki brzegowe, które chcemy sprawdzić. Możesz je ustalić sam albo najpierw poprosić AI o propozycję i ją przejrzeć. Do tego trzy wymagania techniczne: jawne identyfikatory w danych testowych, oczekiwany wynik zapisany w tabeli i porównanie przez EXCEPT w obie strony.
Mam w SQL Server 2022 funkcję tabelaryczną dbo.fn_PaidRevenueByCustomer (kod niżej, schemat bazy w załączonym pliku demoshop_schemat_dla_ai.txt). Napisz test w T-SQL, który uruchomię w pustej bazie DemoShop_Test o tym samym schemacie (te same tabele, klucze i ograniczenia, bez danych).
Test ma sprawdzić przypadki: klient bez żadnego zamówienia, zamówienie bez płatności i zamówienie tylko z płatnością FAILED, zamówienie z dwiema płatnościami SUCCESS (raty), zamówienie bez pozycji, dwie identyczne pozycje w jednym zamówieniu, zamówienia na granicach miesiąca (pierwsza i ostatnia sekunda miesiąca oraz pierwsza sekunda następnego miesiąca), rabat 0 i 100 procent oraz pustą tabelę klientów.
Dla każdego przypadku przygotuj dane (INSERT z jawnymi identyfikatorami, z zachowaniem kluczy obcych), zapisz oczekiwany wynik w tabeli tymczasowej, porównaj go z wynikiem funkcji przez EXCEPT w obie strony i wypisz jedną linię PASS albo FAIL z nazwą przypadku. Każdy przypadek sprząta po sobie. Na końcu wypisz liczbę PASS i FAIL. Całość ma być jednym skryptem do uruchomienia w SSMS.
[kod funkcji]

Skrypt miał ponad 1600 linii i wyglądał porządnie. W SSMS zatrzymał się jednak od razu, na etapie analizy składni, więc nie wykonał się żaden test:

Uruchomiłem skrypt w SSMS na bazie DemoShop_Test i od razu dostaję błąd 102: Incorrect syntax near '='. Żaden test się nie wykonał. Co jest nie tak? Podaj poprawiony skrypt w całości.

Błąd był banalny, ale zablokował cały skrypt. To dobra ilustracja zasady, do której wracamy w sekcji Kiedy AI się myli: kod od AI zawsze uruchamiamy, zanim uznamy go za gotowy. Model nie wykonuje swojego kodu, tylko go pisze.
Test jest tyle wart, ile jego oczekiwany wynik. Gdyby AI wyliczyło oczekiwaną wartość tak samo błędnie jak funkcja, test przeszedłby i niczego by nie udowodnił. Dlatego każdy przypadek przeliczyliśmy ręcznie z danych, które AI wstawia:
| Nr | Przypadek | Dane testowe (maj 2026) | Oczekiwany wynik | Ocena |
|---|---|---|---|---|
| 1 | Klient bez zamówień | jeden klient, brak zamówień | 0 zamówień, 0 zł | poprawny: klient ma być w wyniku z zerami |
| 2 | Zamówienie bez płatności | zamówienie NEW, brak płatności | 0 zamówień, 0 zł | poprawny |
| 3 | Tylko płatność FAILED | płatność 100 zł ze statusem FAILED | 0 zamówień, 0 zł | poprawny |
| 4 | Dwie płatności SUCCESS | pozycja 1 × 120 zł, raty 40 zł i 80 zł | 1 zamówienie, 120 zł | poprawny: zamówienie liczy się raz |
| 5 | Opłacone zamówienie bez pozycji | płatność 0 zł SUCCESS, brak pozycji | 1 zamówienie, 0 zł | poprawny: opłacone, ale bez wartości |
| 6 | Dwie identyczne pozycje | 2 × (2 szt. po 25 zł) | 1 zamówienie, 100 zł | poprawny |
| 7 | Granice miesiąca | 1 maja 00:00:00 (10 zł), 31 maja 23:59:59 (20 zł), 1 czerwca 00:00:00 (40 zł) | 2 zamówienia, 30 zł | poprawny: czerwcowe poza zakresem |
| 8 | Rabat 0 i 100 procent | 100 zł bez rabatu, 500 zł z rabatem 100% | 2 zamówienia, 100 zł | poprawny |
| 9 | Pusta tabela klientów | brak danych | pusty wynik | poprawny |
Wszystkie dziewięć oczekiwanych wyników zgadza się z definicją funkcji. Dopiero teraz uruchamiamy poprawiony skrypt:


Po testach sprawdziliśmy jeszcze stan bazy: wszystkie tabele w DemoShop_Test mają zero wierszy, bo każdy przypadek kończy się wycofaniem transakcji.
Komplet PASS nie dowodzi jeszcze, że testy działają. Test, który zawsze przechodzi, też daje komplet PASS. Żeby to sprawdzić, wprowadzamy do funkcji błąd, który ktoś mógłby zrobić w dobrej wierze: zamieniamy EXISTS na złączenie z tabelą płatności, bo „złączenie jest prostsze”. Taki zabieg nazywa się testem mutacyjnym.
/* „Optymalizacja”: EXISTS zamieniony na złączenie z Payments.
Wygląda niewinnie, ale zamówienie z dwiema płatnościami SUCCESS liczy się teraz dwa razy. */
CREATE OR ALTER FUNCTION dbo.fn_PaidRevenueByCustomer (@MonthStart date)
RETURNS TABLE
AS
RETURN
SELECT c.CustomerID,
COUNT(p.OrderID) AS PaidOrders,
ISNULL(SUM(v.OrderValue), 0) AS PaidRevenue
FROM dbo.Customers AS c
LEFT JOIN dbo.Orders AS o
ON o.CustomerID = c.CustomerID
AND o.OrderDate >= @MonthStart
AND o.OrderDate < DATEADD(MONTH, 1, @MonthStart)
LEFT JOIN dbo.Payments AS p
ON p.OrderID = o.OrderID
AND p.Status = 'SUCCESS'
OUTER APPLY (SELECT SUM(oi.Quantity * oi.UnitPrice * (100 - oi.DiscountPct) / 100) AS OrderValue
FROM dbo.OrderItems AS oi
WHERE oi.OrderID = p.OrderID) AS v
GROUP BY c.CustomerID;


Ile ten błąd kosztowałby na prawdziwych danych? Uruchomiliśmy obie wersje logiki na grudniu 2025 roku w pełnej bazie DemoShop:
| Grudzień 2025 | Opłacone zamówienia | Przychód |
|---|---|---|
| Funkcja poprawna (EXISTS) | 102 426 | 318 886 990,99 zł |
| Funkcja po „optymalizacji” (złączenie) | 107 531 | 368 786 636,93 zł |
| Różnica | +5 105 | +49 899 645,94 zł (+15,6%) |
Prawie 50 milionów złotych zawyżonego przychodu w jednym miesiącu, bez żadnego błędu i bez ostrzeżenia. Na przeglądzie kodu taka zmiana mogłaby przejść, bo wygląda jak uproszczenie. Test numer 4 zatrzymuje ją w kilka sekund.
Pliki z tej sekcji: skrypt pustej bazy DemoShop_Test, funkcja poprawna, funkcja z błędem do testu mutacyjnego i poprawiony skrypt testów od AI.
Przegląd kodu (code review) to drugi zestaw oczu przed wdrożeniem. AI nadaje się do tej roli wyjątkowo dobrze, bo nie męczy się, nie przeocza powtarzalnych wzorców i zna typowe błędy bezpieczeństwa. Polecenie może być krótkie, byle nadawało rolę i nazywało obszary oceny:
Do testu wybraliśmy procedurę, jaką można znaleźć w wielu starszych systemach: wyszukiwanie klientów w panelu obsługi, z warunkami sklejanymi w tekst zapytania i wykonywanymi przez EXEC. Do polecenia dopisaliśmy wersję bazy i dołączyliśmy plik z opisem schematu, żeby AI mogło porównać typy parametrów z kolumnami.
Jesteś seniorem DBA. Zrób code review tego zapytania pod kątem poprawności, wydajności, bezpieczeństwa i czytelności. Baza to SQL Server 2022, schemat w załączonym pliku demoshop_schemat_dla_ai.txt.
[kod procedury]
/* Procedura wyszukiwania klientów z „dawnego” panelu obsługi */
CREATE OR ALTER PROCEDURE dbo.usp_SearchCustomers
@LastName nvarchar(100),
@City nvarchar(100) = NULL,
@SortBy varchar(50) = 'LastName'
AS
BEGIN
DECLARE @sql nvarchar(max) =
N'SELECT * FROM dbo.Customers WITH (NOLOCK) WHERE LastName LIKE ''%' + @LastName + N'%''';
IF @City IS NOT NULL
SET @sql = @sql + N' AND City = ''' + @City + N'''';
SET @sql = @sql + N' ORDER BY ' + @SortBy;
EXEC (@sql);
END;

Przegląd jest kompletny i trafnie uszeregowany. Na szczególną uwagę zasługują trzy rzeczy. Pierwsza: AI wyjaśnia, dlaczego kolumny sortowania nie da się po prostu przekazać jako parametru (parametr jest wartością, a nie nazwą kolumny) i proponuje listę dozwolonych wartości mapowanych na gotowe fragmenty kodu. Druga: opisuje NOLOCK jako błąd znaczeniowy, a nie optymalizację, bo pozwala czytać dane z niezatwierdzonych transakcji. Trzecia: zauważa, że pusty parametr nazwiska zwróci wszystkich 200 tysięcy klientów.
Opis podatności to jedno, a dowód to drugie. Uruchamiamy starą procedurę z parametrem, który zamyka apostrof i dokleja warunek zawsze prawdziwy:
-- Próba wstrzyknięcia przez parametr z nazwiskiem
EXEC dbo.usp_SearchCustomers
@LastName = N'x'' OR 1=1 --';

Groźniejsza jest kolumna sortowania, bo przez nią da się dopisać do zapytania dowolne polecenie. Tu dopisaliśmy niewinne SELECT name FROM sys.databases, ale w tym miejscu równie dobrze mogłoby stać polecenie zmieniające albo usuwające dane, wykonane z uprawnieniami aplikacji:
-- Próba wstrzyknięcia przez kolumnę sortowania: drugie zapytanie dopisane do ORDER BY
EXEC dbo.usp_SearchCustomers
@LastName = N'Kowalsk',
@City = N'Gdańsk',
@SortBy = 'LastName; SELECT name FROM sys.databases';

Napisz poprawioną wersję tej procedury, zachowując jej przeznaczenie: wyszukiwanie klientów po fragmencie nazwiska, opcjonalnie po mieście, z wyborem kolumny sortowania. Panel potrzebuje tylko kolumn CustomerID, FirstName, LastName, City i Segment.

Te same próby na poprawionej wersji kończą się tak, jak powinny. Nazwisko ze wstrzyknięciem jest traktowane jako zwykły tekst, którego żaden klient nie ma w nazwisku, więc wynik jest pusty. Kolumna sortowania spoza listy powoduje błąd 50002 z komunikatem z procedury.
-- Ta sama próba na procedurze poprawionej przez AI
EXEC dbo.usp_SearchCustomers
@LastName = N'x'' OR 1=1 --';

-- Kolumna sortowania spoza listy dozwolonych wartości
EXEC dbo.usp_SearchCustomers
@LastName = N'Kowalsk',
@City = N'Gdańsk',
@SortBy = 'LastName; SELECT name FROM sys.databases';

-- Normalne wywołanie wersji od AI
EXEC dbo.usp_SearchCustomers
@LastName = N'Kowalsk',
@City = N'Gdańsk',
@SortBy = 'FirstName';

Polecenie z ogólną prośbą o przegląd daje dobre wyniki, ale jeszcze lepsze daje lista, według której AI ma przejść punkt po punkcie i przy każdym napisać „spełnione”, „niespełnione” albo „nie dotyczy”. To samo zestawienie przydaje się w przeglądzie robionym przez człowieka. Ułożyliśmy je z problemów, które pokazujemy w tym przewodniku, przy każdym punkcie jest odsyłacz do przykładu.
| Nr | Obszar | Punkt kontrolny |
|---|---|---|
| 1 | Poprawność | Wynik odpowiada celowi biznesowemu zapisanemu jednym zdaniem |
| 2 | Poprawność | Złączenia nie mnożą wierszy (sprawdzone liczności relacji 1:N) |
| 3 | Poprawność | Warunki na tabelach z LEFT JOIN są w ON, a nie w WHERE |
| 4 | Poprawność | NULL obsłużony świadomie: porównania, NOT IN, COUNT(kolumna), ISNULL i COALESCE |
| 5 | Poprawność | Zakresy dat półotwarte (>= początek, < początek następnego okresu) i właściwa strefa czasowa |
| 6 | Poprawność | Agregaty na właściwym poziomie szczegółowości, bez średniej ze średnich |
| 7 | Poprawność | Funkcje okien z jawną partycją, porządkiem i ramką |
| 8 | Poprawność | Kolejność wyniku jednoznaczna (sortowanie z rozstrzygnięciem remisów) |
| 9 | Poprawność | Brak dzielenia całkowitego i obcinania typów (ISNULL, CAST, rozmiary varchar) |
| 10 | Wydajność | Warunki sargowalne: bez funkcji i obliczeń na kolumnach z indeksem |
| 11 | Wydajność | Typy parametrów zgodne z typami kolumn (brak niejawnych konwersji) |
| 12 | Wydajność | Tylko potrzebne kolumny, bez SELECT * |
| 13 | Wydajność | Indeksy wspierają warunki, złączenia i sortowanie (sprawdzone w planie rzeczywistym) |
| 14 | Wydajność | Brak zbędnych DISTINCT, ORDER BY i wielokrotnego czytania tej samej tabeli |
| 15 | Wydajność | Podzapytania skorelowane i funkcje skalarne nie wykonują się dla każdego wiersza |
| 16 | Wydajność | Brak wskazówek (NOLOCK, FORCE ORDER, INDEX) bez udokumentowanego powodu |
| 17 | Wydajność | Zapytanie zachowuje się dobrze dla małych i dużych wartości parametrów (parameter sniffing) |
| 18 | Bezpieczeństwo | Brak sklejania tekstu z danymi użytkownika: parametry przez sp_executesql |
| 19 | Bezpieczeństwo | Identyfikatory (kolumny sortowania, nazwy tabel) tylko z listy dozwolonych wartości |
| 20 | Bezpieczeństwo | Zwracane tylko dane potrzebne odbiorcy, bez zbędnych danych osobowych |
| 21 | Bezpieczeństwo | Uprawnienia minimalne: konto aplikacji bez praw zapisu tam, gdzie wystarczy odczyt |
| 22 | Czytelność | Złączenia jawne (JOIN ... ON), aliasy znaczące, jednolite formatowanie |
| 23 | Czytelność | Logika podzielona na nazwane kroki (CTE) z komentarzem przy nieoczywistych regułach |
| 24 | Utrzymanie | Obsługa błędów i walidacja parametrów (puste, za krótkie, spoza zakresu) |
| 25 | Utrzymanie | Testy z danymi granicznymi i dowód równoważności przy każdej zmianie |
Jesteś seniorem DBA. Zrób code review poniższego kodu T-SQL (SQL Server 2022) według listy kontrolnej. Dla każdego punktu napisz: spełnione, niespełnione albo nie dotyczy, a przy niespełnionych podaj konkretną poprawkę. Na końcu wypisz trzy najważniejsze zmiany w kolejności wdrażania.
Lista kontrolna:
[wklej 25 punktów z tabeli]
Kod:
[wklej kod]
Część 7 z 8 przewodnika AI w SQL
Komentarze (0)
Brak komentarzy...