Wykorzystanie w zapytaniu zmiennych tabelarycznych oraz funkcji niedeterministycznych może dawać ciekawe, niespodziewane wyniki.
Poniższe zapytania są właściwie identyczne. Pierwsze bazuje na zwykłej tabeli, drugie na tabeli tymczasowej, a trzecie na zmiennej tabelarycznej. Idea jest jednak taka sama w każdym z 3 przypadków. Kluczowe jest tu podzapytanie, które ma zwrócić dowolną wartość (TOP 1) z tabeli. Wartość ta będzie za każdym razem inna ze względu na użycie w ORDER BY funkcji NEWID().
W przypadku zwykłej tabeli oraz tabeli tymczasowej podzapytanie zwraca za każdym razem inną wartość (przy ponownym wywołani zapytania), ale wartość jest taka sama dla każdego z rekordów, które zwraca zapytanie zewnętrzne. Tego bym się spodziewał.
W przypadku zmiennej tabelaryczne sytuacja się zmienia. Wartość podzapytania jest różna dla każdego rekordu zapytania zewnętrznego. Tego właśnie się nie spodziewałem i zrobiłem duże oczy.
Przykład nr 1 (tabela):
USE TEMPDB;Poniższe zapytania są właściwie identyczne. Pierwsze bazuje na zwykłej tabeli, drugie na tabeli tymczasowej, a trzecie na zmiennej tabelarycznej. Idea jest jednak taka sama w każdym z 3 przypadków. Kluczowe jest tu podzapytanie, które ma zwrócić dowolną wartość (TOP 1) z tabeli. Wartość ta będzie za każdym razem inna ze względu na użycie w ORDER BY funkcji NEWID().
W przypadku zwykłej tabeli oraz tabeli tymczasowej podzapytanie zwraca za każdym razem inną wartość (przy ponownym wywołani zapytania), ale wartość jest taka sama dla każdego z rekordów, które zwraca zapytanie zewnętrzne. Tego bym się spodziewał.
W przypadku zmiennej tabelaryczne sytuacja się zmienia. Wartość podzapytania jest różna dla każdego rekordu zapytania zewnętrznego. Tego właśnie się nie spodziewałem i zrobiłem duże oczy.
Przykład nr 1 (tabela):
GO
IF OBJECT_ID('T') IS NOT NULL
DROP TABLE T
CREATE TABLE T (NUMBER INT);
WITH NUMBERS
AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1 FROM NUMBERS WHERE NUMBER < 10
)
INSERT INTO T
SELECT NUMBER
FROM NUMBERS;
SELECT NUMBER
, (SELECT TOP 1 NUMBER FROM T ORDER BY NEWID()) AS J
FROM T;
GO
Przykładowe wyniki:
NUMBER J
1 2
2 2
3 2
4 2
5 2
6 2
7 2
8 2
9 2
10 2
![]() |
USE TEMPDB;
GO
IF OBJECT_ID('#T') IS NOT NULL
DROP TABLE #T
CREATE TABLE #T (NUMBER INT);
WITH NUMBERS
AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1 FROM NUMBERS WHERE NUMBER < 10
)
INSERT INTO #T
SELECT NUMBER
FROM NUMBERS;
SELECT NUMBER
, (SELECT TOP 1 NUMBER FROM #T ORDER BY NEWID()) AS J
FROM #T;
GO
Przykładowe wyniki:
NUMBER J
1 6
2 6
3 6
4 6
5 6
6 6
7 6
8 6
9 6
10 6
Przykład nr 3 (zmienna tabelaryczna):
USE TEMPDB;
GO
DECLARE @T TABLE(NUMBER INT);
WITH NUMBERS
AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1 FROM NUMBERS WHERE NUMBER < 10
)
INSERT INTO @T
SELECT NUMBER
FROM NUMBERS;
SELECT NUMBER
, (SELECT TOP 1 NUMBER FROM @T ORDER BY NEWID()) AS J
FROM @T;
GO
Przykładowe wyniki:
NUMBER J
1 4
2 8
3 2
4 8
5 5
6 5
7 7
8 3
9 4
10 8
Ten komentarz został usunięty przez autora.
OdpowiedzUsuńPodejrzewam, że - w zależności od ilości danych w zmiennej tabelarycznej - optymalizator może wybierać różne plany wykonania zapytania i czasem błąd wystąpi, a czasem nie.
OdpowiedzUsuńCiekawa analiza tego zachowania jest także tu http://www.codersrevolution.com/blog/MS-SQL-Server-Table-Variable-And-Nested-Select-Gotcha-Bug, a na Microsoft Connect można przeczytać, że problem jest znany, ale nie będzie poprawiony https://connect.microsoft.com/SQLServer/feedback/details/350485/.
Tu jeszcze jeden fajny opis problemu: http://www.glorf.it/blog/2008/05/16/sql-talk/sql-server-is-not-aware-of-nondeterministic-functions
Usuń