Chciałbym zwrócić uwagę na potencjalne problemy związane z budowaniem zapytań zawierających podzapytania nieskorelowane (uncorrelated subqueries). W pewnych sytuacjach występują zasadnicze rozbieżności między tym, jak zapytanie pojmuje deweloper, a jak - procesor kwerend SQL Servera. Załóżmy istnienie dwóch tabel:
CREATE TABLE tab1
(
colA int,
colB nvarchar(10)
)
CREATE TABLE tab2
(
colX int
)
-- wypełnianie tabel przykładowymi danymi
INSERT INTO tab1 (colA, colB) VALUES (1,'A')
INSERT INTO tab1 (colA, colB) VALUES (2,'B')
INSERT INTO tab1 (colA, colB) VALUES (3,'C')
INSERT INTO tab1 (colA, colB) VALUES (4,'D')
INSERT INTO tab2 (colX) VALUES (44)
INSERT INTO tab2 (colX) VALUES (55)
Następujące zapytanie:
SELECT colA
FROM tab2
oczywiście zwróci błąd 207 (
Msg 207, Level 16, State 1, Line 1: Invalid column name 'colA'.), gdyż kolumna
colA nie istnieje w tabeli
tab2. Jeżeli jednak powyższe zapytanie zostanie użyte jako podzapytanie w następującej kwerendzie:
SELECT colB
FROM tab1
WHERE colA IN
(SELECT colA FROM tab2)
SQL Server nie zwróci błędu i co więcej - wykona zapytanie praktycznie w sposób równoważny sytuacji braku warunku WHERE. Chyba nie trzeba nikogo przekonywać, jakie niebezpieczeństwa może to powodować przy okazji operacji modyfikacji danych.
Nasuwa się proste pytanie: o co chodzi i czy jest to błąd? Semantyka podpowiadałaby, że zapytanie wykonywane jest błędnie, w rzeczywistości jednak SQL Server przetwarza je zgodnie ze standardami języków blokowo-strukturalnych, do których zaliczany jest także SQL.
Powyższa kwerenda logicznie realizowane jest w następujący sposób:
1. sprawdzenie istnienia kolumny
colA w tabeli
tab2
2. kolumna nie istnieje, procesor szuka najbliższej logicznie klauzuli FROM
3. sprawdzenie, czy tabela określona w znalezionej klauzuli FROM zawiera
colA
4. kolumna istnieje w tabeli
tab1, zostaje więc uwzględniona w podzapytaniu
W efekcie powyższego zapytanie przetwarzane jest jako:
SELECT tab1.colB
FROM tab1
WHERE tab1.colA IN
(SELECT tab1.colA FROM tab2)
SQL Server będzie realizował takie zapytanie przez LEFT SEMI JOINa z warunkiem złączenia:
[tab1].[colA]=[tab1].[colA]
Przy takim warunku złączenia wynikowy zbiór rekordów nie jest w żaden sposób zawężany - to dlatego problematyczna kwerenda zwraca wszystkie rekordy z tabeli
tab1. Analogicznie zachowują się także inne silniki BD (w tym Oracle, PostgreSQL, MySQL).
Aby uniknąć problemów, które mogą wynikać z opisanych uwarunkowań, należy deklarować nazwy kolumn w sposób jednoznaczny, tj. albo przez pełną kwalifikację, albo przez aliasy.
Dziękuję znajomemu z pracy - Rafałowi Zwolakowi za wskazanie problematycznego zapytania.