Nie można utworzyć indeksu filtrowanego w kolumnie obliczeniowej

18

W poprzednim moim pytaniu, czy dobrym pomysłem jest wyłączenie eskalacji blokady podczas dodawania nowych kolumn obliczeniowych do tabeli? , Tworzę kolumnę obliczeniową:

ALTER TABLE dbo.tblBGiftVoucherItem
ADD isUsGift AS CAST
(
    ISNULL(
        CASE WHEN sintMarketID = 2 
            AND strType = 'CARD'
            AND strTier1 LIKE 'GG%' 
        THEN 1 
        ELSE 0 
        END
    , 0) 
    AS BIT
) PERSISTED;

Kolumna obliczana jest PERSISTEDi zgodnie z definicją obliczonej_kolumny (Transact-SQL) :

TRWAŁA

Określa, że ​​aparat bazy danych fizycznie zapisze obliczone wartości w tabeli i zaktualizuje wartości, gdy zostaną zaktualizowane inne kolumny, od których zależy obliczona kolumna. Oznaczenie kolumny obliczanej jako PERSISTED pozwala na utworzenie indeksu na kolumnie obliczeniowej, która jest deterministyczna, ale nieprecyzyjna. Aby uzyskać więcej informacji, zobacz Indeksy dotyczące kolumn obliczanych. Wszelkie kolumny obliczane używane jako partycjonowane kolumny tabeli podzielonej na partycje muszą być wyraźnie oznaczone jako PERSISTED. wyrażenie wyliczone_kolumna musi być deterministyczne, gdy określono PERSISTED.

Ale kiedy próbuję utworzyć indeks w mojej kolumnie, pojawia się następujący błąd:

CREATE INDEX FIX_tblBGiftVoucherItem_incl
ON dbo.tblBGiftVoucherItem (strItemNo) 
INCLUDE (strTier3)
WHERE isUsGift = 1;

Filtrowanego indeksu „FIX_tblBGiftVoucherItem_incl” nie można utworzyć w tabeli „dbo.tblBGiftVoucherItem”, ponieważ kolumna „isUsGift” w wyrażeniu filtru jest kolumną obliczoną. Przepisz wyrażenie filtru, aby nie zawierało tej kolumny.

Jak mogę utworzyć filtrowany indeks w kolumnie obliczanej?

lub

Czy istnieje alternatywne rozwiązanie?

Marcello Miorelli
źródło
3
Możesz jednak utworzyć filtrowany indeks WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%').
ypercubeᵀᴹ

Odpowiedzi:

21

Niestety od SQL Server 2014 nie ma możliwości utworzenia miejsca, w Filtered Indexktórym filtr znajduje się w kolumnie obliczeniowej (niezależnie od tego, czy jest on utrwalony).

Nastąpił Przedmiot Połącz otwarty od 2009 roku, więc proszę iść do przodu i zagłosuj na niego. Może Microsoft naprawi to pewnego dnia.

Aaron Bertrand ma artykuł, który omawia wiele innych problemów z indeksami filtrowanymi .

Mark Sinkinson
źródło
21

Chociaż nie można utworzyć filtrowanego indeksu na utrwalonej kolumnie, istnieje dość proste obejście, którego można użyć.

Jako test stworzyłem prostą tabelę z IDENTITYkolumną i utrwaloną kolumną obliczeniową na podstawie kolumny tożsamości:

USE tempdb;

CREATE TABLE dbo.PersistedViewTest
(
    PersistedViewTest_ID INT NOT NULL
        CONSTRAINT PK_PersistedViewTest
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData VARCHAR(2000) NOT NULL
    , TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO

Następnie utworzyłem widok związany ze schematem na podstawie tabeli z filtrem w kolumnie obliczanej:

CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID
    , SomeData 
    , TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);

Następnie utworzyłem indeks klastrowy w widoku związanym ze schematem, który powoduje utrwalenie wartości przechowywanych w widoku, w tym wartości kolumny obliczanej:

CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

Wstaw niektóre dane testowe do tabeli:

INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
    CROSS JOIN sys.objects o1
    CROSS JOIN sys.objects o2;

Utwórz element statystyk i indeks w widoku:

CREATE STATISTICS ST_PersistedViewTest_View
ON dbo.PersistedViewTest_View(TestComputedColumn)
WITH FULLSCAN;

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

Wykonywanie SELECTinstrukcji dla tabeli z utrwaloną kolumną może teraz automatycznie korzystać z utrwalonego widoku, jeśli optymalizator zapytań stwierdzi, że warto to zrobić:

SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)

Rzeczywisty plan wykonania dla powyższego zapytania pokazuje, że optymalizator zapytań wybrał użycie utrwalonego widoku do zwrócenia wyników:

wprowadź opis zdjęcia tutaj

Być może zauważyłeś wyraźną konwersję w WHEREpowyższym punkcie. Wyraźnie to CONVERT(INT, 26)pozwala optymalizatorowi kwerendy poprawnie używać obiektu statystyki do oszacowania liczby wierszy, które zostaną zwrócone przez kwerendę. Jeśli napiszemy zapytanie WHERE pv.TestComputedColumn = 26, optymalizator zapytań może nie oszacować poprawnie liczby wierszy, ponieważ 26 uważa się za a TINY INT; może to spowodować, że SQL Server nie użyje utrwalonego widoku. Niejawne konwersje mogą być bardzo bolesne i opłaca się konsekwentnie używać odpowiednich typów danych do porównań i połączeń.

Oczywiście wszystkie standardowe „gotchas” wynikające z używania wiązania schematu dotyczą powyższego scenariusza; może to uniemożliwić korzystanie z tego obejścia we wszystkich scenariuszach. Na przykład modyfikowanie tabeli podstawowej nie będzie już możliwe bez uprzedniego usunięcia powiązania schematu z widoku. Aby to zrobić, musisz usunąć indeks klastrowany z widoku.

Jeśli nie masz programu SQL Server Enterprise Edition, optymalizator zapytań nie będzie automatycznie używał utrwalonego widoku dla zapytań, które nie odwołują się bezpośrednio do widoku przy użyciu WITH (NOEXPAND)podpowiedzi. Aby wykorzystać zalety korzystania z utrwalonego widoku w wersjach innych niż Enterprise Edition, musisz ponownie napisać powyższe zapytanie w coś takiego:

SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)

Dzięki Ian Ringrose za wskazanie powyższego ograniczenia Enterprise Edition oraz Paulowi Whiteowi za (NOEXPAND)podpowiedź.

Ta odpowiedź Paula zawiera kilka interesujących szczegółów na temat optymalizatora zapytań w odniesieniu do utrwalonych widoków.

Max Vernon
źródło
Obejście pokazuje, że zarówno indeks klastrowany, jak i indeks nieklastrowany są tworzone w widoku. Czy z jakiegoś powodu indeks nieklastrowany musi być używany nad indeksem klastrowanym? A może indeks nieklastrowany jest bardziej wydajny? Gdyby w zapytaniu wykorzystano indeks klastrowany, co pokazałyby statystyki?
Bob Bryan
Interesujące pytanie, @BobBryan - indeks klastrowany jest wymagany, aby umożliwić utrwalenie widoku, chociaż tak naprawdę nie musi to być unikalny indeks. Mógłbym utworzyć indeks klastrowy widoku w innej kolumnie, takiej jak TestComputedColumnzamiast. Ponieważ jednak indeks klastrowany zawiera wszystkie dane dla tabeli / widoku, zdecydowałem, że lepiej będzie użyć monotonicznie rosnącej liczby jako klucza klastrowania. Zauważ, że tak naprawdę nie testowałem tego przypuszczenia i może ono być w rzeczywistości niepoprawne dla niektórych wariantów repro.
Max Vernon
Uwaga: indeks nieklastrowany nie jest indeksem przykrywającym i jako takie każde zapytanie, które albo filtruje, łączy lub zwraca kolumny z widoku albo z tabeli bazowej, będzie musiało wykonać operację wyszukiwania klucza względem tabeli podstawowej lub widok. Jest prawdopodobne, że w przypadku rzeczywistego scenariusza ograniczony zakres mojej odpowiedzi można wyjaśnić z myślą o jeszcze lepszych wynikach.
Max Vernon
4

Od Create Indexi jego whereklauzula nie jest to możliwe:

GDZIE

Tworzy filtrowany indeks, określając, które wiersze mają zostać uwzględnione w indeksie. Filtrowany indeks musi być indeksem nieklastrowanym w tabeli. Tworzy filtrowane statystyki dla wierszy danych w przefiltrowanym indeksie.

Predykat filtru używa prostej logiki porównania i nie może odwoływać się do kolumny obliczanej, kolumny UDT, kolumny typu danych przestrzennych ani kolumny typu danych hierarchyID. Porównania z literałami NULL są niedozwolone z operatorami porównania. Zamiast tego użyj operatorów IS NULL i IS NOT NULL.

Źródło: MSDN

Julien Vavasseur
źródło
3
  • Potrzebujesz kolumny, która nie jest obliczana, aby umieścić filtrowany indeks.
  • Musisz obliczyć wartość, aby przejść do tej kolumny.

Zanim obliczyliśmy kolumny, użyliśmy wyzwalaczy do obliczania wartości kolumn za każdym razem, gdy wiersz był zmieniany lub wstawiany.

(Można także użyć wyzwalacza do wstawienia / usunięcia PK elementu z 2. tabeli, która została następnie użyta w zapytaniach.)

Ian Ringrose
źródło
3

Jest to próba usprawnienia pracy Maxa Vernona . W swoim rozwiązaniu sugeruje użycie 2 indeksów w widoku i obiektu statystycznego.

Pierwszy indeks jest klastrowany, co jest faktycznie wymagane, ponieważ w przeciwieństwie do indeksu nieklastrowanego w tabeli, błąd zostanie wygenerowany, jeśli spróbuje się utworzyć indeks nieklastrowany w widoku bez uprzedniego posiadania indeksu klastrowanego.

Drugi indeks jest indeksem nieklastrowanym, który jest używany jako indeks kwerendy. W sekcji komentarzy jego odpowiedzi zapytałem, co by się stało, gdyby zamiast indeksu nieklastrowanego użyto indeksu klastrowego.

Poniższa analiza próbuje odpowiedzieć na to pytanie.

Używam jego dokładnie tego samego kodu, z tym wyjątkiem, że nie tworzę indeksu nieklastrowanego w widoku.

Nie tworzę też obiektu statystycznego. Jeśli śledzisz i używasz programu SQL Server Management Studio (SSMS), aby wprowadzić poniższy kod, powinieneś być świadomy, że mogą pojawić się pewne czerwone linie - które wyglądają jak błędy. Nie są to (prawdopodobnie) błędy, ale dotyczą problemu z intellisense.

Możesz albo wyłączyć intellisense, albo po prostu zignorować błędy i uruchomić polecenia. Powinny zostać wypełnione bez błędów.

-- Create the test table that uses a computed column.
USE tempdb;
CREATE TABLE dbo.PersistedViewTest
(
    PersistedViewTest_ID INT NOT NULL
    CONSTRAINT PK_PersistedViewTest
    PRIMARY KEY CLUSTERED
    IDENTITY(1,1)
    , SomeData VARCHAR(2000) NOT NULL
    , TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO

-- Insert some test data into the table.
INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
    CROSS JOIN sys.objects o1
    CROSS JOIN sys.objects o2;
GO

Następujący plan wykonania (bez widoku / widoku indeksu) jest tworzony po uruchomieniu następującego zapytania dla tabeli:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

wprowadź opis zdjęcia tutaj

Daje to podstawę do porównania. Zauważ, że po zakończeniu zapytania utworzono obiekt statystyki (_WA_Sys_00000003_1FCDBCEB). Obiekt statystyki PK_PersistedViewTest został utworzony podczas tworzenia indeksu tabeli klastrowej.

Następnie tworzony jest filtrowany widok i indeks klastrowy w tym widoku:

-- Create filtered view on the computed column.
CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID, SomeData, TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);
GO

-- Create unique clustered index to persist the values, including the computed column.
CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

Teraz spróbujmy ponownie uruchomić zapytanie, ale tym razem w widoku:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

Nowy plan wykonania jest teraz:

wprowadź opis zdjęcia tutaj

Jeśli wierzyć nowemu planowi, po dodaniu widoku i indeksu klastrowego w tym widoku statystyki wydają się wskazywać, że czas wymagany do wykonania zapytania podwoił się. Zauważ też, że po uruchomieniu zapytania nie został utworzony nowy obiekt statystyczny do obsługi nowego indeksu, który różni się od zapytania w tabeli.

Plan zapytań nadal sugeruje, że utworzenie nieklastrowanego indeksu byłoby bardzo pomocne w poprawie wydajności zapytania. Czy to oznacza, że ​​do widoku należy dodać indeks nieklastrowany, aby uzyskać pożądaną poprawę wydajności? Jest jeszcze jedna ostatnia rzecz do wypróbowania. Zmodyfikuj zapytanie, aby użyć opcji „WITH NOEXPAND”:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

Powoduje to następujący plan zapytań:

wprowadź opis zdjęcia tutaj

Ten plan wykonania wygląda dość podobnie do tego, który został utworzony z indeksem nieklastrowanym podanym w odpowiedzi Maxa Vernona. Ale ten jest wykonywany z jednym mniejszym (nieklastrowanym) indeksem i jednym mniejszym obiektem statystycznym.

Okazuje się, że opcji NOEXPAND należy używać z ekspresową i standardową wersją SQL Server, aby właściwie wykorzystać widok indeksowany. Paul White ma znakomity artykuł, który wyjaśnia korzyści płynące z używania opcji NOEXPAND. Zaleca także, aby tę opcję stosować z wersją Enterprise, aby zapewnić, że optymalizator zastosuje gwarancję unikalności zapewnianą przez indeksy widoków.

Powyższą analizę wykonano z ekspresową wersją SQL Sever 2014. Próbowałem również z edycją programistyczną SQL Server 2016. Opcja NOEXPAND nie wydaje się być wymagana w przypadku wersji rozwojowej do osiągnięcia wzrostu wydajności, ale nadal jest zalecana .

Niecałe 5 miesięcy temu Microsoft udostępnił wersje programistyczne za darmo . Licencja ogranicza użycie wyłącznie do programowania, co oznacza, że ​​bazy danych nie można używać w środowisku produkcyjnym. Tak więc, jeśli chciałeś przetestować tabele zoptymalizowane pod kątem pamięci, szyfrowanie, R itp., Nie masz już wymówki bez licencji. Z powodzeniem zainstalowałem go na moim komputerze kilka dni temu obok SQL Server 2014 Express bez żadnych problemów.

Bob Bryan
źródło