Неможливо створити відфільтрований індекс у обчисленій колонці


18

У попередньому моєму запитанні, чи корисно вимкнути ескалацію блокування, додаючи до таблиці нові обчислені стовпці? , Я створюю обчислений стовпець:

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;

Розрахований стовпчик є PERSISTED, і відповідно до computed_column_definition (Transact-SQL) :

ПЕРСПЕКТИВНИЙ

Вказує, що Механізм баз даних фізично зберігатиме обчислювані значення в таблиці та оновлюватиме значення, коли будуть оновлені будь-які інші стовпці, від яких залежить обчислений стовпець. Позначення обчислюваного стовпця ПЕРСОНАЛОМ дозволяє створювати індекс на обчисленому стовпчику, який є детермінованим, але не точним. Для отримання додаткової інформації див. Покажчики обчислених стовпців. Будь-які обчислені стовпці, що використовуються як стовпчики розділення таблиці, повинні бути явно позначені ПЕРСОНАЛЬНО. computed_column_expression має бути детермінованим, коли вказано PERSISTED.

Але коли я намагаюся створити індекс у своїй колонці, я отримую таку помилку:

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

Фільтрований індекс 'FIX_tblBGiftVoucherItem_incl' неможливо створити в таблиці 'dbo.tblBGiftVoucherItem', оскільки стовпець 'isUsGift' у виразі фільтра є обчисленою колоною. Перепишіть вираз фільтра, щоб він не включав цей стовпець.

Як я можу створити відфільтрований індекс на обчисленому стовпчику?

або

Чи є альтернативне рішення?


3
Ви можете створити відфільтрований індекс на WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%')хоч.
ypercubeᵀᴹ

Відповіді:


21

На жаль, як і для SQL Server 2014, немає можливості створити Filtered Index фільтр у обчислюваній колонці (незалежно від того, зберігається чи ні).

Там було Connect Item відкритий з 2009 року, тому , будь ласка , йти вперед і проголосувати за нього. Можливо, Microsoft виправить це одного дня.

Аарон Бертран має статтю, яка висвітлює ряд інших проблем із « Фільтрованими індексами» .


21

Хоча ви не можете створити відфільтрований індекс на стовпчику, що зберігається, ви можете використовувати досить просте рішення.

Як тест, я створив просту таблицю зі IDENTITYстовпцем та стійкий обчислений стовпчик на основі стовпця ідентичності:

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

Потім я створив перегляд, пов'язаний із схемою, на основі таблиці з фільтром у обчисленому стовпці:

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

Далі я створив кластерний індекс на перегляді, пов'язаному зі схемою, який має ефект збереження значень, збережених у поданні, включаючи значення обчисленого стовпця:

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

Вставте в таблицю деякі дані тесту:

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;

Створіть елемент статистики та індекс для подання:

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

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

Виконуючи SELECTоператори проти таблиці із стовпчиком, що зберігається, тепер може автоматично використовувати збережений перегляд, якщо оптимізатор запитів визначає, що це має сенс:

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

Фактичний план виконання вищевказаного запиту показує, що оптимізатор запитів вирішив використовувати збережений вид для повернення результатів:

введіть тут опис зображення

Можливо, ви помітили явне перетворення у WHEREвищезазначеному пункті. Це явне CONVERT(INT, 26)дозволяє оптимізатору запитів правильно використовувати об'єкт статистики для оцінки кількості рядків, які будуть повернуті запитом. Якщо ми пишемо запит за допомогою WHERE pv.TestComputedColumn = 26, оптимізатор запитів може неправильно оцінити кількість рядків, оскільки 26 насправді вважаєтьсяTINY INT ; це може призвести до того, що SQL Server не використовувати збережене подання. Неявні перетворення можуть бути дуже болісними, і платять послідовно використовувати правильні типи даних для порівнянь та об'єднань.

Звичайно, всі стандартні "gotchas", отримані в результаті використання прив'язки схеми, застосовуються до вищезазначеного сценарію; це може унеможливити використання цього вирішення у всіх сценаріях. Наприклад, більше не можна буде змінювати базову таблицю, попередньо не видаляючи прив'язку схеми з подання. Для цього вам потрібно буде видалити кластерний індекс із подання.

Якщо у вас немає SQL Server Enterprise Edition, оптимізатор запитів не буде автоматично використовувати збережений перегляд для запитів, які не посилаються безпосередньо на представлення, використовуючи WITH (NOEXPAND)підказку. Щоб усвідомити перевагу використання збереженого перегляду у версіях, що не належать до Enterprise Edition, вам потрібно буде переписати вищезазначений запит на щось подібне:

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

Дякуємо Іану Рінгроуз за вказівку вище обмеження на Enterprise Edition та Полу Уайту за (NOEXPAND)підказку.

У цій відповіді Павла є цікаві деталі щодо оптимізатора запитів стосовно збережених поглядів.


Робота навколо показує, що в представленні створюється як кластерний індекс, так і некластеризований індекс. Чи потрібно чомусь використовувати некластеризований індекс над кластерним індексом? Або, некластерний індекс є більш ефективним? Якби кластерний індекс був використаний у запиті, що б показувала статистика?
Боб Брайан

Цікаве запитання, @BobBryan - кластерний індекс необхідний для того, щоб перегляд міг зберігатись, хоча насправді він не повинен бути унікальним індексом. Я міг би створити кластерний індекс перегляду в іншому стовпчику, наприклад TestComputedColumnнатомість. Однак, оскільки кластерний індекс містить всі дані для таблиці / представлення, я вирішив, що, ймовірно, буде краще використовувати монотонно зростаюче число як кластеризуючий ключ. Зауважте, я насправді не перевіряв це припущення, і воно може бути неправильним для деяких варіантів докори.
Макс Вернон

Зауважте, некластеризований індекс не є покривним індексом, і як такий будь-який запит, який або фільтрує на, приєднується, або повертає стовпці або з перегляду, або з нижньої таблиці, потрібно буде виконати операцію пошуку ключів проти базової таблиці або вид. Цілком імовірно, що в реальному сценарії обмежений обсяг моєї відповіді може бути пояснений з ще кращою ефективністю на увазі.
Макс Вернон

4

З Create Indexта його whereпункту це неможливо:

ДЕ

Створює відфільтрований індекс, вказуючи, які рядки включати до індексу. Відфільтрований індекс повинен бути некластеризованим індексом таблиці. Створює відфільтровану статистику для рядків даних у відфільтрованому індексі.

Присудок фільтра використовує просту логіку порівняння і не може посилатися на обчислений стовпець, стовпець UDT, стовпець типу просторових даних або стовпець типу ієрархії даних. Порівняння з використанням NULL літералів не дозволено з операторами порівняння. Використовуйте оператори IS NULL, а НЕ NULL.

Джерело: MSDN


3
  • Вам потрібен стовпець, який не розрахований, щоб розмістити відфільтрований індекс.
  • Вам потрібно обчислити значення, щоб перейти до цього стовпця.

Перш ніж ми обчислювали стовпці, ми використовували тригери для обчислення значення стовпців щоразу, коли рядок було змінено чи вставлено.

(Тригер також може бути використаний для вставки / видалення ПК ПК з 2-ї таблиці, яка потім використовувалася в запитах.)


3

Це спроба вдосконалити роботу Макса Вернона . У своєму рішенні він пропонує використовувати 2 індекси для представлення даних та об’єкта статистики.

1-й індекс є кластеризованим, що фактично потрібно, оскільки на відміну від некластеризованого індексу в таблиці, буде створена помилка, якщо спробу створення некластеризованого індексу в представленні подано без спроби кластерного індексу.

2-й індекс - це некластеризований індекс, який використовується як індекс за запитом. У розділі коментарів у своїй відповіді я запитав, що буде, якщо замість некластеризованого індексу буде використаний кластерний індекс.

Наступний аналіз намагається дати відповідь на це питання.

Я використовую його точно такий же код, за винятком того, що я не створюю некластеризований індекс для подання

Я також не створюю об’єкт статистики. Якщо ви слідуєте за та використовуєте SQL Server Management Studio (SSMS) для введення коду нижче, вам слід пам’ятати, що ви можете побачити деякі червоні чіткі лінії - схожі на помилки. Це (ймовірно) не помилки, але пов'язані з проблемою intellisense.

Можна або відключити intellisense або просто ігнорувати помилки та запускати команди. Вони повинні виконуватись без помилок.

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

Наступний план виконання (без перегляду / покажчика) створюється після запуску наступного запиту проти таблиці:

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

введіть тут опис зображення

Це дає базову лінію для порівняння. Зауважте, що після завершення запиту було створено об’єкт статистики (_WA_Sys_00000003_1FCDBCEB). Об'єкт статистики PK_PersistedViewTest був створений під час створення індексу кластерної таблиці.

Далі створюються відфільтрований вигляд та кластерний індекс для цього виду:

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

Тепер спробуємо запустити запит ще раз, але цього разу проти перегляду:

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

Новий план виконання:

введіть тут опис зображення

Якщо вірити новому плану, після додавання перегляду та кластеризованого індексу для цього представлення з’являється статистика, яка свідчить про те, що час, необхідний для виконання запиту, тепер подвоївся. Також зауважте, що після запуску запиту не було створено жодного нового об’єкта статистики для підтримки нового індексу, який відрізняється від запиту в таблиці.

План запитів все ще передбачає, що створення некластеризованого індексу було б дуже корисним для підвищення продуктивності запиту. Отже, чи означає це, що перед переглядом потрібно додати некластеризований індекс, перш ніж можна отримати бажане поліпшення продуктивності? Є одне останнє, що потрібно спробувати. Змініть запит, щоб використовувати параметр "З NOEXPAND":

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

Це призводить до отримання наступного плану запитів:

введіть тут опис зображення

Цей план виконання виглядає досить схожим на той, який був складений з некластеризованим індексом, наведеним у відповіді Макса Вернона. Але це робиться з одним меншим (некластеризованим) індексом та одним меншим об'єктом статистики.

Виявляється, що параметр NOEXPAND повинен використовуватися з експрес-та стандартними версіями SQL Server, щоб правильно використовувати індексований вигляд. Пол Уайт має чудову статтю, яка пояснює переваги використання опції NOEXPAND. Він також рекомендує використовувати цей варіант разом з корпоративним виданням, щоб забезпечити оптимізатор гарантії унікальності, наданої індексами перегляду.

Вищевказаний аналіз був зроблений з експрес-виданням SQL Sever 2014. Я також спробував це з версією для розробників SQL Server 2016. Опція NOEXPAND не потрібна версії розробки для досягнення підвищення продуктивності, але все ж рекомендується .

Менш ніж 5 місяців тому Microsoft зробила видання для розробників безкоштовними . Ліцензія обмежує використання лише розробкою, що означає, що база даних не може бути використана у виробничому середовищі. Отже, якщо ви хотіли перевірити таблиці, оптимізовані для пам’яті, шифрування, R тощо, то у вас більше немає виправдання без ліцензії. Я успішно встановив його на своєму комп’ютері кілька днів тому разом із SQL Server 2014 Express без проблем.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.