Impossibile creare un indice filtrato su una colonna calcolata


18

In una mia precedente domanda, è una buona idea disabilitare l'escalation dei blocchi durante l'aggiunta di nuove colonne calcolate a una tabella? , Sto creando una colonna calcolata:

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;

La colonna calcolata è PERSISTED, e secondo computed_column_definition (Transact-SQL) :

PERSISTED

Specifica che Motore di database memorizzerà fisicamente i valori calcolati nella tabella e aggiornerà i valori quando vengono aggiornate eventuali altre colonne da cui dipende la colonna calcolata. Contrassegnare una colonna calcolata come PERSISTED consente di creare un indice su una colonna calcolata che è deterministico, ma non preciso. Per ulteriori informazioni, consultare Indici su colonne calcolate. Tutte le colonne calcolate utilizzate come colonne di partizionamento di una tabella partizionata devono essere esplicitamente contrassegnate come PERSISTED. computed_column_expression deve essere deterministico quando viene specificato PERSISTED.

Ma quando provo a creare un indice sulla mia colonna ottengo il seguente errore:

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

L'indice filtrato 'FIX_tblBGiftVoucherItem_incl' non può essere creato nella tabella 'dbo.tblBGiftVoucherItem' perché la colonna 'isUsGift' nell'espressione di filtro è una colonna calcolata. Riscrivi l'espressione di filtro in modo che non includa questa colonna.

Come posso creare un indice filtrato su una colonna calcolata?

o

C'è una soluzione alternativa?


3
WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%')Tuttavia, è possibile creare un indice filtrato .
ypercubeᵀᴹ

Risposte:


21

Sfortunatamente a partire da SQL Server 2014, non è possibile creare un punto in Filtered Indexcui il filtro si trova su una colonna calcolata (indipendentemente dal fatto che sia persistente o meno).

È stato aperto un articolo Connect dal 2009, quindi vai avanti e vota. Forse Microsoft lo risolverà un giorno.

Aaron Bertrand ha un articolo che tratta una serie di altri problemi con gli indici filtrati .


21

Sebbene non sia possibile creare un indice filtrato su una colonna persistente, esiste una soluzione abbastanza semplice che è possibile utilizzare.

Come test, ho creato una tabella semplice con una IDENTITYcolonna e una colonna calcolata persistente basata sulla colonna identità:

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

Quindi, ho creato una vista associata allo schema basata sulla tabella con un filtro sulla colonna calcolata:

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

Successivamente, ho creato un indice cluster sulla vista associata allo schema, che ha l'effetto di mantenere i valori memorizzati nella vista, incluso il valore della colonna calcolata:

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

Inserisci alcuni dati di test nella tabella:

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;

Crea un elemento statistico e un indice nella vista:

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

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

L'esecuzione di SELECTistruzioni sulla tabella con la colonna persistente ora può utilizzare automaticamente la vista persistente, se Query Optimizer determina che ha senso farlo:

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

Il piano di esecuzione effettivo per la query sopra mostra che Query Optimizer ha scelto di utilizzare la vista persistente per restituire i risultati:

inserisci qui la descrizione dell'immagine

Potresti aver notato la conversione esplicita nella WHEREclausola sopra. Questo esplicito CONVERT(INT, 26)consente a Query Optimizer di utilizzare correttamente l'oggetto statistics per stimare il numero di righe che verranno restituite dalla query. Se scriviamo la query con WHERE pv.TestComputedColumn = 26, Query Optimizer potrebbe non stimare correttamente il numero di righe poiché 26 è effettivamente considerato un TINY INT; ciò potrebbe impedire a SQL Server di utilizzare la vista persistente. Le conversioni implicite possono essere molto dolorose e vale la pena utilizzare costantemente i tipi di dati corretti per confronti e join.

Ovviamente, tutti gli "gotcha" standard risultanti dall'uso dell'associazione dello schema si applicano allo scenario sopra; ciò potrebbe impedire l'utilizzo di questa soluzione alternativa in tutti gli scenari. Ad esempio, non sarà più possibile modificare la tabella di base senza prima rimuovere l'associazione dello schema dalla vista. Per fare ciò, dovrai rimuovere l'indice cluster dalla vista.

Se non si dispone di SQL Server Enterprise Edition, Query Optimizer non utilizzerà automaticamente la vista persistente per le query che non fanno direttamente riferimento alla vista utilizzando il WITH (NOEXPAND)suggerimento. Per ottenere il vantaggio di utilizzare la vista persistente nelle versioni non Enterprise Edition, dovrai riscrivere la query sopra in qualcosa del tipo:

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

Grazie a Ian Ringrose per aver sottolineato la limitazione Enterprise Edition sopra e a Paul White per il (NOEXPAND)suggerimento.

Questa risposta di Paul ha alcuni dettagli interessanti sull'ottimizzatore delle query in relazione alle viste persistenti.


La soluzione alternativa mostra che nella vista vengono creati sia un indice cluster che un indice non cluster. L'indice non cluster deve essere utilizzato sull'indice cluster per qualche motivo? Oppure l'indice non cluster è più performante? Se nella query venisse utilizzato l'indice cluster, che cosa mostrerebbero le statistiche?
Bob Bryan,

Domanda interessante, @BobBryan: per consentire la persistenza della vista è necessario l'indice cluster, sebbene in realtà non debba essere un indice univoco. Avrei potuto creare l'indice cluster della vista su qualche altra colonna, come TestComputedColumninvece. Tuttavia, poiché l'indice cluster contiene tutti i dati per la tabella / vista, ho deciso che sarebbe probabilmente meglio usare un numero crescente monotonicamente come chiave di clustering. Nota, in realtà non ho testato tale supposizione, e in effetti potrebbe essere errato per alcune variazioni della riproduzione.
Max Vernon,

Si noti che l'indice non cluster non è un indice di copertura e pertanto qualsiasi query che filtra, unisce o restituisce colonne dalla vista o dalla tabella sottostante dovrà eseguire un'operazione di ricerca della chiave sulla tabella di base o la vista. È probabile che per uno scenario del mondo reale, l'ambito limitato della mia risposta potrebbe essere spiegato tenendo conto delle prestazioni ancora migliori.
Max Vernon,

4

Da Create Indexe la sua whereclausola, questo non è possibile:

DOVE

Crea un indice filtrato specificando quali righe includere nell'indice. L'indice filtrato deve essere un indice non cluster su una tabella. Crea statistiche filtrate per le righe di dati nell'indice filtrato.

Il predicato del filtro utilizza una semplice logica di confronto e non può fare riferimento a una colonna calcolata, una colonna UDT, una colonna del tipo di dati spaziali o una colonna del tipo di dati hierarchyID. I confronti utilizzando valori letterali NULL non sono consentiti con gli operatori di confronto. Utilizzare invece gli operatori IS NULL e IS NOT NULL.

Fonte: MSDN


3
  • È necessaria una colonna che non viene calcolata per attivare l'indice filtrato.
  • Devi calcolare il valore per andare in quella colonna.

Prima avevamo calcolato le colonne che usavamo trigger per calcolare il valore delle colonne ogni volta che la riga veniva modificata o inserita.

(Un trigger può anche essere utilizzato per inserire / rimuovere il PK dell'elemento da una seconda tabella che è stata quindi utilizzata nelle query.)


3

Questo è un tentativo di migliorare il lavoro di Max Vernon . Nella sua soluzione, suggerisce di utilizzare 2 indici nella vista e un oggetto statistico.

Il primo indice è in cluster, il che è effettivamente richiesto poiché, diversamente da un indice non cluster su una tabella, verrà generato un errore se si tenta di creare un indice non cluster nella vista senza prima avere un indice cluster.

Il secondo indice è un indice non cluster, che viene utilizzato come indice dietro la query. Nella sezione commenti della sua risposta, ho chiesto cosa sarebbe successo se fosse stato utilizzato un indice cluster invece di un indice non cluster.

La seguente analisi prova a rispondere a questa domanda.

Sto usando il suo stesso identico codice, tranne per il fatto che non sto creando un indice non cluster nella vista.

Inoltre non sto creando un oggetto statistico. Se stai seguendo e stai usando SQL Server Management Studio (SSMS) per inserire il codice qui sotto, dovresti essere consapevole che potresti vedere alcune linee rosse che sembrano errori. Questi non sono (probabilmente) errori, ma comportano un problema con intellisense.

Puoi disabilitare intellisense o semplicemente ignorare gli errori ed eseguire i comandi. Dovrebbero completare senza errori.

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

Il seguente piano di esecuzione (senza vista / vista indice) viene prodotto dopo l'esecuzione della seguente query sulla tabella:

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

inserisci qui la descrizione dell'immagine

Questo fornisce una linea di base per il confronto. Si noti che dopo il completamento della query, è stato creato un oggetto statistico (_WA_Sys_00000003_1FCDBCEB). L'oggetto statistico PK_PersistedViewTest è stato creato al momento della creazione dell'indice della tabella in cluster.

Successivamente, vengono create la vista filtrata e l'indice cluster su quella vista:

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

Ora, proviamo a eseguire nuovamente la query, ma questa volta rispetto alla vista:

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

Il nuovo piano di esecuzione è ora:

inserisci qui la descrizione dell'immagine

Se si deve credere al nuovo piano, dopo l'aggiunta della vista e dell'indice cluster in quella vista, le statistiche sembrano indicare che il tempo necessario per eseguire la query è ora raddoppiato. Si noti inoltre che non è stato creato alcun nuovo oggetto statistico per supportare il nuovo indice dopo l'esecuzione della query, che è diverso dalla query sulla tabella.

Il piano di query suggerisce ancora che la creazione di un indice non cluster sarebbe molto utile per migliorare le prestazioni della query. Quindi, ciò significa che un indice non cluster deve essere aggiunto alla vista prima di ottenere il miglioramento delle prestazioni desiderato? C'è un'ultima cosa da provare. Modifica la query per utilizzare l'opzione "WITH NOEXPAND":

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

Ciò comporta il seguente piano di query:

inserisci qui la descrizione dell'immagine

Questo piano di esecuzione sembra abbastanza simile a quello prodotto con l'indice non cluster indicato nella risposta di Max Vernon. Ma questo viene fatto con un indice in meno (non cluster) e un oggetto in statistica in meno.

Si scopre che l'opzione NOEXPAND deve essere utilizzata con le versioni express e standard di SQL Server per utilizzare correttamente una vista indicizzata. Paul White ha un eccellente articolo che espone i vantaggi dell'utilizzo dell'opzione NOEXPAND. Raccomanda inoltre di utilizzare questa opzione con l'edizione enterprise per garantire che l'ottimizzatore utilizzi la garanzia di unicità fornita dagli indici di visualizzazione.

L'analisi sopra è stata eseguita con l'edizione express di SQL Sever 2014. L'ho provata anche con l'edizione per sviluppatori di SQL Server 2016. L'opzione NOEXPAND non sembra essere richiesta con l'edizione di sviluppo per ottenere i miglioramenti delle prestazioni, ma è comunque consigliata .

Meno di 5 mesi fa, Microsoft ha reso le edizioni degli sviluppatori gratuite . La licenza limita l'uso allo sviluppo, il che significa che il database non può essere utilizzato in un ambiente di produzione. Quindi, se hai cercato di testare tabelle ottimizzate per la memoria, crittografia, R, ecc., Non hai più la scusa senza licenza. L'ho installato con successo sul mio computer qualche giorno fa insieme a SQL Server 2014 Express senza problemi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.