Tidak dapat membuat Indeks yang Difilter pada Kolom yang Dihitung

17

Dalam pertanyaan saya sebelumnya, Apakah ide yang baik untuk menonaktifkan eskalasi kunci sambil menambahkan kolom terhitung baru ke tabel? , Saya membuat kolom yang dihitung:

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;

Kolom yang dihitung adalah PERSISTED, dan menurut computed_column_definition (Transact-SQL) :

TERTENTU

Menentukan bahwa Mesin Basis Data akan secara fisik menyimpan nilai yang dihitung dalam tabel, dan memperbarui nilai ketika kolom lain yang bergantung pada kolom yang dihitung diperbarui. Menandai kolom yang dihitung sebagai PERSISTED memungkinkan indeks dibuat pada kolom yang dihitung yang bersifat deterministik, tetapi tidak tepat. Untuk informasi lebih lanjut, lihat Indeks pada Kolom yang Dihitung. Setiap kolom yang dihitung yang digunakan sebagai kolom partisi dari tabel yang dipartisi harus secara eksplisit ditandai PERSISTED. computed_column_expression harus deterministik ketika PERSISTED ditentukan.

Tetapi ketika saya mencoba membuat indeks pada kolom saya, saya mendapatkan kesalahan berikut:

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

Indeks yang difilter 'FIX_tblBGiftVoucherItem_incl' tidak dapat dibuat di tabel 'dbo.tblBGiftVoucherItem' karena kolom 'isUsGift' dalam ekspresi filter adalah kolom yang dihitung. Tulis ulang ekspresi filter sehingga tidak termasuk kolom ini.

Bagaimana saya bisa membuat indeks yang difilter pada kolom yang dihitung?

atau

Apakah ada solusi alternatif?

Marcello Miorelli
sumber
3
Anda dapat membuat indeks yang difilter pada WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%').
ypercubeᵀᴹ

Jawaban:

20

Sayangnya pada SQL Server 2014, tidak ada kemampuan untuk membuat Filtered Indextempat Filter berada pada kolom yang dikomputasi (terlepas dari apakah itu tetap ada atau tidak).

Sudah ada Item Sambung yang dibuka sejak 2009, jadi silakan lanjutkan dan pilih. Mungkin Microsoft akan memperbaikinya suatu hari.

Aaron Bertrand memiliki artikel yang mencakup sejumlah masalah lain dengan Indeks yang Difilter .

Mark Sinkinson
sumber
21

Meskipun Anda tidak dapat membuat indeks yang difilter pada kolom yang tetap, ada solusi yang cukup sederhana yang mungkin dapat Anda gunakan.

Sebagai ujian, saya telah membuat tabel sederhana dengan IDENTITYkolom, dan kolom yang dihitung tetap berdasarkan kolom identitas:

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

Lalu, saya membuat tampilan terikat skema berdasarkan tabel dengan filter pada kolom yang dihitung:

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

Berikutnya, saya membuat indeks berkerumun pada tampilan terikat skema, yang memiliki efek mempertahankan nilai yang disimpan dalam tampilan, termasuk nilai kolom yang dihitung:

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

Masukkan beberapa data uji ke dalam tabel:

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;

Buat item statistik dan indeks pada tampilan:

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

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

Melakukan SELECTpernyataan terhadap tabel dengan kolom tetap sekarang dapat secara otomatis menggunakan tampilan tetap, jika pengoptimal permintaan menentukan masuk akal untuk melakukannya:

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

Rencana eksekusi aktual untuk kueri di atas menunjukkan optimizer kueri memilih untuk menggunakan tampilan bertahan untuk mengembalikan hasil:

masukkan deskripsi gambar di sini

Anda mungkin telah memperhatikan konversi eksplisit dalam WHEREklausa di atas. Ini eksplisit CONVERT(INT, 26)memungkinkan pengoptimal permintaan untuk menggunakan objek statistik dengan benar untuk memperkirakan jumlah baris yang akan dikembalikan oleh permintaan. Jika kita menulis kueri dengan WHERE pv.TestComputedColumn = 26, pengoptimal kueri mungkin tidak memperkirakan jumlah baris dengan benar karena 26 dianggap sebagai aTINY INT ; ini dapat menyebabkan SQL Server tidak menggunakan tampilan bertahan. Konversi tersirat bisa sangat menyakitkan, dan membayar untuk secara konsisten menggunakan tipe data yang benar untuk perbandingan dan bergabung.

Tentu saja, semua standar "gotcha" yang dihasilkan dari penggunaan skema mengikat berlaku untuk skenario di atas; ini dapat mencegah penggunaan solusi ini di semua skenario. Misalnya, tidak akan lagi mungkin untuk memodifikasi tabel dasar tanpa terlebih dahulu menghapus skema pengikatan dari tampilan. Untuk melakukan itu, Anda harus menghapus indeks berkerumun dari tampilan.

Jika Anda tidak memiliki SQL Server Enterprise Edition, pengoptimal kueri tidak akan secara otomatis menggunakan tampilan bertahan untuk kueri yang tidak secara langsung mereferensikan tampilan menggunakan WITH (NOEXPAND)petunjuk. Untuk menyadari manfaat menggunakan tampilan bertahan dalam versi non-Enterprise Edition, Anda harus menulis ulang kueri di atas untuk sesuatu seperti:

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

Terima kasih kepada Ian Ringrose karena telah menunjukkan batasan Edisi Enterprise di atas, dan untuk Paul White atas (NOEXPAND)petunjuknya.

Jawaban oleh Paul ini memiliki beberapa detail menarik tentang pengoptimal permintaan sehubungan dengan pandangan yang ada.

Max Vernon
sumber
Pekerjaan di sekitar menunjukkan bahwa indeks berkerumun dan indeks tidak tercakup dibuat pada tampilan. Apakah indeks nonclustered harus digunakan di atas indeks berkerumun untuk beberapa alasan? Atau, apakah indeks nonclustered lebih berkinerja? Jika indeks berkerumun digunakan dalam kueri, apa yang akan ditampilkan statistik?
Bob Bryan
Pertanyaan yang menarik, @BobBryan - indeks berkerumun diperlukan untuk memungkinkan tampilan tetap ada, meskipun sebenarnya tidak perlu menjadi indeks unik. Saya bisa membuat indeks pengelompokan tampilan pada beberapa kolom lain, seperti TestComputedColumnsebaliknya. Namun, karena indeks berkerumun berisi semua data untuk tabel / tampilan, saya memutuskan akan lebih baik menggunakan angka yang meningkat secara monoton sebagai kunci pengelompokan. Catatan, saya tidak benar-benar menguji anggapan itu, dan mungkin sebenarnya tidak benar untuk beberapa variasi repro.
Max Vernon
Catatan, indeks non-cluster bukan indeks penutup, dan dengan demikian setiap permintaan yang menyaring, bergabung, atau mengembalikan kolom dari tampilan atau tabel yang mendasarinya perlu melakukan operasi pencarian kunci terhadap tabel dasar atau pandangan. Kemungkinan untuk skenario dunia nyata, cakupan jawaban saya yang terbatas dapat dijelaskan dengan kinerja yang lebih baik.
Max Vernon
4

Dari Create Indexdan whereklausulnya, ini tidak mungkin:

DIMANA

Membuat indeks yang difilter dengan menentukan baris mana yang akan dimasukkan dalam indeks. Indeks yang difilter harus merupakan indeks yang tidak tercakup pada tabel. Membuat statistik yang difilter untuk baris data dalam indeks yang difilter.

Predikat filter menggunakan logika perbandingan sederhana dan tidak dapat mereferensikan kolom yang dihitung, kolom UDT, kolom tipe data spasial, atau kolom tipe data hierarkiID. Perbandingan yang menggunakan NULL literal tidak diperbolehkan dengan operator pembanding. Gunakan operator IS NULL dan IS NOT NULL.

Sumber: MSDN

Julien Vavasseur
sumber
3
  • Anda memerlukan kolom yang tidak dihitung untuk menempatkan indeks yang difilter.
  • Anda perlu menghitung nilai untuk masuk dalam kolom itu.

Sebelum kami menghitung kolom, kami menggunakan pemicu untuk menghitung nilai kolom setiap kali baris diubah atau dimasukkan.

(Pemicu juga bisa digunakan untuk menyisipkan / menghapus PK item dari tabel ke-2 yang kemudian digunakan dalam kueri.)

Ian Ringrose
sumber
3

Ini adalah upaya untuk meningkatkan pekerjaan Max Vernon . Dalam solusinya, ia menyarankan menggunakan 2 indeks pada tampilan dan objek statistik.

Indeks 1 dikelompokkan, yang sebenarnya diperlukan karena tidak seperti indeks nonclustered pada tabel, kesalahan akan dihasilkan jika pembuatan indeks nonclustered pada tampilan diupayakan tanpa terlebih dahulu memiliki indeks cluster.

Indeks ke-2 adalah indeks nonclustered, yang digunakan sebagai indeks di balik kueri. Di bagian komentar dari jawabannya, saya bertanya apa yang akan terjadi jika indeks berkerumun digunakan bukan indeks yang tidak dikelompokkan.

Analisis berikut mencoba menjawab pertanyaan ini.

Saya menggunakan kode yang sama persis, kecuali saya tidak membuat indeks nonclustered pada tampilan.

Saya juga tidak membuat objek statistik. Jika Anda mengikuti dan menggunakan SQL Server Management Studio (SSMS) untuk memasukkan kode di bawah ini, Anda harus sadar bahwa Anda mungkin melihat beberapa garis berlekuk merah - yang terlihat seperti kesalahan. Ini (mungkin) bukan kesalahan, tetapi melibatkan masalah dengan intellisense.

Anda dapat menonaktifkan intellisense atau mengabaikan kesalahan dan menjalankan perintah. Mereka harus menyelesaikan tanpa kesalahan.

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

Rencana eksekusi berikut (tanpa tampilan / tampilan indeks) dibuat setelah kueri berikut dijalankan terhadap tabel:

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

masukkan deskripsi gambar di sini

Ini memberikan dasar untuk membandingkan. Perhatikan bahwa setelah kueri selesai, objek statistik dibuat (_WA_Sys_00000003_1FCDBCEB). Objek statistik PK_PersistedViewTest dibuat ketika indeks tabel berkerumun dibuat.

Selanjutnya, tampilan yang difilter dan indeks yang dikelompokkan pada tampilan tersebut dibuat:

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

Sekarang, mari kita coba jalankan kueri lagi, tapi kali ini bertentangan dengan pandangan:

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

Rencana eksekusi baru sekarang:

masukkan deskripsi gambar di sini

Jika rencana baru dapat dipercaya, setelah penambahan tampilan dan indeks berkerumun pada tampilan itu, statistik muncul untuk menunjukkan bahwa waktu yang diperlukan untuk menjalankan kueri kini telah berlipat ganda. Juga, perhatikan bahwa tidak ada objek statistik baru yang dibuat untuk mendukung indeks baru setelah kueri dijalankan, yang berbeda dari kueri pada tabel.

Rencana kueri masih menyarankan bahwa pembuatan indeks yang tidak tercakup akan sangat membantu dalam meningkatkan kinerja kueri. Jadi, apakah itu berarti bahwa indeks nonclustered harus ditambahkan ke tampilan sebelum peningkatan kinerja yang diinginkan dapat diperoleh? Ada satu hal terakhir untuk dicoba. Ubah kueri untuk menggunakan opsi "WITH NOEXPAND":

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

Ini menghasilkan rencana permintaan berikut:

masukkan deskripsi gambar di sini

Rencana eksekusi ini terlihat sangat mirip dengan yang dihasilkan dengan indeks nonclustered yang diberikan dalam jawaban Max Vernon. Tapi, ini dilakukan dengan satu indeks lebih sedikit (nonclustered) dan satu objek statistik kurang.

Ternyata opsi NOEXPAND harus digunakan dengan versi SQL Server express dan standar untuk memanfaatkan tampilan indeks. Paul White memiliki artikel bagus yang menguraikan manfaat menggunakan opsi NOEXPAND. Dia juga merekomendasikan opsi ini digunakan dengan edisi perusahaan untuk memastikan jaminan keunikan yang diberikan oleh indeks tampilan digunakan oleh pengoptimal.

Analisis di atas dilakukan dengan edisi ekspres SQL Sever 2014. Saya juga mencobanya dengan edisi pengembang SQL Server 2016. Opsi NOEXPAND tampaknya tidak diperlukan dengan edisi pengembangan untuk mencapai peningkatan kinerja, tetapi masih disarankan .

Kurang dari 5 bulan lalu, Microsoft membuat edisi pengembang gratis . Lisensi membatasi penggunaan hanya untuk pengembangan, yang berarti database tidak dapat digunakan dalam lingkungan produksi. Jadi, jika Anda mencari untuk menguji tabel yang dioptimalkan memori, enkripsi, R, dll. Maka Anda tidak lagi memiliki alasan tanpa lisensi. Saya berhasil menginstalnya di komputer saya beberapa hari yang lalu bersama SQL Server 2014 Express tanpa masalah.

Bob Bryan
sumber