2011-12-15 23 views
8

Tło: starałem się dostać kilka losowych wartości „hex” podczas tworzenia danych manekina i wymyślił tej konstrukcji:Dziwne zachowanie budowlane CASE

SELECT TOP 100 
    result = (CASE ABS(Binary_Checksum(NewID())) % 16 
       WHEN -1 THEN 'hello' 
       WHEN 0 THEN '0' 
       WHEN 1 THEN '1' 
       WHEN 2 THEN '2' 
       WHEN 3 THEN '3' 
       WHEN 4 THEN '4' 
       WHEN 5 THEN '5' 
       WHEN 6 THEN '6' 
       WHEN 7 THEN '7' 
       WHEN 8 THEN '8' 
       WHEN 9 THEN '9' 
       WHEN 10 THEN 'a' 
       WHEN 11 THEN 'b' 
       WHEN 12 THEN 'c' 
       WHEN 13 THEN 'd' 
       WHEN 14 THEN 'e' 
       WHEN 15 THEN 'f' 
       ELSE 'huh' END) 
      FROM sys.objects 

Kiedy działa to na moim SQL Server 2008 przykład R2, Dostaję całkiem sporo "huh":

result 
------ 
huh 
3 
huh 
huh 
6 
8 
6 

Naprawdę nie rozumiem dlaczego. Co spodziewałbym się wydarzyć jest:

  • dla każdego rekordu NewID() pojawia się z nowym losowo wartości
  • Binary_Checksum() oblicza int na podstawie wspomnianej wartości
  • ABS() sprawia, że ​​wartość dodatnią
  • % 16 zwraca pozostałą część tej wartości dodatniej, jeśli byłaby podzielona przez 16, co oznaczałoby wtedy wartość między 0 a 15
  • th e CASE budowa konwertuje wartość do odpowiedniego charakteru
  • Ponieważ istnieje WHEN s dla każdej wartości pomiędzy 0 a 15, ELSE nigdy nie powinny być potrzebne

lub przynajmniej, że to, co myślę, że powinno się zdarzyć ... ale oczywiście coś pójdzie nie po drodze ...

Kiedy robi to samo w podejściu dwuetapowym (przez temp-tabeli), huh na odeszły ...

SELECT TOP 100 x = ABS(Binary_Checksum(NewID())) % 16, 
       result = 'hello' 
    INTO #test 
    FROM sys.objects 

UPDATE #test 
    SET result = (CASE x WHEN 0 THEN '0' WHEN 1 THEN '1' WHEN 2 THEN '2' WHEN 3 THEN '3' 
         WHEN 4 THEN '4' WHEN 5 THEN '5' WHEN 6 THEN '6' WHEN 7 THEN '7' 
         WHEN 8 THEN '8' WHEN 9 THEN '9' WHEN 10 THEN 'a' WHEN 11 THEN 'b' 
         WHEN 12 THEN 'c' WHEN 13 THEN 'd' WHEN 14 THEN 'e' WHEN 15 THEN 'f' 
         ELSE 'huh' END) 

SELECT * FROM #test 

Ktoś, kto to rozumie? O ile mogę powiedzieć, powinien dać ten sam wynik (rzeczywiście jest to wklejanie i wklejanie), niezależnie od tego, czy robię to bezpośrednio, czy za pośrednictwem tabeli temp ... Ale oczywiście coś pójdzie nie tak, jeśli zrobię to w pojedynczej instrukcji.

PS: Nie potrzebuję do tego "poprawki", mam już obejście (patrz poniżej), mam jedynie nadzieję, że ktoś może mi wyjaśnić, dlaczego robi to, co robi.

Obejście:

SELECT TOP 100 result = SubString('abcdef', 1 + (ABS(Binary_Checksum(NewID())) % 16), 1) 
    FROM sys.objects 
+0

PS: jeśli ktokolwiek mógłby to przetestować na różnych wersjach SQL-Server, które mogą być interesujące ... – deroby

+0

Twoje obejście wygląda bardziej zwięźle niż oryginał ... – Russell

+0

Thx za wyjaśnienie tych gości ... idąc z odpowiedzią Damiens ponieważ jest to trochę bardziej szczegółowe dla każdego, kto mógłby się na to natknąć. (a on był o 1 minuta szybszy w górze = P) – deroby

Odpowiedz

3

wierzę, że w przeciwieństwie do opisu simple CASE expression, że rzeczywiście ponownie ocenia input_expression dla każdego porównania input_expression = when_expression (zazwyczaj jest to bezpieczne, chyba że, jak w tym przypadku, istnieje zakaz deterministyczny funkcja w input_expression)

Więc, co się dzieje, jest to, że utrzymuje generowanie różnych liczb losowych między 0 i 15 dla każdego porównania, a huh s wyjdzie, jeśli po 16 ocenach/porównaniach, to nigdy generowa ed pasujący numer.


nie generuje huh s:

SELECT TOP 100 
    result = (CASE ABS(Binary_Checksum(Value)) % 16 
      WHEN -1 THEN 'hello' 
      WHEN 0 THEN '0' 
      WHEN 1 THEN '1' 
      WHEN 2 THEN '2' 
      WHEN 3 THEN '3' 
      WHEN 4 THEN '4' 
      WHEN 5 THEN '5' 
      WHEN 6 THEN '6' 
      WHEN 7 THEN '7' 
      WHEN 8 THEN '8' 
      WHEN 9 THEN '9' 
      WHEN 10 THEN 'a' 
      WHEN 11 THEN 'b' 
      WHEN 12 THEN 'c' 
      WHEN 13 THEN 'd' 
      WHEN 14 THEN 'e' 
      WHEN 15 THEN 'f' 
      ELSE 'huh' END) 
      FROM (select NewID() as Value,* from sys.objects) so 
+0

Tak się wydaje. Ma sens, gdy to wytłumaczycie, ale nie myślałbym o tym sam. Dzięki. – deroby

+1

"To nie generuje huh" w teście, ale czy możesz wskazać jakiekolwiek dokumenty, które mówią, że SQL Server ma gwarancję, że oceni je tylko raz? –

+2

Powinno to być: 'result = (CASE ABS (Binary_Checksum (wartość))% 16' – Johan

8

Skalar obliczyć w planie ma następujący wzór

[Expr1038] = skalarne operatora (przypadek, gdy ABS (binary_checksum (newid()))% (16) = (- 1) THEN "cześć" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (0) TO "0" ELSE CASE WHEN abs (binary_checksum (nowy id()))% (16) = (1) TO "1" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (2) TO "2" ELSE CASE WHENY abs (binary_checksum (newid()))% (16) = (3) TO "3" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (4) TO "4" ELSE CASE WH abs (binary_checksum (newid()))% (16) = (5) TO "5" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (6) TO "6" ELSE CASE KIEDY abs (binary_checksum (newid()))% (16) = (7) TO "7" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (8) THEN "8" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (9) TO "9" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (10) WTEDY "a" ELSE CASE KIEDY abs (binary_checksum (newid()))% (16) = (11) WTEDY "B" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (12) THEN "c" ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (13) TO "d" ELSE CASE KIEDY abs (binary_checksum (newid()))% (16) = (14) THEN 'e' ELSE CASE WHEN abs (binary_checksum (newid()))% (16) = (15) THEN 'f' ELSE 'huh "END END KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC KONIEC)

liczba losowa jest wielokrotnie ponownej ocenie zamiast być oceniany raz i utrzymywane na stałym poziomie przez cały każdej gałęzi rachunku CASE.

The (stałe) proponowane rozwiązanie w odpowiedzi Damiana działa dla mnie

SELECT TOP 100 
    result = (CASE ABS(Binary_Checksum(Value)) % 16 
      WHEN -1 THEN 'hello' 
      /*...*/ 
      ELSE 'huh' END) 
      FROM (select NewID() as Value,* from sys.objects) so 

Ponieważ plan ma 2 operatorów obliczeniowej skalarnych. Pierwszy z definicji

[Expr1038] = Scalar Operator(newid()) 

Plan

Następnie, że stała ekspresja Expr1038 doprowadza się do ekspresji CASE. Nie jestem jednak pewien, czy to zachowanie jest absolutnie gwarantowane. Może podlegać kaprysom optymalizatora.

+0

LOL, to by to wyjaśniło. Więc wewnętrznie CASE x KIEDY 1 THEN a WHEN 2 THEN b .... jest zamieniany na CASE WHEN x = 1 THEN a WHEN x = 2 THEN b etc ... To ma sens. – deroby

+0

@deroby - Tak. Dość dużo. Nie wiesz, czy istnieje alternatywa w 100% gwarantowana, aby tego uniknąć. –

+0

Zgaduję, że przy użyciu CTE, CROSS APPLY lub CROSS JOIN można obejść ten problem; ale jak ktoś już zauważył, dane obejście faktycznie wygląda czystsze =) – deroby