SQL i bazy danych

Od pytania biznesowego do SQL z AI: jak nie dać się zaskoczyć

AI pisze zapytanie SQL w kilka sekund, a za wynik odpowiadasz Ty. Pokazuję pułapki złączeń, NULL-i i dat oraz prosty protokół sprawdzania zapytań.

Pixel art w kolorach magenta i czerni: walec bazy danych z kłódką, lupa nad rzędami tabeli i okno terminala z kreskami zapytania.

Pytanie przychodzi na komunikatorze w czwartek po południu: „ile zarobiliśmy we wrześniu na zamówieniach z książkami?”. Wklejam je do czatu z AI i po kilku sekundach mam zapytanie SQL. Wygląda porządnie: ma aliasy, wcięcia, a nawet komentarz. Uruchamiam je, baza odpowiada bez jednego błędu, liczba pojawia się na ekranie. Jest o kilkanaście procent za wysoka i nic w niej tego nie zdradza.

Tak wygląda codzienność pracy z AI przy bazach danych. Model pisze SQL szybciej niż ktokolwiek z nas, a w prostych przypadkach pisze go naprawdę dobrze. Za to, co zapytanie policzy, odpowiada jednak osoba, która wyśle wynik dalej. W czwartym tygodniu kursu, w temacie „Od pytania biznesowego do SQL”, ćwiczymy właśnie to przejście: od pytania zadanego ludzkim językiem do zapytania, któremu można zaufać. W tym artykule opiszę, jak to robię, jakie pułapki widuję najczęściej i jak je wyłapać, zanim wynik trafi na slajd.

Dlaczego „zapytanie działa” to za mało

Baza danych ocenia zapytanie według bardzo skromnego kryterium. Sprawdza, czy składnia jest poprawna i czy tabele oraz kolumny istnieją. Jeśli tak, wykonuje polecenie i zwraca wynik. O Twoim biznesie wie dokładnie tyle, ile jest w schemacie, czyli zwykle niewiele. Zapytanie, które zsumuje wartość jednego zamówienia trzy razy, wykona z takim samym spokojem jak zapytanie poprawne.

Dlatego najgroźniejsze zapytanie to takie, które działa. Błąd składni jest uczciwy: widać go od razu, model go poprawia i po minucie jedziemy dalej. Zapytanie, które wykonuje się bez zarzutu i liczy coś trochę innego, przechodzi przez wszystkie automatyczne bramki i zatrzymuje się dopiero na człowieku, który zna liczby. Albo nie zatrzymuje się wcale i trafia do prezentacji dla zarządu.

Co mówią benchmarki

Skąd w ogóle wiadomo, jak dobrze modele piszą SQL? Badacze mierzą to na benchmarkach text-to-SQL, czyli zestawach pytań w języku naturalnym z bazami i wzorcowymi odpowiedziami. Jednym z najbardziej znanych jest BIRD. Według pracy, w której go opisano, zawiera 12 751 par pytanie i zapytanie oraz 95 baz o łącznym rozmiarze 33,4 GB z 37 dziedzin. Autorzy celowo dodali to, co znamy z życia: brudne dane i pytania, które wymagają wiedzy spoza samej bazy. W tamtej pracy najlepszy badany model, ChatGPT, osiągnął 40,08 procent poprawności wykonania, a ludzie 92,96 procent. Ranking na stronie projektu pokazuje dziś najlepsze wyniki powyżej 80 procent, więc postęp jest wyraźny.

Jeszcze ciekawszy jest Spider 2.0, zbudowany z zadań przypominających prawdziwe środowiska firmowe. Bazy często mają tam ponad tysiąc kolumn, a wzorcowe zapytania nierzadko przekraczają sto linii. W pracy opisującej benchmark autorzy zebrali 632 takie zadania i podali, że ich agent oparty na modelu o1-preview rozwiązał 21,3 procent z nich, podczas gdy na starszych zbiorach wynik wynosił 91,2 procent (Spider 1.0) i 73 procent (BIRD). Od tamtej pory wyspecjalizowane systemy w rankingu projektu bardzo podniosły wyniki.

Wyciągam z tego dwa wnioski. Pierwszy: modele szybko się poprawiają i przy dobrze opisanych danych potrafią naprawdę dużo. Drugi: każdy benchmark mierzy skuteczność na swoim zestawie baz i pytań. Twoja hurtownia ma kolumny nazwane przez kogoś, kto dawno zmienił pracę, statusy, których nikt nie spisał, i definicję przychodu, która zmieniła się w zeszłym roku. Wyniki z rankingu przenoszą się na nią co najwyżej częściowo. Liczba, która Cię naprawdę interesuje, to odsetek poprawnych zapytań na Twoich danych, a tę poznajesz dopiero przez weryfikację.

Jest w BIRD jeszcze jeden szczegół, który bardzo lubię. Do wielu pytań benchmark dołącza tak zwaną wiedzę zewnętrzną, czyli podpowiedź w rodzaju „kredyt może dostać konto typu właściciel”, z której model wyprowadza warunek na odpowiedniej kolumnie. W Twojej firmie nikt takich podpowiedzi modelowi nie dołącza. Chyba że zrobisz to Ty.

Schemat, relacje i słownik danych

Model piszący SQL widzi to, co mu pokażesz. Jeśli dasz mu samo pytanie, zgadnie nazwy tabel. Jeśli dasz mu schemat, zgadnie znaczenie kolumn. Jeśli dasz mu schemat i słownik danych, zgadywania zostaje niewiele. Dlatego w kursie pierwszym krokiem jest poznanie schematu, a zapytania przychodzą później.

Schemat i relacje

Minimum to definicje tabel, których dotyczy pytanie: nazwy kolumn, typy i klucze. W większości baz wyciągniesz je jednym poleceniem albo z widoków systemowych, a agent podłączony do bazy zrobi to sam. Do tego potrzebne są relacje: która kolumna wskazuje na którą tabelę i czy relacja jest jeden do jednego, czy jeden do wielu. Ta druga informacja decyduje o tym, czy złączenie pomnoży wiersze, więc do niej jeszcze wrócę.

Ziarnistość, czyli co oznacza jeden wiersz

Najważniejsze zdanie w opisie każdej tabeli zaczyna się od słów „jeden wiersz to…”. Jedno zamówienie? Jedna pozycja w zamówieniu? Jedna próba płatności? Stan magazynu na koniec dnia? Większość błędów, które widuję w zapytaniach pisanych przez AI (i przez ludzi, żeby była jasność), bierze się z pomylenia ziarnistości. Model widzi kolumnę z wartością w tabeli pozycji i w tabeli zamówień i nie wie, która jest sumą której.

Słownik danych

Słownik to miejsce na wszystko, czego nie widać w schemacie. Co oznaczają poszczególne wartości statusu. W jakiej jednostce zapisano kwoty: w złotych czy w groszach. W jakiej strefie czasowej są daty. Jak oznaczone są zamówienia testowe. Czy usunięte rekordy znikają z tabeli, czy dostają flagę. To dokładnie ta wiedza, którą BIRD podaje modelowi w podpowiedziach, a którą w firmie trzeba spisać samodzielnie.

Gdzie ją trzymać? W briefie, jeśli dotyczy jednej analizy, i w stałych plikach projektu, jeśli wraca przy każdej. Kajo opisał oba miejsca w artykułach o briefie zamiast prompta i o pamięci projektu w README, AGENTS.md i CLAUDE.md. Przy bazie danych dobrze sprawdza się osobny plik ze słownikiem, do którego plik instrukcji odsyła.

Osobną kategorią są definicje wskaźników. „Aktywny klient”, „przychód netto” albo „zamówienie zrealizowane” w każdej firmie znaczą coś trochę innego. O tym, jak je spisać i co zrobić, gdy dwa źródła się kłócą, piszę w artykule o definicjach KPI i źródłach prawdy.

Pięć pułapek, które widuję najczęściej

Poniższa lista jest krótka i dość nudna. Mimo to obejmuje większość błędów w zapytaniach, które sprawdzałam, zarówno tych od modelu, jak i tych od ludzi.

1. Złączenie, które mnoży wiersze

Wróćmy do pytania z początku: ile przychodu przyniosły we wrześniu zamówienia, w których była co najmniej jedna książka. Mamy tabelę zamowienia, w której jeden wiersz to jedno zamówienie z kolumną wartosc, oraz tabelę pozycje, w której jeden wiersz to jedna pozycja zamówienia z kolumną kategoria. Dane są przykładowe. Model proponuje coś takiego:

-- Wersja z modelu: wykonuje się bez błędu
SELECT SUM(z.wartosc) AS przychod
FROM zamowienia z
JOIN pozycje p ON p.zamowienie_id = z.id
WHERE p.kategoria = 'ksiazki'
  AND z.data_zamowienia >= '2026-09-01'
  AND z.data_zamowienia <  '2026-10-01';

Zapytanie jest poprawne składniowo i wygląda rozsądnie. Kłopot zaczyna się przy zamówieniu, w którym są trzy książki. Złączenie zwraca dla niego trzy wiersze, a SUM dodaje jego wartość trzy razy. Wynik rośnie po cichu, tym bardziej, im częściej klienci kupują kilka książek naraz.

Pixel art: dwie tabele łączą się w trzecią, w której wiersze się mnożą, a zdublowany wiersz jest przekreślony.

Najprostszy test to porównanie liczby wierszy po złączeniu z liczbą unikalnych zamówień:

SELECT COUNT(*)             AS wiersze,
       COUNT(DISTINCT z.id) AS zamowienia
FROM zamowienia z
JOIN pozycje p ON p.zamowienie_id = z.id
WHERE p.kategoria = 'ksiazki';

Jeśli wiersze jest większe od zamowienia, złączenie mnoży rekordy i każda suma po kolumnie z tabeli zamówień będzie zawyżona. Poprawiona wersja sprawdza, czy w zamówieniu jest książka, i przy tym nie dokłada wierszy:

SELECT SUM(z.wartosc) AS przychod
FROM zamowienia z
WHERE z.data_zamowienia >= '2026-09-01'
  AND z.data_zamowienia <  '2026-10-01'
  AND EXISTS (
    SELECT 1
    FROM pozycje p
    WHERE p.zamowienie_id = z.id
      AND p.kategoria = 'ksiazki'
  );

Ta sama pułapka czai się przy każdej relacji jeden do wielu: zamówienia i płatności (bo za jedno zamówienie można zapłacić w kilku transakcjach), klienci i adresy, produkty i historia cen. Model zna wzorzec z EXISTS doskonale. Potrzebuje jednak informacji, że relacja jest jeden do wielu, i stąd mój nacisk na ziarnistość w opisie tabel.

2. Filtr w złym miejscu

Prosisz o listę wszystkich klientów z liczbą zamówień we wrześniu, łącznie z tymi, którzy nic nie kupili. Model pisze LEFT JOIN do tabeli zamówień i dorzuca warunek na datę zamówienia w WHERE. Klienci bez zamówień mają w kolumnach zamówień wartości NULL, więc warunek na dacie ich odrzuca, a złączenie zewnętrzne zachowuje się jak zwykłe. Klienci z zerem znikają z listy, a razem z nimi informacja o tym, ilu ich jest. Warunek na datę powinien trafić do klauzuli ON.

Podobnie bywa z warunkami po agregacji. Filtr „klienci z co najmniej trzema zamówieniami” należy do HAVING, a filtr na pojedyncze zamówienia do WHERE. Pomylenie miejsc daje wynik, który wygląda wiarygodnie i odpowiada na inne pytanie niż zadane.

3. NULL, który zjada wiersze

Warunek WHERE status <> 'anulowane' czyta się jak „wszystko poza anulowanymi”. W SQL porównanie z NULL daje jednak wynik nieznany, więc wiersze bez statusu też odpadną. Podobnych niespodzianek jest więcej. COUNT(kolumna) liczy wyłącznie wartości wypełnione, a AVG pomija braki, co bywa właściwe albo zupełnie błędne, zależnie od tego, co brak oznacza. Klasyk z wyższej półki to NOT IN z podzapytaniem: wystarczy jeden NULL w podzapytaniu, żeby całe zapytanie zwróciło pusty wynik.

Dlatego w słowniku danych warto zapisać, co oznacza brak w kluczowych kolumnach. Status nieznany? Zamówienie w trakcie realizacji? Błąd importu? Model, który to wie, napisze warunek świadomie.

4. Daty i strefy czasowe

Systemy często zapisują czas w UTC, a biznes liczy dni po polsku. Zamówienie złożone 1 października o 0:30 czasu polskiego ma w UTC datę 30 września, godzinę 22:30, więc trafi do wrześniowej sprzedaży. W skali miesiąca to zwykle drobiazg. W skali jednego dnia z dużym ruchem wieczorem potrafi wyraźnie zmienić wynik. W PostgreSQL przeliczenie na polski czas zapewnia AT TIME ZONE 'Europe/Warsaw', a informacja o strefie zapisu należy do słownika danych.

Druga klasyczna wpadka to BETWEEN '2026-09-01' AND '2026-09-30' na kolumnie, która przechowuje także godzinę. Górna granica oznacza wtedy 30 września, godzinę 0:00, więc prawie cały ostatni dzień miesiąca wypada z zakresu. Bezpieczniej używać przedziału domkniętego z jednej strony, tak jak w przykładach powyżej: od pierwszego dnia miesiąca włącznie do pierwszego dnia następnego miesiąca wyłącznie.

5. Definicja wskaźnika wymyślona przez model

Ostatnia pułapka jest najmniej techniczna. Poproszony o „aktywnych klientów” model wybierze definicję, która wydaje mu się najbardziej typowa: zamówienie w ostatnich trzydziestu dniach, logowanie na konto albo cokolwiek z tabeli, która ma w nazwie słowo „activity”. Zapytanie będzie bezbłędne technicznie i policzy pojęcie, którego nikt w firmie nie używa. Pomaga definicja w słowniku i prośba, by model przed napisaniem zapytania powtórzył ją własnymi słowami. Szerzej opisywałam to w artykule o sprawdzaniu wyników AI, przy kontroli definicji.

Bezpieczny dostęp: tylko odczyt, osobny użytkownik, limity i log

Wszystko powyżej dotyczy poprawności. Zostaje bezpieczeństwo, któremu w kursie poświęcamy drugi temat czwartego tygodnia: bezpieczne podłączenie bazy danych. Gdy agent ma połączenie z bazą, sam uruchamia zapytania, poprawia je i uruchamia ponownie. To ogromna wygoda i spora odpowiedzialność, bo model, który potrafi napisać SELECT, równie sprawnie napisze DELETE. Różnicę między czatem a agentem, który sam wykonuje kolejne kroki, Kajo opisał w artykule o tym, jak wybrać między modelem, czatem i agentem.

Moje zasady są proste:

  1. Osobny użytkownik dla AI. Agent nigdy nie korzysta z mojego konta, a już na pewno z konta administratora. Dzięki temu w logu widać, co zrobił on, a co ja.
  2. Tylko odczyt, wymuszony uprawnieniami. Użytkownik dostaje prawo SELECT na wybranym schemacie i nic więcej. Przykład dla PostgreSQL znajdziesz niżej.
  3. Sekrety w pliku .env. Dane logowania nie trafiają do briefu, do plików instrukcji ani do repozytorium. Trzymam je w pliku .env, który jest wpisany do .gitignore, a agent odczytuje je jako zmienne środowiskowe.
  4. Kopia albo replika, gdy to możliwe. Analiza rzadko potrzebuje danych sprzed sekundy. Replika do odczytu albo nocna kopia oddzielają eksperymenty od systemu, na którym pracuje firma.
  5. Bez danych osobowych. Schemat udostępniony AI zawiera tabele potrzebne do analizy. Imiona, adresy i telefony zostają poza zasięgiem uprawnień albo trafiają do widoku po pseudonimizacji.
  6. Limity. Limit czasu pojedynczego zapytania chroni bazę przed złączeniem, które przypadkiem próbuje zestawić każdy wiersz z każdym. W hurtowniach rozliczanych za ilość przetworzonych danych warto dodatkowo ustawić limit kosztu zapytania.
  7. Log. Każde zapytanie wykonane przez użytkownika AI zostaje zapisane. Log odpowiada na pytanie, które prędzej czy później pada na każdym spotkaniu: skąd ta liczba?

W PostgreSQL taki użytkownik może wyglądać tak (hasło ustawia administrator i nie pojawia się w żadnym pliku projektu):

-- Osobny użytkownik dla AI: odczyt wybranego schematu
CREATE ROLE ai_odczyt LOGIN;
GRANT CONNECT ON DATABASE sklep TO ai_odczyt;
GRANT USAGE ON SCHEMA raporty TO ai_odczyt;
GRANT SELECT ON ALL TABLES IN SCHEMA raporty TO ai_odczyt;
ALTER DEFAULT PRIVILEGES IN SCHEMA raporty
  GRANT SELECT ON TABLES TO ai_odczyt;

-- Dodatkowe pasy bezpieczeństwa
ALTER ROLE ai_odczyt SET default_transaction_read_only = on;
ALTER ROLE ai_odczyt SET statement_timeout = '30s';
ALTER ROLE ai_odczyt SET log_statement = 'all';

Polecenie GRANT daje prawo odczytu tabel w schemacie raporty, a ALTER DEFAULT PRIVILEGES rozciąga je na tabele, które rola wykonująca polecenie utworzy tam później. Parametr default_transaction_read_only ze stron o ustawieniach sesji sprawia, że każda nowa transakcja startuje w trybie tylko do odczytu, a statement_timeout przerywa polecenia trwające dłużej niż zadany czas. Traktuję je jako pasy bezpieczeństwa: użytkownik może zmienić tryb transakcji we własnej sesji, więc prawdziwą barierą pozostają uprawnienia. Parametr log_statement z wartością all zapisuje w logu serwera każde polecenie tej roli. Ustawić go może wyłącznie administrator. I dobrze, bo to on powinien decydować o tym, co się loguje.

Jak czytam zapytanie od AI

Zapytanie SQL pisze się w innej kolejności, niż baza je wykonuje. Zaczyna się od SELECT, a logicznie najpierw liczą się FROM i złączenia, potem WHERE, GROUP BY, HAVING, dopiero na końcu SELECT, sortowanie i limit. Czytam więc w kolejności wykonania i przy każdym kroku zadaję sobie pytanie: co teraz oznacza jeden wiersz?

Pomaga w tym kilka nawyków:

  • Najpierw wyjaśnienie, potem kod. Proszę model, żeby opisał każdy krok zapytania jednym zdaniem i podał ziarnistość wyniku po każdym kroku. Jeśli zapytanie ma kilka bloków WITH, każdy dostaje własny opis. Rozbieżność między opisem a kodem to pierwszy trop.
  • Założenia na głos. Proszę o listę założeń i miejsc, w których model zgadywał: którą kolumnę uznał za datę sprzedaży, co zrobił z brakami, czy wykluczył zamówienia testowe. Zgadywanie wypowiedziane na głos da się sprawdzić w minutę.
  • Liczba wierszy po każdym złączeniu. Ten sam test co w pułapce pierwszej, wykonany dla każdego JOIN.
  • Mała próbka o znanym wyniku. Biorę jedno zamówienie, jednego klienta albo jeden dzień i liczę wynik niezależnie: w arkuszu, prostszym zapytaniem albo na kartce. Jeszcze lepiej działa mała tabela testowa, w której celowo umieszczam przypadki skrajne: zamówienie z trzema książkami, wiersz bez statusu, zamówienie z 1 października o 0:30. Znam wynik z góry, więc od razu widzę, który przypadek zapytanie obsłużyło źle.
  • Porównanie z niezależnym źródłem. Suma z zapytania powinna zgadzać się z raportem finansowym albo z innym uznanym źródłem w ustalonej tolerancji.

Protokół w pięciu krokach

Wszystko to składa się w powtarzalny schemat, którego uczymy w kursie. Ma pięć kroków: pytanie, schemat, zapytanie, wyjaśnienie, weryfikacja.

  1. Pytanie. Zamieniam pytanie biznesowe na precyzyjne. „Ile zarobiliśmy we wrześniu” staje się „sumą wartości netto opłaconych zamówień złożonych od 1 do 30 września czasu polskiego, bez zamówień testowych, w złotych”. Jeśli nie umiem tego zrobić, wracam z pytaniem do osoby, która zleciła analizę. To najtańszy moment na wyjaśnienie nieporozumień.
  2. Schemat. Model dostaje albo sam pobiera definicje właściwych tabel, relacje i słownik danych. Proszę go, żeby przed napisaniem zapytania wypisał brakujące informacje.
  3. Zapytanie. Proszę o zapytanie podzielone na nazwane kroki, z których każdy robi jedną rzecz. Czytelność jest tu ważniejsza od zwięzłości, bo zapytanie będę czytać ja.
  4. Wyjaśnienie. Model opisuje kroki, ziarnistość i założenia. Porównuję opis z kodem.
  5. Weryfikacja. Liczba wierszy po złączeniach, próbka o znanym wyniku, porównanie z niezależnym źródłem. Sprawdzone zapytanie zapisuję razem z opisem w repozytorium projektu, żeby następnym razem zaczynać od wersji poprawionej.

Protokół wygląda na dłuższy niż prośba „napisz mi zapytanie”. W praktyce kroki od drugiego do czwartego wykonuje głównie model, a ja najwięcej czasu poświęcam pierwszemu i ostatniemu. Tak też powinno być, bo to są kroki, w których potrzebna jest wiedza o firmie.

Co zostaje po stronie analityka

Z czasem zauważyłam, że pisania składni mam coraz mniej. Zostało wszystko, co dzieje się przed zapytaniem i po nim. Zamiana luźnego pytania na precyzyjne. Wiedza o danych: ziarnistość tabel, dziwactwa statusów, strefy czasowe, historia zmian w definicjach. Decyzja o tym, do czego AI ma dostęp. Ocena wyniku i odpowiedzialność za liczbę, która trafia do decyzji.

Znajomość SQL nadal się przydaje, zmienia się tylko jej rola: z pisania przechodzi w czytanie. Kto rozumie JOIN, GROUP BY i zachowanie NULL, wyłapie błąd w kilka sekund. Kto tego nie rozumie, musi zapytaniu ufać, a ufanie zapytaniu, którego się nie rozumie, jest w analizie danych dość kosztownym hobby.

Jeśli po tym artykule zrobisz tylko jedną rzecz, niech to będzie ta: przy następnym zapytaniu od AI porównaj liczbę wierszy przed złączeniem i po nim. Kosztuje to jedno dodatkowe zapytanie. Raz na jakiś czas oszczędzi Ci spotkania, na którym ktoś z finansów zapyta, dlaczego Twoja liczba różni się od ich raportu.

Najważniejsze pytania i odpowiedzi

Czy dać AI dostęp do bazy produkcyjnej?

Najbezpieczniej zacząć od kopii albo repliki z danymi do analizy. Jeśli połączenie z produkcją jest konieczne, to przez osobnego użytkownika z prawem odczytu wybranych schematów, z limitem czasu zapytań i z logowaniem wszystkiego, co wykonał. Dane logowania trzymam w pliku .env poza repozytorium, a tabele z danymi osobowymi wyłączam z uprawnień.

Jak sprawdzić zapytanie SQL wygenerowane przez AI?

Proszę model o wyjaśnienie każdego kroku, a potem czytam zapytanie w kolejności wykonania: tabele i złączenia, filtry, grupowanie. Po każdym złączeniu porównuję liczbę wierszy z liczbą unikalnych kluczy, uruchamiam zapytanie na małej próbce o znanym wyniku i porównuję sumę z niezależnym raportem.

Czy trzeba znać SQL, żeby korzystać z AI do zapytań?

Pisanie można w dużej mierze oddać modelowi. Czytanie zostaje po Twojej stronie: trzeba rozumieć SELECT, JOIN, WHERE, GROUP BY i zachowanie wartości NULL. To kilka godzin nauki, które zwracają się przy pierwszym zapytaniu, które wykonuje się bez błędu i liczy coś innego, niż miało.

Źródła

Autor

Katarzyna Pęsik

Senior Data Scientist w Wakacje.pl

Wcześniej analityczka w Lyreco Group i Amazon. Pracuje z Power BI i Pythonem, tworzy raporty, modele danych i praktyczne automatyzacje. Twórczyni Ninja Data.

Ninja Data

Przy pracy nad artykułem użyto w zautomatyzowany sposób sztucznej inteligencji. Jeśli też chcesz tak umieć, sprawdź ten projekt.

Chcesz przećwiczyć to na własnych danych?

Analiza z AI to 6 tygodni praktyki z Kajo i Kasią. Start 11 stycznia 2027.

Dołącz do kursu