Невозможно создать отфильтрованный индекс для вычисляемого столбца

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

PERSISTED

Указывает, что компонент Database Engine будет физически хранить вычисленные значения в таблице и обновлять значения при обновлении любых других столбцов, от которых зависит вычисляемый столбец. Пометка вычисляемого столбца как PERSISTED позволяет создать индекс для вычисляемого столбца, который является детерминированным, но не точным. Для получения дополнительной информации см. Индексы для вычисляемых столбцов. Любые вычисляемые столбцы, используемые в качестве разделительных столбцов разделенной таблицы, должны быть явно помечены как PERSISTED. 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местоположение фильтра в вычисляемом столбце (независимо от того, сохранен он или нет).

С 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 не будет использовать постоянное представление. Неявные преобразования могут быть очень болезненными, и стоит постоянно использовать правильные типы данных для сравнений и объединений.

Конечно, все стандартные «ошибки», возникающие в результате использования привязки схемы, применимы к вышеописанному сценарию; Это может помешать использовать этот обходной путь во всех сценариях. Например, больше невозможно будет изменить базовую таблицу, не удалив сначала привязку схемы из представления. Для этого вам необходимо удалить кластерный индекс из представления.

Если у вас нет 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, столбец типа пространственных данных или столбец типа данныхierarchyID. Сравнения с использованием литералов NULL недопустимы с операторами сравнения. Вместо этого используйте операторы IS NULL и IS NOT NULL.

Источник: MSDN

Жюльен Вавассер
источник
3
  • Вам нужен столбец, который не рассчитан для добавления отфильтрованного индекса.
  • Вам нужно вычислить значение, чтобы перейти в этот столбец.

Прежде чем мы вычислили столбцы, мы использовали триггеры для вычисления значения столбцов всякий раз, когда строка была изменена или вставлена.

(Триггер также можно использовать для вставки / удаления PK элемента из второй таблицы, которая затем использовалась в запросах.)

Ян Рингроз
источник
3

Это попытка улучшить работу Макса Вернона . В своем решении он предлагает использовать 2 индекса для представления и объект статистики.

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

Второй индекс - это некластеризованный индекс, который используется в качестве индекса за запросом. В разделе комментариев его ответа я спросил, что произойдет, если вместо некластеризованного индекса будет использован кластерный индекс.

Следующий анализ пытается ответить на этот вопрос.

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

Я также не создаю объект статистики. Если вы следите за этим и используете 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

Новый план выполнения теперь:

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

Если верить новому плану, то после добавления представления и кластеризованного индекса в этом представлении появляется статистика, показывающая, что время, необходимое для выполнения запроса, теперь удвоилось. Также обратите внимание, что не был создан новый объект статистики для поддержки нового индекса после выполнения запроса, который отличается от запроса в таблице.

План запроса по-прежнему предполагает, что создание некластеризованного индекса было бы весьма полезным для повышения производительности запроса. Итак, означает ли это, что некластерный индекс должен быть добавлен в представление до того, как может быть достигнуто желаемое улучшение производительности? Есть еще одна вещь, которую нужно попробовать. Измените запрос, чтобы использовать параметр «WITH 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 без проблем.

Боб Брайан
источник