Database
 sql >> Baza danych >  >> RDS >> Database

PRZYPADEK SQL:Poznaj i unikaj 3 mniej znanych kłopotów

PRZYPADEK SQL? Bułka z masłem!

Naprawdę?

Nie, dopóki nie natkniesz się na 3 kłopotliwe problemy, które mogą powodować błędy w czasie wykonywania i spowolnienie działania.

Jeśli próbujesz zeskanować podtytuły, aby zobaczyć, jakie są problemy, nie mogę cię winić. Czytelnicy, w tym ja, są niecierpliwi.

Ufam, że znasz już podstawy SQL CASE, więc nie będę Cię nudził długimi wprowadzeniami. Zagłębmy się w głębsze zrozumienie tego, co dzieje się pod maską.

1. SQL CASE nie zawsze ocenia sekwencyjnie

Wyrażenia w instrukcji Microsoft SQL CASE są w większości oceniane sekwencyjnie lub od lewej do prawej. To jednak inna historia, gdy używa się go z funkcjami agregacyjnymi. Spójrzmy na przykład:

-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;

Powyższy kod wygląda normalnie. Jeśli zapytam, jaki jest wynik tych stwierdzeń, prawdopodobnie odpowiesz 1. Kontrola wizualna mówi nam, że ponieważ @wartość jest ustawiona na 0. Gdy @wartość wynosi 0, wynik wynosi 1.

Ale tak nie jest w tym przypadku. Spójrz na prawdziwy wynik SQL Server Management Studio:

Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.

Ale dlaczego?

Gdy wyrażenia warunkowe używają funkcji agregujących, takich jak MAX() w SQL CASE, jest to oceniane jako pierwsze. Zatem MAX(1/@wartość) spowoduje błąd dzielenia przez zero, ponieważ @wartość to zero.

Ta sytuacja jest bardziej kłopotliwa, gdy jest ukryta. Wyjaśnię to później.

2. Proste wyrażenie SQL CASE ocenia wiele razy

I co z tego?

Dobre pytanie. Prawda jest taka, że ​​nie ma żadnych problemów, jeśli używasz dosłownych lub prostych wyrażeń. Ale jeśli użyjesz podzapytań jako wyrażenia warunkowego, czeka Cię wielka niespodzianka.

Zanim wypróbujesz poniższy przykład, możesz chcieć przywrócić kopię bazy danych z tego miejsca. Użyjemy go w pozostałych przykładach.

Rozważmy teraz to bardzo proste zapytanie:


SELECT TOP 1 manufacturerID FROM SportsCars

To bardzo proste, prawda? Zwraca 1 wiersz z 1 kolumną danych. We/wy STATISTICS ujawniają minimalne odczyty logiczne.

Krótka uwaga :Dla niewtajemniczonych posiadanie wyższych odczytów logicznych spowalnia zapytanie. Przeczytaj to, aby uzyskać więcej informacji.

Plan wykonania ujawnia również prosty proces:

Teraz umieśćmy to zapytanie w wyrażeniu CASE jako podzapytanie:

-- Using a subquery in a SQL CASE
DECLARE @manufacturer NVARCHAR(50)

SET @manufacturer = (CASE (SELECT TOP 1 manufacturerID FROM SportsCars)
				WHEN 6 THEN 'Alfa Romeo'
				WHEN 21 THEN 'Aston Martin'
				WHEN 64 THEN 'Ferrari'
				WHEN 108 THEN 'McLaren'
				ELSE 'Others'
		     END)

SELECT @manufacturer;

Analiza

Trzymajcie kciuki, bo to zdmuchnie logiczne odczyty 4 razy.

Niespodzianka! W porównaniu z rysunkiem 1 z zaledwie 2 odczytami logicznymi, jest to 4 razy wyższe. W ten sposób zapytanie jest 4 razy wolniejsze. Jak to się mogło stać? Podzapytanie widzieliśmy tylko raz.

Ale to nie koniec historii. Sprawdź plan wykonania:

Na rysunku 4 widzimy 4 wystąpienia operatorów Top i Index Scan. Jeśli każde Top i Index Scan zużywa 2 odczyty logiczne, wyjaśnia to, dlaczego odczyty logiczne zmieniły się na 8 na rysunku 3. A ponieważ każde skanowanie Top i Index Scan ma 25% koszt , mówi nam również, że są takie same.

Ale to nie koniec. Właściwości operatora obliczeniowego skalarnego pokazują, w jaki sposób traktowana jest cała instrukcja.

Widzimy 4 CASE WEN wyrażenia pochodzące z wartości zdefiniowanych przez operatora Compute Scalar. Wygląda na to, że nasze proste wyrażenie CASE stało się wyszukiwanym wyrażeniem CASE w następujący sposób:

DECLARE @manufacturer NVARCHAR(50)

SET @manufacturer = (CASE 
		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 6 THEN 'Alfa Romeo'
		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 21 THEN 'Aston Martin'
		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 64 THEN 'Ferrari'
		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 108 THEN 'McLaren'
		     ELSE 'Others'
		     END)

SELECT @manufacturer;

Podsumujmy. Dla każdego operatora skanowania górnego i indeksowego były 2 odczyty logiczne. To pomnożone przez 4 daje 8 logicznych odczytów. Widzieliśmy również 4 wyrażenia CASE WHEN w operatorze Compute Scalar.

Ostatecznie podzapytanie w prostym wyrażeniu CASE zostało ocenione 4 razy. Spowoduje to opóźnienie zapytania.

Jak uniknąć wielu ocen podzapytania w prostym wyrażeniu CASE

Aby uniknąć takich problemów z wydajnością, jak wielokrotne wyrażenie CASE w SQL, musimy przepisać zapytanie.

Najpierw umieść wynik podzapytania w zmiennej. Następnie użyj tej zmiennej w warunku prostego wyrażenia SQL Server CASE, tak jak to:

DECLARE @manufacturer NVARCHAR(50)
DECLARE @ManufacturerID INT -- create a new variable

-- store the result of the subquery in a variable
SET @ManufacturerID = (SELECT TOP 1 manufacturerID FROM SportsCars) 

-- use the new variable in the simple CASE expression
SET @manufacturer = (CASE @ManufacturerID
		     WHEN 6 THEN 'Alfa Romeo'
		     WHEN 21 THEN 'Aston Martin'
		     WHEN 64 THEN 'Ferrari'
		     WHEN 108 THEN 'McLaren'
		     ELSE 'Others'
		     END)
		
SELECT @manufacturer;

Czy to dobra poprawka? Zobaczmy logiczne odczyty w STATISTICS IO:

Widzimy niższe odczyty logiczne ze zmodyfikowanego zapytania. Wyjęcie podzapytania i przypisanie wyniku do zmiennej jest znacznie lepsze. A co z planem wykonania? Zobacz to poniżej.

Operator Top i Index Scan pojawił się tylko raz, a nie 4 razy. Cudownie!

Na wynos :Nie używaj podzapytania jako warunku w wyrażeniu CASE. Jeśli chcesz pobrać wartość, najpierw umieść wynik podzapytania w zmiennej. Następnie użyj tej zmiennej w wyrażeniu CASE.

3. Te 3 wbudowane funkcje potajemnie przekształcają się w SQL CASE

Istnieje sekret, a instrukcja SQL Server CASE ma z tym coś wspólnego. Jeśli nie wiesz, jak zachowują się te 3 funkcje, nie będziesz wiedział, że popełniasz błąd, którego staraliśmy się uniknąć w punktach #1 i #2 wcześniej. Oto one:

  • IIF
  • POŁĄCZENIE
  • WYBIERZ

Zbadajmy je jeden po drugim.

IIF

Użyłem Immediate IF lub IIF w Visual Basic i Visual Basic for Applications. Jest to również równoważne operatorowi trójskładnikowemu C#: ? :.

Ta funkcja mająca warunek zwróci 1 z 2 argumentów na podstawie wyniku warunku. Ta funkcja jest również dostępna w T-SQL. Instrukcja CASE w klauzuli WHERE może być użyta w instrukcji SELECT

Ale to tylko cukrowy płaszcz o dłuższej ekspresji CASE. Skąd wiemy? Przyjrzyjmy się przykładowi.

SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');

Wynikiem tego zapytania jest „Nie”. Jednak sprawdź plan wykonania wraz z właściwościami Compute Scalar.

Ponieważ IIF to CASE KIEDY, jak myślisz, co się stanie, jeśli wykonasz coś takiego?

DECLARE @averageCost MONEY = 1000000.00;
DECLARE @noOfPayments TINYINT = 0;   -- intentional to force the error

SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'SF90 Spider', 83333.33,MIN(@averageCost / @noOfPayments));

Spowoduje to błąd dzielenia przez zero, jeśli @noOfPayments wynosi 0. To samo wydarzyło się w punkcie 1 wcześniej.

Możesz się zastanawiać, co powoduje ten błąd, ponieważ powyższe zapytanie spowoduje TRUE i powinno zwrócić 83333.33. Sprawdź ponownie punkt #1.

Tak więc, jeśli utkniesz z takim błędem podczas korzystania z IIF, winowajcą jest SQL CASE.

POŁĄCZENIE

COALESCE to także skrót wyrażenia SQL CASE. Ocenia listę wartości i zwraca pierwszą niepustą wartość. W poprzednim artykule o COALESCE przedstawiłem przykład, który dwukrotnie ocenia podzapytanie. Ale użyłem innej metody, aby ujawnić SQL CASE w Execution Plan. Oto kolejny przykład, który będzie wykorzystywał te same techniki.

SELECT 
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car 
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID

Zobaczmy plan wykonania i zdefiniowane wartości skalarne.

SQL CASE jest w porządku. Słowa kluczowego COALESCE nie ma nigdzie w oknie Zdefiniowane wartości. To dowodzi tajemnicy tej funkcji.

Ale to nie wszystko. Ile razy widziałeś [Pojazdy].[dbo].[Style].[Styl] w oknie Zdefiniowane wartości? DWA RAZY! Jest to zgodne z oficjalną dokumentacją Microsoft. Wyobraź sobie, że jeden z argumentów w COALESCE jest podzapytaniem. Następnie podwój odczyty logiczne i uzyskaj wolniejsze wykonanie.

WYBIERZ

Wreszcie WYBIERZ. Jest to podobne do funkcji MS Access WYBIERZ. Zwraca 1 wartość z listy wartości na podstawie pozycji indeksu. Działa również jako indeks do tablicy.

Zobaczmy, czy możemy wykopać transformację w SQL CASE na przykładzie. Sprawdź poniższy kod:

;WITH McLarenCars AS 
(
SELECT 
 CASE 
	WHEN sc.Model IN ('Artura','Speedtail','P1/ P1 GTR','P1 LM') THEN '1'
	ELSE '2'
 END AS [type]
,sc.Model
,s.Style
FROM SportsCars sc
INNER JOIN Styles s ON sc.StyleID = s.StyleID
WHERE sc.ManufacturerID = 108
)
SELECT 
 Model
,Style
,CHOOSE([Type],'Hybrid','Gasoline') AS [type]
FROM McLarenCars

Oto nasz przykład WYBIERZ. Sprawdźmy teraz plan wykonania i zdefiniowane wartości skalarne:

Czy widzisz słowo kluczowe WYBIERZ w oknie Zdefiniowane wartości na rysunku 10? Co powiesz na PRZYPADEK KIEDY?

Podobnie jak w poprzednich przykładach, ta funkcja WYBIERZ jest tylko płaszczem cukrowym do dłuższego wyrażenia CASE. A ponieważ zapytanie zawiera 2 pozycje dla WYBIERZ, słowo kluczowe CASE, WEN pojawiło się dwukrotnie. Zobacz okno Zdefiniowane wartości zamknięte w czerwonym polu.

Jednak mamy tutaj wiele CASE WHEN w SQL. Dzieje się tak z powodu wyrażenia CASE w wewnętrznym zapytaniu CTE. Jeśli przyjrzysz się uważnie, ta część wewnętrznego zapytania również pojawi się dwukrotnie.

Na wynos

Teraz, gdy tajemnice wyszły na jaw, czego się nauczyliśmy?

  1. SQL CASE zachowuje się inaczej, gdy używane są funkcje agregujące. Zachowaj ostrożność podczas przekazywania argumentów do funkcji agregujących, takich jak MIN, MAX lub COUNT.
  2. Proste wyrażenie CASE będzie oceniane wielokrotnie. Zauważ to i unikaj przekazywania podzapytania. Chociaż jest poprawny składniowo, będzie działał słabo.
  3. IIF, CHOOSE i COALESCE mają brudne sekrety. Pamiętaj o tym przed przekazaniem wartości do tych funkcji. Przekształci się w SQL CASE. W zależności od wartości powodujesz błąd lub spadek wydajności.

Mam nadzieję, że to inne podejście do SQL CASE było dla Ciebie przydatne. Jeśli tak, Twoi znajomi programiści też mogą to polubić. Udostępnij go na swoich ulubionych platformach społecznościowych. Daj nam znać, co o tym myślisz w sekcji Komentarze.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Podstawy wyrażeń tabelarycznych, Część 4 – Tabele pochodne, rozważania dotyczące optymalizacji, ciąg dalszy

  2. Przechowywana procedura pobierania informacji o pamięci serwera na serwerze

  3. Jak znaleźć średnią kolumny numerycznej w SQL?

  4. Podstawy wyrażeń tabelarycznych, Część 5 – CTE, rozważania logiczne

  5. Używanie JShell w Javie 9 w NetBeans 9.0, część 3