2017-01-04 14 views
5

Jest to najprostszy przykład, z którym mogę odtworzyć problem. Jako taki wygląda trochę wymyślnie, ale znosić mnie.Zaskakujące zachowanie ponownego przypisywania zmiennej dla każdego wiersza w wybranym

declare @t table(number int) 
insert into @t values (1), (2) 

declare @sum bigint = 0 

select @sum = @sum + number 
    from (select top 2 number from @t order by number) subquery 
    order by number desc 

select @sum 

Here's the query on the data explorer.

Spodziewam się, że to zwróci 3, suma wartości w tabeli @t. Zamiast tego jednak, zwraca 1.

wykonując jedną z następujących czynności spowoduje zapytanie do prawidłowego powrotu 3:

  • zrobić @t.number i @sum mają ten sam typ (poprzez @sumint lub @t.number się bigint).
  • usuwając zewnętrzną order by
  • wyjmowania wewnętrznej order by
  • wytwarzaniu obu order by s rodzaju w tym samym kierunku przez dodanie desc wewnętrznej jednej lub usunięcia jej z zewnętrznego jednego
  • usuwania podkwerendę (czyli po prostu wybierając from @t)

Żadna z tych rzeczy nie uderza mnie jako coś, co powinno zmienić zachowanie tego zapytania.

Zamiana zlecenia sortowania (malejąco w podzapytania, rosnąco na zewnątrz) dokona zwrotu zapytania 2 zamiast 1.

Podobnie dzieje się z ciągami zamiast liczb, więc to nie jest ograniczone do int i bigint.

Dzieje się tak zarówno SQL Server 2014 i 2016, a raczej

Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 
Feb 20 2014 20:04:26 
Copyright (c) Microsoft Corporation 
Developer Edition (64-bit) on Windows NT 6.3 <X64> (Build 10586:) 

i

Microsoft SQL Server 2016 (RTM-CU1) (KB3164674) - 13.0.2149.0 (X64) 
Jul 11 2016 22:05:22 
Copyright (c) Microsoft Corporation 
Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600:) 

(przy czym ten ostatni badacz danych).

Co się dzieje?

+1

Niezawodny sposób przypisania sumy to: "SELECT @ sum = SUMA (liczba) OD ...". Wykonywanie zadania w "SELECT", gdy zaangażowane jest wiele wierszy, wymaga kłopotów - zasadniczo pytasz optymalizatora, żeby Cię zepsuł, jeśli znajdzie jakiś sprytny sposób oceny zapytania. Słyszałem, jak ludzie mówią, że to nie działa, nie powinno działać, będzie działało w szczególnych okolicznościach lub nawet, że * gwarantuje * działanie w pewnych okolicznościach, ale niezawodne źródła są rzadkością. To może, ale nie musi być błąd optymalizatora, ale biorąc pod uwagę przepisanie powinno być pierwszą drogą ataku. –

+0

@JeroenMostert Używam sumy jako prostego przykładu. Jak już powiedziałem, jest to trochę wymyślne. – balpha

+0

Zdaję sobie z tego sprawę, ale ogólna rada jest ważna. Znalezienie sposobu na przepisanie przypisania wiersz po wierszu do czegoś opartego na zestawie jest twoim najlepszym pomysłem (chociaż bez utraty wydajności lub (horroru) wprowadzenie kursorów może być trudnym zadaniem). –

Odpowiedz

1

Odpowiedź wydaje się być to, że są/były powołując się na nieudokumentowane zachowania, które zmieniły w SQL Server 2012.

Per dokumentacji:

SELECT @ local_variable jest zwykle używana do zwracania pojedynczej wartości do zmiennej. Jednak gdy wyrażenie jest nazwą kolumny, może zwracać wiele wartości. Jeśli instrukcja SELECT zwraca więcej niż jedną wartość, do zmiennej zostanie przypisana ostatnia zwracana wartość.

Nie jest udokumentowane, co się stanie, jeśli zmienna docelowa (do przypisania) jest częścią wyrażenia źródłowego. Wygląda na to, że to zachowanie się zmieniło. We wcześniejszych wersjach zmienna byłaby przypisywana raz dla każdego wiersza, ale wydaje się, że już nie występuje.

Jest to najbardziej widoczne dla wielu funkcji, gdzie „grupa concat” trick przestał działać:

SELECT @sentence = @sentence + ' ' + word from SENTENCE_WORDS order by position 

Mają one zazwyczaj być zastąpiony przez podstęp xml concat.

set @sentence = (
    select word as "text()", ' ' as "text()" 
    from SENTENCE_WORDS 
    order by position 
    for xml path(''), root('root'), type 
).value('(/root)[1]', 'nvarchar(max)') 
+0

Twój przykład działa dobrze: http://data.stackexchange.com/stackoverflow/query/611317 (podobnie jak odpowiadająca wersja mojej sumy - jest to tylko kombinacja konwersji typu, podzapytania i dwóch 'order by's które to przełamują). – balpha

+0

Rzeczywiście. Ale ponieważ nie jest to udokumentowane zachowanie, nie można na nim polegać. Może się zmienić w dodatku Service Pack, zmianie indeksu lub po prostu zmianie planu kwerendy ... Nie wiesz, co może spowodować, że przestanie działać. – Ben

+0

... nie wiem też, co może spowodować, że przestanie działać. – Ben

0

Usuń drugie ZAMÓWIENIE (tj. "Zamówienie według numeru").

Używasz nieudokumentowanej funkcji T-SQL (jak sądzę, nazywa się to ROW concatenation?), Co nie gwarantuje działania w przyszłych wersjach SQL. To trochę hacky, ale bardzo przydatne bez względu na mniej! Jak odkryłeś, łamie się, gdy używasz klauzuli ORDER BY. Jest to znany problem z użyciem konkatenacji wierszy.