Sekcja odpowiedzi
Istnieją różne sposoby przepisania tego przy użyciu różnych konstrukcji T-SQL. Przyjrzymy się zaletom i wadom i przeprowadzimy ogólne porównanie poniżej.
Po pierwsze : korzystanieOR
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;
Użycie OR
daje nam bardziej wydajny plan wyszukiwania, który odczytuje dokładną liczbę potrzebnych wierszy, jednak dodaje to, co świat techniczny wzywa a whole mess of malarkey
do planu zapytań.
Zauważ też, że Seek jest tutaj wykonywany dwukrotnie, co naprawdę powinno być bardziej oczywiste dla operatora graficznego:
Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 469 ms, elapsed time = 473 ms.
Po drugie : Używanie tabel pochodnych z UNION ALL
naszym zapytaniem można również przepisać w ten sposób
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records);
Daje to ten sam typ planu, przy znacznie mniejszym nieprzyzwoitości i bardziej widocznym stopniu uczciwości co do tego, ile razy wskaźnik był poszukiwany (poszukiwany?).
Robi tyle samo odczytów (8233) co OR
zapytanie, ale oszczędza około 100 ms czasu procesora.
CPU time = 313 ms, elapsed time = 315 ms.
Musisz jednak być bardzo ostrożny, ponieważ jeśli ten plan COUNT
będzie działał równolegle, dwie oddzielne operacje zostaną serializowane, ponieważ każda z nich jest traktowana jako globalny agregat skalarny. Jeśli wymusimy równoległy plan przy użyciu flagi śledzenia 8649, problem stanie się oczywisty.
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Można tego uniknąć, zmieniając nieco nasze zapytanie.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Teraz oba węzły wykonujące wyszukiwanie są w pełni zrównoleglone, dopóki nie natrafimy na operator konkatenacji.
Pod względem wartości w pełni równoległa wersja ma pewne zalety. Kosztem około 100 dodatkowych odczytów i około 90 ms dodatkowego czasu procesora upływ czasu skraca się do 93 ms.
Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 500 ms, elapsed time = 93 ms.
Co z aplikacją CROSS?
Żadna odpowiedź nie jest kompletna bez magii CROSS APPLY
!
Niestety mamy więcej problemów COUNT
.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Ten plan jest okropny. Jest to rodzaj planu, który kończysz, kiedy pojawiasz się ostatni do Dnia Świętego Patryka. Chociaż ładnie równoległy, z jakiegoś powodu skanuje PK / CX. Ew. Koszt planu to 2198 dolców za zapytania.
Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 29532 ms, elapsed time = 5828 ms.
Co jest dziwnym wyborem, ponieważ jeśli zmuszymy go do użycia indeksu nieklastrowanego, koszt znacznie spadnie do 1798 dolców za zapytanie.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Hej, szuka! Sprawdź się tam. Zauważ też, że dzięki magii CROSS APPLY
nie musimy robić nic głupiego, aby mieć w większości całkowicie równoległy plan.
Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 27625 ms, elapsed time = 4909 ms.
Zastosuj krzyżowanie kończy się lepiej bez tych COUNT
rzeczy.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Plan wygląda dobrze, ale odczyty i procesor nie stanowią poprawy.
Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 4844 ms, elapsed time = 863 ms.
Przepisanie krzyża stosuje się, aby uzyskać pochodne połączenie w dokładnie tym samym wszystkim. Nie zamierzam ponownie publikować informacji o planie zapytań i statystykach - naprawdę się nie zmieniły.
SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN
(
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x ON x.Id = u.Id;
Relacyjna algebra : Aby być dokładnym i powstrzymać Joe Celko przed prześladowaniem moich snów, musimy przynajmniej spróbować dziwnych relacji. Tutaj nic nie idzie!
Próba z INTERSECT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
INTERSECT
SELECT u.Age WHERE u.Age IS NOT NULL );
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 1094 ms, elapsed time = 1090 ms.
A oto próba z EXCEPT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
EXCEPT
SELECT u.Age WHERE u.Age IS NULL);
Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 2126 ms, elapsed time = 376 ms.
Mogą być też inne sposoby, aby napisać te, ale zostawię to do ludzi, którzy być może używają EXCEPT
i INTERSECT
częściej niż ja.
Jeśli naprawdę potrzebujesz tylko liczby
, używam COUNT
w moich zapytaniach jako skrótu (czytaj: jestem zbyt leniwy, by czasem wymyślić bardziej zaangażowane scenariusze). Jeśli potrzebujesz tylko liczby, możesz użyć CASE
wyrażenia, aby zrobić dokładnie to samo.
SELECT SUM(CASE WHEN u.Age < 18 THEN 1
WHEN u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
Oba mają ten sam plan i mają tę samą charakterystykę procesora i odczytu.
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 719 ms, elapsed time = 719 ms.
Zwycięzca?
W moich testach wymuszony plan równoległy z SUM nad tabelą pochodną działał najlepiej. I tak, wielu z tych zapytań można było pomóc, dodając kilka filtrowanych indeksów w celu uwzględnienia obu predykatów, ale chciałem pozostawić pewne eksperymenty innym.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Dzięki!
NOT EXISTS ( INTERSECT / EXCEPT )
wyszukiwania może działać bezINTERSECT / EXCEPT
części:WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
Inny sposób, który wykorzystuje -EXCEPT
:SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ;
(gdzie identyfikator użytkownika PK i niepowtarzalny niezerowy kolumnę (-y)).