• nowy

    Duże wydanie 2026 jest już dostępne – wprowadzenie Data Observability do Twojego kodu

  • nowy

    Współtwórz przyszłość innowacji w obszarze sztucznej inteligencji i danych

  • nowy

    • Wersja 2026.06 — wprowadzenie Data Observability do Twojego kodu

  • nowy

    • Współtwórz przyszłość innowacji w obszarze sztucznej inteligencji i danych

Rekordy osierocone: jak je znaleźć w SQL (i nie wpuszczać)

|

6

min. czyt.

Diagram: wiersze payments odwołujące się do konta A-777, którego brakuje w tabeli accounts

Wiersz płatności zawiera account_id = 884213. W tabeli accounts nie ma takiego konta. Nic nie zgłasza błędu, ładowanie kończy się na zielono, a następnego ranka w raporcie oddziału brakuje dokładnie tej płatności, bo raport łączy płatności z kontami, a złączenie po cichu ją odrzuca. Rekord osierocony to wiersz podrzędny, którego wartość klucza obcego nie ma pasującego wiersza w tabeli nadrzędnej, do której się odwołuje.

Rekordy osierocone (osierocone wiersze) pojawiają się wszędzie tam, gdzie klucze obce nie są wymuszane: w hurtowniach danych, warstwach stagingowych, replikach i potokach, które ładują rekordy podrzędne przed nadrzędnymi. Ich wyszukiwanie to standardowe zadanie w SQL – złączenie wykluczające (anti join). Są cztery popularne sposoby, by je zapisać, a jeden z nich zgłasza „brak sierot”, gdy tylko pojawi się choćby jeden NULL.

Ten poradnik omawia wzorce, pułapki i sposób, by zamienić zapytanie w kontrolę uruchamianą przy każdym ładowaniu. Samo pojęcie wyjaśniamy w artykule Czy Twoje dane wciąż odnajdują swoich rodziców? Integralność referencyjna w praktyce.

Najważniejsze wnioski

  • INNER JOIN ukrywa rekordy osierocone: niedopasowane wiersze podrzędne znikają z wyniku, zamiast zgłosić błąd.

  • Sieroty znajdziesz złączeniem wykluczającym: LEFT JOIN … IS NULL, NOT EXISTS lub EXCEPT na unikalnych kluczach.

  • Unikaj NOT IN na kolumnie dopuszczającej NULL: jeden NULL w podzapytaniu zwraca zero wierszy, co wygląda jak czysty wynik.

  • Klucze złożone dopasowuj na wszystkich kolumnach naraz i świadomie zdecyduj, czy klucz obcy o wartości NULL jest błędem.

  • Zapytanie, o którym trzeba pamiętać, nie jest mechanizmem kontrolnym. W digna to samo złączenie wykluczające jest jedną regułą Referential Integrity, która działa przy każdej inspekcji.

Spis treści

  • Czym jest rekord osierocony?

  • Dlaczego INNER JOIN ukrywa rekordy osierocone?

  • Jak znaleźć rekordy osierocone w SQL?

    • LEFT JOIN … IS NULL

    • NOT EXISTS

    • EXCEPT (MINUS) na unikalnych kluczach

  • Dlaczego NOT IN nie zwraca żadnych wierszy, gdy pojawia się NULL?

  • Którego wzorca złączenia wykluczającego używać?

  • Jak obsłużyć klucze złożone i klucze obce o wartości NULL?

  • Jak znaleźć rekordy osierocone w dużych tabelach?

  • Co, jeśli tabela nadrzędna jest w innej bazie danych?

  • Jak zamienić zapytanie o sieroty w stałą kontrolę?

  • Znajdź je raz, a potem nie wpuszczaj

Czym jest rekord osierocony?

Rekord osierocony to wiersz w tabeli podrzędnej, którego klucz obcy wskazuje na nieistniejący klucz nadrzędny: płatność, której account_id nie ma w accounts, albo podanie leku, którego product_code nie ma w medications. Sam wiersz może być w pełni poprawny. Zepsuta jest relacja.

Sieroty zawsze leżą po stronie podrzędnej; konto bez płatności to nic niezwykłego. Typowe przyczyny są prozaiczne: rekord nadrzędny został usunięty lub nigdy nie został załadowany, rekord podrzędny dotarł przed jutrzejszym ładowaniem danych głównych albo formaty kluczy się różnią ('00884213' zamiast 884213 lub spacja na końcu).

Operacyjne bazy danych, takie jak PostgreSQL czy Oracle, wymuszają zadeklarowane klucze obce, ale kopie w hurtowni zwykle nie przenoszą tych ograniczeń. Większość chmurowych hurtowni danych przyjmuje deklarację FOREIGN KEY, ale jej nie wymusza; dokumentacja BigQuery stwierdza, że nie wymusza ograniczeń klucza głównego ani obcego. Dlaczego Snowflake, BigQuery, Redshift i Databricks nie wymuszają kluczy obcych, opisujemy w osobnym artykule.

Dlaczego INNER JOIN ukrywa rekordy osierocone?

INNER JOIN zwraca tylko wiersze dopasowane po obu stronach, więc wiersza podrzędnego bez rekordu nadrzędnego po prostu nie ma w wyniku. Nie ma błędu, ostrzeżenia ani wartości NULL, którą można by zauważyć. Sumy wychodzą niższe, niż powinny, a w samym raporcie nic nie pokazuje różnicy.

SELECT a.branch_code,       SUM(p.amount) AS total_paidFROM   payments pJOIN   accounts a ON a.account_id = p.account_idWHERE  p.booking_date = DATE '2026-10-08'GROUP  BY
SELECT a.branch_code,       SUM(p.amount) AS total_paidFROM   payments pJOIN   accounts a ON a.account_id = p.account_idWHERE  p.booking_date = DATE '2026-10-08'GROUP  BY
SELECT a.branch_code,       SUM(p.amount) AS total_paidFROM   payments pJOIN   accounts a ON a.account_id = p.account_idWHERE  p.booking_date = DATE '2026-10-08'GROUP  BY

Każda płatność, której account_id nie ma w accounts, wypada przed SUM. Najszybciej zobaczysz lukę, licząc na dwa sposoby:

SELECT  (SELECT COUNT(*) FROM payments   WHERE booking_date = DATE '2026-10-08')               AS all_payments,  (SELECT COUNT(*) FROM payments p   JOIN accounts a ON a.account_id = p.account_id   WHERE p.booking_date = DATE '2026-10-08')             AS
SELECT  (SELECT COUNT(*) FROM payments   WHERE booking_date = DATE '2026-10-08')               AS all_payments,  (SELECT COUNT(*) FROM payments p   JOIN accounts a ON a.account_id = p.account_id   WHERE p.booking_date = DATE '2026-10-08')             AS
SELECT  (SELECT COUNT(*) FROM payments   WHERE booking_date = DATE '2026-10-08')               AS all_payments,  (SELECT COUNT(*) FROM payments p   JOIN accounts a ON a.account_id = p.account_id   WHERE p.booking_date = DATE '2026-10-08')             AS

Jeśli account_id jest unikalny w accounts, różnica to Twoje sieroty plus wiersze z account_id o wartości NULL.

Zadeklarowane, ale niesprawdzane klucze mogą to jeszcze pogorszyć. Planer zapytań Amazon Redshift zakłada, że zadeklarowane klucze są prawidłowe, a AWS ostrzega, że nieprawidłowe klucze mogą sprawić, iż niektóre zapytania zwrócą błędne wyniki.

Jak znaleźć rekordy osierocone w SQL?

Rekordy osierocone znajdziesz złączeniem wykluczającym (anti join): zapytaniem, które zwraca wiersze podrzędne, dla których nie istnieje pasujący wiersz nadrzędny. W SQL zapiszesz je jako LEFT JOIN … WHERE parent_key IS NULL, jako NOT EXISTS lub jako EXCEPT na unikalnych wartościach klucza. Dla kluczy bez NULL wszystkie trzy dają ten sam wynik.

LEFT JOIN … IS NULL

SELECT p.payment_id, p.account_id, p.amount, p.booking_dateFROM   payments pLEFT   JOIN accounts a       ON a.account_id = p.account_idWHERE  a.account_id IS NULL  AND  p.account_id IS NOT NULL
SELECT p.payment_id, p.account_id, p.amount, p.booking_dateFROM   payments pLEFT   JOIN accounts a       ON a.account_id = p.account_idWHERE  a.account_id IS NULL  AND  p.account_id IS NOT NULL
SELECT p.payment_id, p.account_id, p.amount, p.booking_dateFROM   payments pLEFT   JOIN accounts a       ON a.account_id = p.account_idWHERE  a.account_id IS NULL  AND  p.account_id IS NOT NULL

Testuj kolumnę nadrzędną, która w dopasowanym wierszu nie może mieć wartości NULL – najlepiej klucz złączenia. Warunki dotyczące tabeli nadrzędnej umieszczaj w klauzuli ON: WHERE a.status = 'ACTIVE' nigdy nie jest prawdziwe dla niedopasowanego wiersza, więc zapytanie nic by nie zwróciło.

NOT EXISTS

SELECT p.payment_id, p.account_id, p.amount, p.booking_dateFROM   payments pWHERE  p.account_id IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   accounts a         WHERE  a.account_id = p.account_id       )
SELECT p.payment_id, p.account_id, p.amount, p.booking_dateFROM   payments pWHERE  p.account_id IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   accounts a         WHERE  a.account_id = p.account_id       )
SELECT p.payment_id, p.account_id, p.amount, p.booking_dateFROM   payments pWHERE  p.account_id IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   accounts a         WHERE  a.account_id = p.account_id       )

Ten zapis czyta się jak samo pytanie. Zduplikowane klucze nadrzędne nie mnożą wierszy, a wartości NULL w accounts.account_id go nie zepsują: porównanie z NULL nigdy nie daje dopasowania.

EXCEPT (MINUS) na unikalnych kluczach

SELECT account_id FROM payments WHERE account_id IS NOT NULLEXCEPTSELECT account_id FROM
SELECT account_id FROM payments WHERE account_id IS NOT NULLEXCEPTSELECT account_id FROM
SELECT account_id FROM payments WHERE account_id IS NOT NULLEXCEPTSELECT account_id FROM

Ten wariant zwraca unikalne brakujące wartości klucza, a nie wiersze. Często to lepsze pierwsze pytanie: kilka brakujących kont może wyjaśnić tysiące osieroconych płatności. Oracle tradycyjnie zapisuje ten operator jako MINUS; BigQuery wymaga EXCEPT DISTINCT. Operatory zbiorów traktują dwa NULL-e jako równe – to kolejny powód, by jawnie odfiltrować klucze NULL.

Dlaczego NOT IN nie zwraca żadnych wierszy, gdy pojawia się NULL?

NOT IN nie zwraca żadnych wierszy, jeśli jego podzapytanie zawiera choćby jeden NULL, ponieważ logika trójwartościowa SQL sprawia, że każde porównanie z tym NULL ma wartość „nieznana”. x NOT IN (1, 2, NULL) oznacza x <> 1 AND x <> 2 AND x <> NULL. Ostatni człon nigdy nie jest prawdziwy, więc cały warunek też nigdy nie jest prawdziwy.

-- Looks right. Returns nothing if any accounts.account_id is NULL.SELECT p.payment_id, p.account_idFROM   payments pWHERE  p.account_id NOT IN (SELECT a.account_id FROM accounts a);
-- Looks right. Returns nothing if any accounts.account_id is NULL.SELECT p.payment_id, p.account_idFROM   payments pWHERE  p.account_id NOT IN (SELECT a.account_id FROM accounts a);
-- Looks right. Returns nothing if any accounts.account_id is NULL.SELECT p.payment_id, p.account_idFROM   payments pWHERE  p.account_id NOT IN (SELECT a.account_id FROM accounts a);

Dla płatności, której konto istnieje, jedno porównanie jest fałszywe i wiersz zostaje słusznie wykluczony. Dla brakującego konta każde porównanie z prawdziwym kluczem jest prawdziwe, ale to z NULL ma wartość „nieznana”, więc cały warunek jest nieznany, a WHERE zachowuje tylko wiersze prawdziwe. Wynikiem jest pusty zbiór, nie do odróżnienia od „brak sierot”.

Ten błąd jest cichy i daje fałszywe „wszystko w porządku”, a kolumny kluczy w hurtowni często nie są zadeklarowane jako NOT NULL. Płatności, których własny account_id to NULL, również nigdy nie zostaną wychwycone. Używaj NOT EXISTS albo przynajmniej dodaj WHERE a.account_id IS NOT NULL wewnątrz podzapytania.

Którego wzorca złączenia wykluczającego używać?

Domyślnie używaj NOT EXISTS do kontroli sierot na poziomie wierszy, EXCEPT, gdy potrzebujesz listy brakujących wartości klucza, a LEFT JOIN … IS NULL, gdy w tym samym przebiegu chcesz też liczby dopasowanych wierszy. NOT IN stosuj tylko wtedy, gdy masz gwarancję, że żadna z kolumn nie zawiera NULL.

Wzorzec

Zwraca

NULL w kluczu nadrzędnym

Klucz obcy NULL w tabeli podrzędnej

Czytelność

Typowa wydajność

LEFT JOIN … IS NULL

Wiersze podrzędne

Bezpieczny

Zgłaszany jako sierota, jeśli nie zostanie odfiltrowany

Znajomy; intencja kryje się w klauzuli WHERE

Zwykle planowany jako anti join

NOT EXISTS

Wiersze podrzędne

Bezpieczny

Zgłaszany jako sierota, jeśli nie zostanie odfiltrowany

Czyta się jak samo pytanie

Zwykle planowany jako anti join

EXCEPT / MINUS

Unikalne wartości klucza

Bezpieczny

Zwracany raz jako NULL, jeśli nie zostanie odfiltrowany

Krótki i przejrzysty dla list kluczy

Usuwa duplikaty po obu stronach; dobry do podsumowań na poziomie kluczy

NOT IN

Wiersze podrzędne

Niebezpieczny: jeden NULL zwraca zero wierszy

Po cichu wykluczany

Dobrze się czyta, ale wprowadza w błąd

W porządku na kolumnach bez NULL; na kolumnach z NULL może dostać gorszy plan

Większość optymalizatorów planuje LEFT JOIN i NOT EXISTS tak samo; sprawdź plan na swojej platformie.

Jak obsłużyć klucze złożone i klucze obce o wartości NULL?

Przy kluczu złożonym dopasowuj wszystkie kolumny klucza razem, w jednym predykacie, nigdy kolumna po kolumnie: wiersz jest sierotą, gdy brakuje jego kombinacji, nawet jeśli każda wartość z osobna gdzieś istnieje. Klucze obce o wartości NULL wymagają świadomej decyzji, zanim zaczniesz je liczyć: brakujące odwołanie czy uzasadniony brak wartości.

Załóżmy, że każdy szpital ma własny receptariusz szpitalny z kluczem (hospital_id, product_code):

SELECT m.administration_id, m.hospital_id, m.product_codeFROM   medication_administrations mWHERE  m.hospital_id IS NOT NULL  AND  m.product_code IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   hospital_formulary f         WHERE  f.hospital_id  = m.hospital_id           AND  f.product_code = m.product_code       )
SELECT m.administration_id, m.hospital_id, m.product_codeFROM   medication_administrations mWHERE  m.hospital_id IS NOT NULL  AND  m.product_code IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   hospital_formulary f         WHERE  f.hospital_id  = m.hospital_id           AND  f.product_code = m.product_code       )
SELECT m.administration_id, m.hospital_id, m.product_codeFROM   medication_administrations mWHERE  m.hospital_id IS NOT NULL  AND  m.product_code IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   hospital_formulary f         WHERE  f.hospital_id  = m.hospital_id           AND  f.product_code = m.product_code       )

Dwie osobne kontrole – „szpital istnieje” i „produkt istnieje” – obie przejdą dla produktu wpisanego tylko w szpitalu A, a podanego w szpitalu B. Wyłapie go tylko połączony predykat. EXCEPT naturalnie obsługuje klucze złożone. Nie sklejaj kluczy w jeden ciąg znaków: '1' || '23' i '12' || '3' dają ten sam wynik.

Płatność z account_id o wartości NULL nie wskazuje na brakujące konto; nie wskazuje nigdzie. Płatność musi mieć konto, natomiast opcjonalne odwołanie, takie jak referring_doctor_id, może być puste. Wyklucz wartości NULL z zapytania o sieroty i policz je osobno:

SELECT COUNT(*)                      AS total_rows,       COUNT(*) - COUNT(account_id)  AS
SELECT COUNT(*)                      AS total_rows,       COUNT(*) - COUNT(account_id)  AS
SELECT COUNT(*)                      AS total_rows,       COUNT(*) - COUNT(account_id)  AS

Jak znaleźć rekordy osierocone w dużych tabelach?

W dużych tabelach najpierw licz, potem listuj, porównuj unikalne wartości klucza zamiast każdego wiersza i ograniczaj stronę podrzędną do ostatniego ładowania lub partycji. Stronę nadrzędną zostaw kompletną: płatność zaksięgowana dziś może odwoływać się do konta otwartego lata temu, więc nigdy nie filtruj tabeli nadrzędnej po dacie ładowania.

  1. Najpierw policz. Uruchom złączenie wykluczające jako COUNT(*). Pobieraj wiersze tylko wtedy, gdy wynik jest niezerowy, i to z limitem.

  2. Porównuj unikalne klucze. Najpierw zredukuj stronę podrzędną do SELECT DISTINCT account_id. Unikalnych kluczy jest zwykle znacznie mniej niż wierszy.

  3. Filtruj do ostatniego ładowania. Ogranicz tabelę podrzędną do najnowszej partycji lub load_date, aby silnik mógł pominąć resztę danych (pruning).

  4. Zadbaj o porównywalność kolumny złączenia. Ten sam typ danych po obu stronach, bez CAST ani TRIM w predykacie, które mogą blokować użycie indeksów i pruning. Formaty poprawiaj raczej podczas ładowania; zobacz nasz przewodnik po czyszczeniu danych w SQL.

  5. Zapisuj wynik. Przechowuj datę, liczbę ocenianych wierszy i liczbę znalezionych sierot, aby widzieć trend.

WITH child_keys AS (  SELECT DISTINCT account_id  FROM   payments  WHERE  load_date = DATE '2026-10-08'    AND  account_id IS NOT NULL)SELECT COUNT(*) AS missing_account_idsFROM   child_keys kWHERE  NOT EXISTS (         SELECT 1 FROM accounts a WHERE a.account_id = k.account_id       )
WITH child_keys AS (  SELECT DISTINCT account_id  FROM   payments  WHERE  load_date = DATE '2026-10-08'    AND  account_id IS NOT NULL)SELECT COUNT(*) AS missing_account_idsFROM   child_keys kWHERE  NOT EXISTS (         SELECT 1 FROM accounts a WHERE a.account_id = k.account_id       )
WITH child_keys AS (  SELECT DISTINCT account_id  FROM   payments  WHERE  load_date = DATE '2026-10-08'    AND  account_id IS NOT NULL)SELECT COUNT(*) AS missing_account_idsFROM   child_keys kWHERE  NOT EXISTS (         SELECT 1 FROM accounts a WHERE a.account_id = k.account_id       )

To zapytanie liczy brakujące klucze, a nie osierocone wiersze; wiersze dla tych kluczy pobierz potem.

Co, jeśli tabela nadrzędna jest w innej bazie danych?

Złączenie wykluczające działa tylko wtedy, gdy jeden silnik zapytań może odczytać obie tabele. Jeśli płatności są w hurtowni danych, a konta w bazie systemu bankowości centralnej (core banking), zwykły SQL nie widzi obu, więc kopiujesz jedną stronę, używasz łącza bazodanowego (database link) lub zapytania federacyjnego albo uruchamiasz kontrolę w narzędziu, które ma dostęp do obu połączeń.

Skopiowana tabela danych głównych to kolejny potok z własnym opóźnieniem: nieaktualna kopia zgłasza fałszywe sieroty albo przeocza prawdziwe. Łącza bazodanowe zależą od wsparcia platformy i wymagają zgody działu bezpieczeństwa dla każdego nowego połączenia.

Jak zamienić zapytanie o sieroty w stałą kontrolę?

Zapytanie o sieroty staje się stałą kontrolą, gdy uruchamiasz je przy każdym ładowaniu, zapisujesz liczby wierszy poprawnych i błędnych, ustawiasz próg niepowodzenia i przechowujesz błędne wiersze tam, gdzie zespół odpowiedzialny może je zobaczyć. W digna to jedna reguła Referential Integrity, konfigurowana w oknie dialogowym, bez pisania SQL.

digna Data Validation ma trzy rodzaje reguł: Rule, Uniqueness i Referential Integrity. Reguła referencyjna to złączenie wykluczające z tego artykułu: dla kolumn w jednym źródle danych i odpowiadającego im zestawu w innym digna bierze unikalne wartości z drugiej tabeli i oznacza jako błędny każdy wiersz, który się z nimi nie łączy. Reguła wykonuje się w Twojej źródłowej bazie danych; Twoje dane nigdy nie opuszczają Twojej infrastruktury. Od Release 2026.01 działa między różnymi połączeniami bazodanowymi w tym samym projekcie, bez replikowania danych.

Zrzuty ekranu poniżej pokazują dane demonstracyjne digna dla Danubia Kliniken, fikcyjnej austriackiej grupy szpitali.

2026-04-22 oddziały zarejestrowały 82 podania leku Coavira 2.5 mg (kod produktu 3858646), którego nie było jeszcze w kartotece produktów apteki. Każdy raport łączący dawki z produktami pokazywał 0 dawek tego leku, podczas gdy pielęgniarki podały ich 82. Reguła hc_product_in_master zakończyła się niepowodzeniem: kontrolę przeszło 4 244 z 4 326 wierszy. Ręcznie trzeba by napisać takie zapytanie:

SELECT m.*FROM   hospital_medication_administrations mWHERE  m.product_code IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   hospital_medications p         WHERE  p.product_code = m.product_code       )
SELECT m.*FROM   hospital_medication_administrations mWHERE  m.product_code IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   hospital_medications p         WHERE  p.product_code = m.product_code       )
SELECT m.*FROM   hospital_medication_administrations mWHERE  m.product_code IS NOT NULL  AND  NOT EXISTS (         SELECT 1         FROM   hospital_medications p         WHERE  p.product_code = m.product_code       )

Widok Invalid Records pokazuje ten zbiór wyników bez pisania czegokolwiek:

Widok Invalid Records w digna, filtr Failed, kontrola Full - hc_product_in_master: podania leków ze szpitalem, oddziałem, działem, product_code 3858646 i medication_name Coavira 2.5 mg

Invalid Records z filtrem Failed: każda osierocona dawka ze szpitalem, oddziałem, działem, kodem produktu i nazwą leku.

Konfiguracja reguły:

  1. Configuration → źródło danych hospital_medication_administrations → zakładka Data Validation → Add Rule. Otworzy się okno Add Data Validation Rule.

  2. Wpisz Name (hc_product_in_master) i Description.

  3. Ustaw Type na Referential Integrity i wybierz w Attributes kolumnę product_code.

  4. W sekcji must exist in wybierz Data Source hospital_medications i jego Attributes product_code. Dla klucza złożonego wybierz kilka kolumn po obu stronach w tej samej kolejności; listy o różnej długości są odrzucane.

  5. Wybierz Threshold Mode (tryb progu) Absolute lub Relative i ustaw Info threshold oraz Warn threshold (tutaj: Absolute, Info 0, Warn 1). Zapisz. Reguła działa przy każdej inspekcji, według harmonogramu lub na żądanie.

Okno Add Data Validation Rule w digna dla hc_product_in_master: Type Referential Integrity, Attributes product_code, must exist in Data Source hospital_medications, Attributes product_code, Threshold Mode Absolute, Info 0, Warn 1

Cała reguła: dwie listy atrybutów, docelowe źródło danych i dwa progi.

Referential Integrity pomija wartości NULL, tak jak filtr IS NOT NULL powyżej; jeśli wartość jest wymagana, dodaj osobną regułę typu Rule z product_code IS NOT NULL. Powyżej Info threshold status to Uncertain, powyżej Warn threshold – Failed, więc przy Info równym 0 każda sierota zdejmuje regułę ze statusu Passed. Błędne rekordy to to samo zapytanie z zanegowanym warunkiem poprawności; można je wyeksportować, a wyniki mogą trafiać jako powiadomienia do zespołu, który jest właścicielem danych.

Od Release 2026.06 reguły mogą też istnieć jako kod dzięki Python SDK (pip install digna-sdk). Pełny opis znajdziesz w artykule o konfigurowaniu kontroli integralności referencyjnej w digna lub w filmie o konfiguracji (2:26).

Znajdź je raz, a potem nie wpuszczaj

Do badania rekordów osieroconych: NOT EXISTS dla wierszy, EXCEPT dla brakujących kluczy, nigdy NOT IN na kolumnie dopuszczającej NULL i świadoma decyzja w sprawie wartości NULL. Żeby sieroty się nie pojawiały, trzeba więcej: wymuszaj klucze obce tam, gdzie baza danych to potrafi, ładuj najpierw rekordy nadrzędne, jawnie obsługuj spóźnione dane główne i uruchamiaj kontrolę referencyjną przy każdym ładowaniu.

Jeśli chcesz zobaczyć tę kontrolę na własnych tabelach, we własnej infrastrukturze, umów się na demo z zespołem digna.

Najczęściej zadawane pytania

Jak znaleźć rekordy osierocone w SQL?

Użyj złączenia wykluczającego, które zwraca wiersze podrzędne bez pasującego rekordu nadrzędnego. Najbardziej niezawodna forma to NOT EXISTS ze skorelowanym podzapytaniem po kluczu; LEFT JOIN … WHERE parent_key IS NULL daje ten sam wynik. Najpierw odfiltruj klucze obce o wartości NULL i policz je w osobnej kontroli kompletności.

Dlaczego NOT IN nie zwraca żadnych wierszy, gdy podzapytanie zawiera NULL?

Powodem jest logika trójwartościowa. x NOT IN (1, 2, NULL) rozwija się do x <> 1 AND x <> 2 AND x <> NULL, a ostatnie porównanie ma wartość „nieznana”, nigdy „prawda”. WHERE zachowuje tylko wiersze prawdziwe, więc zapytanie nic nie zwraca, co wygląda dokładnie jak czysty wynik.

Czy NOT EXISTS jest szybsze niż LEFT JOIN IS NULL?

Zwykle żadne nie jest szybsze: większość nowoczesnych optymalizatorów planuje oba jako anti join, więc wybór sprowadza się do czytelności. Wyjątkiem jest NOT IN. Na kolumnach dopuszczających NULL może dostać gorszy plan i nie zwraca żadnych wierszy, gdy podzapytanie zawiera NULL.

Czym różni się rekord osierocony od klucza obcego o wartości NULL?

Rekord osierocony wskazuje na klucz nadrzędny, który nie istnieje, a klucz obcy o wartości NULL nie wskazuje nigdzie. Wymagają różnych kontroli: integralności referencyjnej dla sierot i reguły IS NOT NULL tam, gdzie odwołanie jest obowiązkowe. Właśnie dlatego reguła Referential Integrity w digna pomija wartości NULL.

Jak automatycznie sprawdzać rekordy osierocone po każdym ładowaniu?

Zamień złączenie wykluczające w kontrolę uruchamianą według harmonogramu, z progami i zapisywanymi wynikami. W digna to jedna reguła Referential Integrity w Data Validation: wybierz kolumny, wskaż źródło danych, w którym muszą istnieć, ustaw progi Info i Warn, zapisz. Reguła działa w Twojej bazie danych przy każdej inspekcji.

✦ Wygenerowano z użyciem sztucznej inteligencji

Udostępnij na X
Udostępnij na X
Udostępnij na Facebooku
Udostępnij na Facebooku
Udostępnij na LinkedIn
Udostępnij na LinkedIn

Poznaj zespół tworzący platformę

Wiedeński zespół ekspertów od AI, danych i oprogramowania, oparty

na rygorze akademickim i doświadczeniu korporacyjnym.

Poznaj zespół tworzący platformę

Wiedeński zespół ekspertów od AI, danych i oprogramowania, oparty na rygorze akademickim i doświadczeniu korporacyjnym.

Produkt

Integracje

Zasoby

Firma

INDEXED BYIndexerNow INDEXED BYIndexerNow