Es kann kein gefilterter Index für eine berechnete Spalte erstellt werden

17

In einer früheren Frage von mir, ist es eine gute Idee zu deaktivieren Sperreneskalation während neue berechnete Spalten zu einer Tabelle hinzufügen? , Ich erstelle eine berechnete Spalte:

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;

Die berechnete Spalte lautet PERSISTEDlaut computed_column_definition (Transact-SQL) :

BESTÄNDIG

Gibt an, dass das Datenbankmodul die berechneten Werte physisch in der Tabelle speichert und die Werte aktualisiert, wenn andere Spalten aktualisiert werden, von denen die berechnete Spalte abhängt. Durch Markieren einer berechneten Spalte als PERSISTED kann ein Index für eine berechnete Spalte erstellt werden, die deterministisch, aber nicht genau ist. Weitere Informationen finden Sie unter Indizes für berechnete Spalten. Berechnete Spalten, die als Partitionierungsspalten einer partitionierten Tabelle verwendet werden, müssen explizit als PERSISTED gekennzeichnet werden. computed_column_expression muss bei Angabe von PERSISTED deterministisch sein.

Wenn ich jedoch versuche, einen Index für meine Spalte zu erstellen, wird die folgende Fehlermeldung angezeigt:

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

Der gefilterte Index 'FIX_tblBGiftVoucherItem_incl' kann für die Tabelle 'dbo.tblBGiftVoucherItem' nicht erstellt werden, da die Spalte 'isUsGift' im Filterausdruck eine berechnete Spalte ist. Schreiben Sie den Filterausdruck so um, dass er diese Spalte nicht enthält.

Wie kann ich einen gefilterten Index für eine berechnete Spalte erstellen?

oder

Gibt es eine alternative Lösung?

Marcello Miorelli
quelle
3
Sie können jedoch einen gefilterten Index erstellen WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%').
Ypercubeᵀᴹ

Antworten:

20

Leider ist es ab SQL Server 2014 nicht möglich, einen Filtered IndexFilter zu erstellen, bei dem sich der Filter in einer berechneten Spalte befindet (unabhängig davon, ob er beibehalten wird oder nicht).

Seit 2009 ist ein Connect Item geöffnet. Bitte stimmen Sie ab. Vielleicht wird Microsoft dies eines Tages beheben.

Aaron Bertrand hat einen Artikel, der eine Reihe anderer Probleme mit gefilterten Indizes behandelt .

Mark Sinkinson
quelle
21

Obwohl Sie keinen gefilterten Index für eine persistierte Spalte erstellen können, gibt es eine relativ einfache Problemumgehung, die Sie möglicherweise verwenden können.

Als Test habe ich eine einfache Tabelle mit einer IDENTITYSpalte und einer fortgesetzten berechneten Spalte basierend auf der Identitätsspalte erstellt:

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

Dann habe ich eine schemagebundene Ansicht basierend auf der Tabelle mit einem Filter für die berechnete Spalte erstellt:

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

Als Nächstes habe ich einen Clustered-Index für die schemagebundene Ansicht erstellt, wodurch die in der Ansicht gespeicherten Werte beibehalten werden, einschließlich des Werts der berechneten Spalte:

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

Fügen Sie einige Testdaten in die Tabelle ein:

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;

Erstellen Sie ein Statistikelement und einen Index für die Ansicht:

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

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

Das Ausführen von SELECTAnweisungen für die Tabelle mit der persistierten Spalte kann jetzt automatisch die persistierte Ansicht verwenden, wenn das Abfrageoptimierungsprogramm dies für sinnvoll erachtet:

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

Der tatsächliche Ausführungsplan für die obige Abfrage zeigt, dass das Abfrageoptimierungsprogramm ausgewählt hat, die permanente Ansicht zu verwenden, um die Ergebnisse zurückzugeben:

Bildbeschreibung hier eingeben

Möglicherweise haben Sie die explizite Konvertierung in der WHEREobigen Klausel bemerkt . Diese explizite Methode CONVERT(INT, 26)ermöglicht es dem Abfrageoptimierer, das Statistikobjekt ordnungsgemäß zu verwenden, um die Anzahl der Zeilen zu schätzen, die von der Abfrage zurückgegeben werden. Wenn wir die Abfrage mit schreiben WHERE pv.TestComputedColumn = 26, schätzt das Abfrageoptimierungsprogramm die Anzahl der Zeilen möglicherweise nicht richtig, da 26 tatsächlich als a betrachtet wird TINY INT. Dies kann dazu führen, dass SQL Server die permanente Ansicht nicht verwendet. Implizite Konvertierungen können sehr schmerzhaft sein, und es lohnt sich, für Vergleiche und Verknüpfungen stets die richtigen Datentypen zu verwenden.

Natürlich gelten alle Standard-Fallstricke, die sich aus der Verwendung der Schemabindung ergeben, für das obige Szenario. Dies kann die Verwendung dieser Problemumgehung in allen Szenarien verhindern. Beispielsweise ist es nicht mehr möglich, die Basistabelle zu ändern, ohne zuvor die Schemabindung aus der Ansicht zu entfernen. Dazu müssen Sie den Clustered-Index aus der Ansicht entfernen.

Wenn Sie nicht über SQL Server Enterprise Edition verfügen, verwendet das Abfrageoptimierungsprogramm die persistierte Ansicht nicht automatisch für Abfragen, bei denen der WITH (NOEXPAND)Hinweis nicht direkt auf die Ansicht verweist . Um den Vorteil der Verwendung der beständigen Ansicht in Nicht-Enterprise Edition-Versionen zu erkennen, müssen Sie die obige Abfrage in etwa wie folgt umschreiben:

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

Vielen Dank an Ian Ringrose für den Hinweis auf die oben genannte Einschränkung der Enterprise Edition und an Paul White für den (NOEXPAND)Hinweis.

Diese Antwort von Paul enthält einige interessante Details zum Abfrageoptimierer in Bezug auf dauerhafte Ansichten.

Max Vernon
quelle
Die Problemumgehung zeigt, dass für die Ansicht sowohl ein Clustered-Index als auch ein Nonclustered-Index erstellt wird. Muss der nicht gruppierte Index aus irgendeinem Grund über dem gruppierten Index verwendet werden? Oder ist der nicht gruppierte Index performanter? Wenn der Clustered-Index in der Abfrage verwendet würde, was würde die Statistik anzeigen?
Bob Bryan
Interessante Frage, @BobBryan - Der Clustered-Index ist erforderlich, damit die Ansicht beibehalten werden kann, obwohl es sich eigentlich nicht um einen eindeutigen Index handeln muss. Ich hätte den Clustered-Index der Ansicht für eine andere Spalte erstellen können, z. B. für die TestComputedColumn. Da der Clustered-Index jedoch alle Daten für die Tabelle / Ansicht enthält, habe ich entschieden, dass es wahrscheinlich besser ist, eine monoton ansteigende Zahl als Clustering-Schlüssel zu verwenden. Beachten Sie, dass ich diese Vermutung nicht wirklich getestet habe und sie für einige Variationen des Repros möglicherweise falsch ist.
Max Vernon
Beachten Sie, dass der nicht gruppierte Index kein abdeckender Index ist. Daher muss für jede Abfrage, die Spalten aus der Ansicht oder der zugrunde liegenden Tabelle filtert, verknüpft oder zurückgibt, eine Schlüsselsuchoperation für die Basistabelle oder ausgeführt werden die Aussicht. Es ist wahrscheinlich, dass in einem realen Szenario der begrenzte Umfang meiner Antwort im Hinblick auf eine noch bessere Leistung erläutert werden könnte.
Max Vernon
4

Von Create Indexund seiner whereKlausel ist dies nicht möglich:

WOHER

Erstellt einen gefilterten Index, indem angegeben wird, welche Zeilen in den Index aufgenommen werden sollen. Der gefilterte Index muss ein nicht gruppierter Index für eine Tabelle sein. Erstellt gefilterte Statistiken für die Datenzeilen im gefilterten Index.

Das Filterprädikat verwendet eine einfache Vergleichslogik und kann nicht auf eine berechnete Spalte, eine UDT-Spalte, eine räumliche Datentypspalte oder eine hierarchyID-Datentypspalte verweisen. Vergleiche mit NULL-Literalen sind mit den Vergleichsoperatoren nicht zulässig. Verwenden Sie stattdessen die Operatoren IS NULL und IS NOT NULL.

Quelle: MSDN

Julien Vavasseur
quelle
3
  • Sie benötigen eine Spalte, die nicht berechnet wird, um den gefilterten Index zu aktivieren.
  • Sie müssen den Wert berechnen, um in diese Spalte zu gelangen.

Bevor wir Spalten berechnet hatten, haben wir Trigger verwendet , um den Spaltenwert zu berechnen, wenn die Zeile geändert oder eingefügt wurde.

(Ein Trigger kann auch verwendet werden, um die PK des Elements aus einer zweiten Tabelle einzufügen / zu entfernen, die dann in Abfragen verwendet wurde.)

Ian Ringrose
quelle
3

Dies ist ein Versuch, die Arbeit von Max Vernon zu verbessern . In seiner Lösung schlägt er vor, 2 Indizes für die Ansicht und ein Statistikobjekt zu verwenden.

Der 1. Index wird geclustert, was tatsächlich erforderlich ist, da im Gegensatz zu einem nicht geclusterten Index für eine Tabelle ein Fehler generiert wird, wenn versucht wird, einen nicht geclusterten Index für die Ansicht zu erstellen, ohne zuvor einen geclusterten Index zu haben.

Der 2. Index ist ein nicht gruppierter Index, der als Index hinter der Abfrage verwendet wird. Im Kommentarbereich seiner Antwort fragte ich, was passieren würde, wenn anstelle eines nicht gruppierten Index ein gruppierter Index verwendet würde.

Die folgende Analyse versucht, diese Frage zu beantworten.

Ich verwende genau denselben Code, außer dass ich keinen nicht gruppierten Index für die Ansicht erstelle.

Ich erstelle auch kein Statistikobjekt. Wenn Sie den folgenden Code mit SQL Server Management Studio (SSMS) eingeben, sollten Sie sich bewusst sein, dass möglicherweise rote, verzerrte Linien angezeigt werden, die wie Fehler aussehen. Dies sind (wahrscheinlich) keine Fehler, sondern ein Problem mit der Intellisense.

Sie können entweder Intellisense deaktivieren oder die Fehler ignorieren und die Befehle ausführen. Sie sollten fehlerfrei vervollständigt werden.

-- 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

Der folgende Ausführungsplan (ohne Sicht / Indexsicht) wird erstellt, nachdem die folgende Abfrage für die Tabelle ausgeführt wurde:

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

Bildbeschreibung hier eingeben

Dies gibt eine Vergleichsbasis. Beachten Sie, dass nach Abschluss der Abfrage ein Statistikobjekt erstellt wurde (_WA_Sys_00000003_1FCDBCEB). Das Statistikobjekt PK_PersistedViewTest wurde beim Erstellen des Clustered Table Index erstellt.

Als Nächstes werden die gefilterte Ansicht und der Clustered-Index für diese Ansicht erstellt:

-- 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

Versuchen wir nun erneut, die Abfrage auszuführen, diesmal jedoch gegen die Ansicht:

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

Der neue Ausführungsplan lautet nun:

Bildbeschreibung hier eingeben

Wenn der neue Plan nach dem Hinzufügen der Ansicht und des Clustered-Index für diese Ansicht angenommen werden soll, scheinen die Statistiken darauf hinzudeuten, dass sich die für die Ausführung der Abfrage erforderliche Zeit verdoppelt hat. Beachten Sie außerdem, dass nach der Ausführung der Abfrage kein neues Statistikobjekt zur Unterstützung des neuen Index erstellt wurde, das sich von der Abfrage in der Tabelle unterscheidet.

Der Abfrageplan schlägt weiterhin vor, dass die Erstellung eines nicht gruppierten Indexes zur Verbesserung der Leistung der Abfrage sehr hilfreich ist. Bedeutet dies, dass ein nicht gruppierter Index zur Ansicht hinzugefügt werden muss, bevor die gewünschte Leistungsverbesserung erzielt werden kann? Es gibt noch eine letzte Sache zu versuchen. Ändern Sie die Abfrage, um die Option "WITH NOEXPAND" zu verwenden:

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

Daraus ergibt sich der folgende Abfrageplan:

Bildbeschreibung hier eingeben

Dieser Ausführungsplan ähnelt dem, der mit dem in Max Vernons Antwort angegebenen nicht gruppierten Index erstellt wurde. Dies geschieht jedoch mit einem (nicht gruppierten) Index und einem Statistikobjekt weniger.

Es stellt sich heraus, dass die NOEXPAND-Option mit der Express- und der Standardversion von SQL Server verwendet werden muss, um eine indizierte Ansicht ordnungsgemäß zu verwenden. Paul White hat einen ausgezeichneten Artikel , in dem die Vorteile der NOEXPAND-Option erläutert werden. Er empfiehlt außerdem , diese Option in Verbindung mit der Enterprise Edition zu verwenden, um sicherzustellen, dass die von den Ansichtsindizes bereitgestellte Eindeutigkeitsgarantie vom Optimierer verwendet wird.

Die obige Analyse wurde mit der Express Edition von SQL Server 2014 durchgeführt. Ich habe sie auch mit der Developer Edition von SQL Server 2016 ausprobiert. Die NOEXPAND-Option scheint in der Development Edition nicht erforderlich zu sein, um die Leistungssteigerungen zu erzielen, wird jedoch weiterhin empfohlen .

Vor weniger als 5 Monaten hat Microsoft die Entwicklereditionen kostenlos zur Verfügung gestellt . Die Lizenz beschränkt die Verwendung nur auf die Entwicklung, was bedeutet, dass die Datenbank nicht in einer Produktionsumgebung verwendet werden kann. Wenn Sie also speicheroptimierte Tabellen, Verschlüsselung, R usw. testen möchten, gibt es keine lizenzfreie Ausrede mehr. Ich habe es vor ein paar Tagen zusammen mit SQL Server 2014 Express ohne Probleme erfolgreich auf meinem Computer installiert.

Bob Bryan
quelle